Glossary
This site's vocabulary in one place: design-system, model, Figma fork, basketball and template terms, each in a sentence or two.
The terms that recur across these docs, alphabetically. Where a term has a fuller
treatment elsewhere, this entry is the one-line definition and the guide is the
detail. Definitions are grounded in CLAUDE.md and the .claude/rules, and
describe this repo, the Euro League website.
Bolt-on domain
A content domain this project bought on top of the baseline — listed in the
model's composition.json and resolved into the schema at compose time. The
baseline core and content domains always compose; this site adds video
(migrations video/0000–0005) and basketball. Basketball is this project's
own: it brings no inherited content types, and the basketball types are created
by our migrations in migrations/club/.
Bridge ({name}.figma.json)
The file that ties a component to its Figma design: the file key, the component
set and the node id of every variant. node packages/token-parser/scripts/check-bridges.mjs
checks, offline and in CI, that every bridge names a file this repo is actually
built from — a bridge whose ids resolve can still point at the wrong file (see Fork, below).
Club theme part
How the web app emits the club theme: in PARTS, with createClubThemePart, so a
route loads only the component themes it renders — ghost-core and ghost-content
on every page, video and basketball from their segment layouts. The club's global
layer is declared once, on :root. See
The web app.
Colour set
The first-class theming axis — one of ten per-surface themes (default,
inverse, base1, base2, colourSet1–colourSet6) that each specify a rich
surface (typography, links, backgrounds, borders, buttons, form fields). A
component may support a subset, and an unsupported value degrades to its
default. A dark surface is a colour set, never an onDark-style prop.
Colour way (ColourWay)
The Figma variant axis a component set uses for its surfaces — ColourWay=Default,
ColourWay=Inverse, ColourWay=ColorSet-1 and so on. In code it becomes the
component's colourSet prop. Polarity is per component (one set's Default is a
dark pill, another's is dark on light), so the mapping is read from that
component's own extracted bindings, never assumed; a composing component resolves
the way once and hands each child the value it supports.
Competition and competition section
Euro League runs three competitions — EuroLeague, EuroCup and NextGen — each a
Basketball 🏀 Competition entry. A competition is a section of the site: its
pages, news and data areas sit under its own addresses, and EuroCup and NextGen
carry their own theme layer (competition-themes.css.ts) over the club theme.
club.config.ts sections and @v4/model core/sections.ts hold the rules.
See Competition sections.
Competition Engine
The service that holds the competition data — seasons, games, teams, players,
coaches, standings and stats (stadionHQ/stadion-euroleague). @v4/competition-engine
is its only importer: types generated from the vendored OpenAPI spec, cached
server fetchers, and the merge in which an editorial value set in Contentful wins
over the engine's.
Component contract
The typed per-component token surface — the themeContract a component's
.theme.ts builds and its .css.ts reads via t.*. When Figma adds a token the
regenerated contract gains the path, so any component not consuming it shows up as
a visible diff.
Composition (composition.json)
The list of bolt-on domains an environment composes, in
packages/model/composition.json — for this site content, video and
basketball. It decides which migrations run, which route packages the app
mounts and which types the schema holds.
Composition fidelity
The user ruling that if component A composes B, C and D in Figma, the React component must import and compose the real B, C and D — never re-implement or placeholder a child that already exists (and build it as a standalone component first if it does not).
Contract marker
A // contract: <reason> comment required by lint-migrations.mjs on any
destructive or backwards-incompatible model migration (deleteField,
changeFieldId, required(true) on an existing field, deleteContentType) — the
proof that an expand/contract step ships one release after the code stops
depending on it.
Data area
One of the basketball sections of the site map — teams, players, coaches,
games, standings and stats (SPORT_AREAS). Each has a list per competition
at /<area>/<competition> and, for teams, players, coaches and games, one page
per item at /<area>/<slug>; a bare /<area> redirects to the default
competition's. Served by @v4/basketball-app; BASKETBALL_ROUTES=false turns
them all off.
Declared responsive mode (lg{Axis})
The house answer when a child's appearance must change at a breakpoint but is set
by a prop, which cannot follow a media query: the child takes an lg{Axis} prop
(lgOrientation, lgRootWidth, lgSize) that emits the media rule itself, and
the composing component declares the step once. Its default emits no rule, so
every other consumer is unchanged.
Design status
The status the client's Figma file records for each component, in the Statuses
group of its spec frame: a Department (Design or Development) and a Status
(Complete, Not Started, In Progress, Awaiting review, Roadmap), read from the
instance's variant properties. node packages/token-parser/scripts/design-status.mjs
reports it, and /fork-status crosses it with fork-diff so a refactor starts
only on finished designs. A ticket's DESIGN panel shows the same status.
Domain (ownership map)
A content domain is a slice of the model; domains.json is the ownership map
where every inherited content type has exactly one owning domain.
lint-migrations.mjs enforces it: a domain's migrations create or delete only
types it owns, and cross-domain edits need a // cross-domain: marker and must
stay expand-only. This project's own migrations, in migrations/club/, are
exempt — they are ours, never a domain's.
Drift gate
A CI check that reruns a generator and fails when its output differs from what is
committed (git diff --exit-code). There is one each for the generated tokens,
the model schema and its generated types, the composition shims and the app
codegen, which is why generated files are never edited by hand: the fix is always
to rerun the generator and commit. pnpm verify runs the composition and codegen
gates locally, and pnpm --filter @v4/model drift the model one.
Env marker
env.public("NAME") or env.server("NAME") in apps/web/club.config.ts — the
only sanctioned path from the club config to process.env. defineClubConfig()
resolves every marker when the config is parsed, so a missing required variable
fails the build rather than a page. See
Template config.
Expand/contract
The mandatory migration discipline: every model change must be backwards-compatible with currently deployed code. Add optional fields first (expand); destructive operations (contract) ship one release later, carrying a contract marker.
Flightcontrol
The host the site deploys to. Every push to develop deploys Development;
Staging deploys the release branch named in flightcontrol.json, migrating
the staging Contentful environment as part of its build. Hosting secrets live
in Flightcontrol, not in GitHub. See
Environments and secrets.
Fork (client fork)
ELB's copy of a Ghost Figma file, modified in place —
ELB – Web – Core (HIjezWNeRQSBGkxx8VqmjA) and ELB – Web – Content
(9iz5GeR9syO68cmPr1ppE1). A fork preserves the upstream node ids, so a
bridge naming the Ghost file resolves perfectly while measuring the wrong design.
check-bridges, design-status, fork-diff and design-drift exist to catch
that; the rule is .claude/rules/frontend/fork-refactor.md.
Fragment
A curated GraphQL fragment in packages/model/src/<domain>/fragments/*.graphql,
one per component surface, named {Surface}Fields — the reviewed statement "this
surface consumes exactly these fields". Prune, never select-all; its types are
generated, and page-level queries in the app are composed by spreading fragments.
Game Centre
A game's page, at /en/games/<game>: one address that is pre-match, live and
final in turn. @v4/basketball-app reads the game from the Competition Engine
and maps it onto @v4/ghost-basketball's PageGameCentre; while the game can be
live the page polls for updates, and its tabs are reached by the address's
fragment (#box-score). See
The Game Centre and the widgets.
Ghost (Ghost 2.0)
The Figma design system the inherited component library was first built from —
Ghost – Core 2.0 (67J5FrDZn5HzYzf5hJgoNA) and Ghost – Content 2.0
(RHCx7G92nx50KjNK6QshhU). Its tokens.json (Token Studio) is the default token
source, and the Ghost contract is what the tests assert. For this site the
design truth is ELB's own files: the two ELB forks above, ELB – Web –
Basketball (8Jl1miPEIZslMda8jZ8w0C, new UI for @v4/ghost-basketball) and
ELB – Web – EXT (Yqy0zItrtL4UptOVW54wVg).
Ledger (modelMigrationState)
The per-environment migration ledger — a content type with a singleton entry
recording which migrations have run in that environment, written only by the
migration runner (and by sync-apps for the installed app set). It is data, not
config; never edit it in the web app.
Motion pattern (P-*)
A named animation in the ELB motion spec — P-REVEAL, P-CASCADE, P-HOVER,
P-COUNT and the rest. Every animation in the code names its pattern in a
comment and takes its values from the motion.* tokens. See
Motion.
Override levels
The five levels at which tokens compose, highest wins: (1) theme defaults, (2)
createTheme({ global }) club overrides, (3) createTheme({ components })
per-component overrides, (4) raw CSS custom properties, (5) the per-instance
tokens prop. A tokens prop is fixed at render, so it cannot follow a :hover
or a media query.
Owner-keyed emission
How each component package emits its themes exactly once: its init.css.ts
lists the themes it owns (OWN_THEMES) and calls createPackageTheme(OWN_THEMES).
In the web app PH_THEME_EMISSION=club makes the club theme the single emitter
instead.
Page measure, gutter and rung
A rung is a breakpoint band: 360, 768 (md), 1024 (lg), 1440 (xl), 1920
(xxl). The design draws most components at only Small and X-Large, so the
tablet (768–1023) and laptop (1024–1439) bands follow rules rather than frames.
The page measure (g.semantic.size.pageMeasure) is the width a band's ONE
outermost container takes — min(100% − 2 × gutter, ContainerWidth.XL) — and the
gutter (pageGutter.{s,md,lg,xl}) steps per rung. See
Responsive rungs.
Platform and Client mode
The two modes of the ticket tooling. This repo is in Client mode: the ELBWEB
Jira board, the Contentful space (12gd2cvfgb6b) and ELB's Figma files are
authoritative, and the tools read from them — a ticket on the board is never
regenerated from the code. Platform mode is the template's, where the code is
the source of truth; its tooling is not in this repo. See
@v4/tickets.
Precompile
The step in each component package's build (packages/ghost-core/scripts/precompile-styles.mjs)
that turns its vanilla-extract stylesheets into plain .css files and
class-name modules, so the app does not compile ~250 stylesheets itself. Only
init.css.ts and what it imports stay raw, because that is where the themes
register for the club theme to emit. A consequence: a stylesheet or .theme.ts
must never be reachable from a package barrel, or the build fails.
Protected environment
A Contentful environment the migration runner refuses to touch outside CI —
master, production, staging, staging-release. --env is always explicit,
and the guard is never weakened.
Release branch
A release/X.Y.Z branch cut from develop when a release is ready for Staging.
Every push to it is a build, tagged vX.Y.Z-rc.N (a release candidate, or
rc) and deployed to Staging; fixes reach it as pull requests, and
release.yml finally fast-forwards main to it and tags vX.Y.Z.
Release train
The path a change takes to release: develop integrates and deploys
Development, a release branch builds rc tags for Staging, and main carries the
released tags, with Jira tracking each build. See
Releasing.
Route factory
A per-route factory function exported by a domain's route package
(createNewsArticleRoute, the video landing/detail/collection/playlist
factories, the basketball data-area pages) — the page body the app mounts through
a generated shim.
Route package
A package that owns one domain's routes and declares them in its routes.json:
@v4/content-app, @v4/video-app and @v4/basketball-app. Each owns its page
queries and their codegen; the app only mounts them.
Shim
A generated file the app mounts to wire a route package's factory into
apps/web's routing, under src/app/[locale]/… — produced by pnpm compose
(the compose-club generator) and CI drift-gated, so a hand-edit or a stale shim
reddens the build.
Slot
A component prop that takes React content (ReactNode) rather than data — a
crest, a sponsor logo, an image, children. A slot is legitimate only for
consumer-owned artwork or genuinely consumer content; where a slot stands in for
a real component, the component is composed instead (see Composition fidelity).
A slot does not inherit: a colour way pinned on the parent reaches its own
elements but nothing passed into the slot, so the composer hands each block the
way explicitly.
Story suite
The vitest browser-mode run of every Storybook story (pnpm --filter @v4/storybook test):
each story renders, its play function runs, and axe accessibility violations fail
it. Unlike the Playwright e2e, which pins the Ghost theme, the story suite renders
the club theme, so a change to ELB's palette moves its colour and contrast results;
and it renders motion at its final state.
Template (Publishing Hub v4)
Where this repo came from. It was generated from the Publishing Hub v4 template
(the commit is recorded in .claude/manifest.json as templateCommit), so most
of its packages — every @v4/* name — the Ghost design system and the tooling
started there. What matters day to day is who owns what. The inherited paths —
the design-system packages, content-app, the media, image and video packages,
the model's core and content slices and its scripts, Storybook, the e2e
suite, scripts/, .claude/ and CLAUDE.md — arrive as pnpm sync-upstream
pull requests, which change them under you. This project's own code is never
offered by a sync: apps/web/club.config.ts, the model's migrations/club/,
@v4/tokens-euro-league, the basketball and integration packages, and these
docs. See Take template updates.
Template config
@v4/template-config and its defineClubConfig() — the typed site config
(apps/web/club.config.ts) that declares env markers, feature modules, bought
domains, sections, and locale/theme/feature cross-gates, and drives
pnpm compose.
Token contract
The typed token layer: paths like
colourSets.base1.button.primary… emitted as --ph__* CSS variables, built by
buildGlobalTheme and registered via registerComponentTheme. The generated
packages/tokens/src/__generated__/tokens.ts is the source of truth for the
Ghost defaults; the club's values are generated into @v4/tokens-euro-league.
Token Studio
The Figma plugin that authors tokens into tokens.json, which @v4/token-parser
slices per component namespace into generated contracts. It stores bindings in a
node's sharedPluginData; brand tints/shades are authored as a reference plus a
modify transform, not as literal hex. For this site, Token Studio pushes the
club's tokens.json straight to the repo; run generate after each push.
Transform
The step in the model spine that maps a Contentful-shaped GraphQL result into the
props a component consumes — the layer between curated fragments and the app.
Each domain exports its own: @v4/model/core, /content, /video (the category
card and the landing settings — most video data comes from the platform API via
@v4/media) and /basketball (the Contentful side of the Competition Engine
merge).