The Triumph design system. Two tracks, one brand: dark data-dense dashboards for staff, and the brand guide for everything a fan or sponsor sees. Link two files, use the tokens, and it comes out right.
Fonts (Fabiola + Inter), the full palette, the type scale, spacing, and every component arrive through these two stylesheets. Then clone starter.html and edit content, not layout.
Internal staff dashboards and ops tools. Data-dense, dark, screen-tuned. Everything on this page.
Use for: report.greenvilletriumph.club, scan, numbers, projections, the intern boards.
The actual Brandpad brand. Flyers, digital ads, printed pieces, and any page a customer or sponsor will see.
Use for: matchday flyers, IG posts, sponsor one-sheeters, landing pages.
The split is audience, not medium. A web page does not automatically follow the dashboard system. If a fan or a sponsor will see it, it is built on the brand, on a light canvas, even though it is a web page. Sections 08 Pattern, 09 Craft, 07 Logos, and 10 Voice are shared law and govern both.
<!-- dark-mode lock (meta + first style block) --> <meta name="color-scheme" content="only dark"> <meta name="supported-color-schemes" content="dark"> <meta name="darkreader-lock"> <meta name="theme-color" content="#0b1017"> <style>:root,html,body{background:#0b1017!important;color:#f2fafa!important;color-scheme:only dark;}</style> <!-- tokens first, then components; all three fonts ride along --> <link rel="stylesheet" href="https://design.greenvilletriumph.club/tokens.css"> <link rel="stylesheet" href="https://design.greenvilletriumph.club/components.css">
var(--token-name) in CSS. Never hardcode a hex, rgba, or oklch value..h-display, once per page). Every other heading is Inter.--text-label, nothing renders below --text-xs, and font-size only ever takes a --text-* token.The whole system is built from these. Every surface, border, and accent is one of them or a derived tint. Click a swatch to copy its hex.
Depth without leaving the palette. Deep Night lifted toward Upstate Fog gives a calm slate ladder, so cards read as layers instead of a flat wall of saturated navy. Greenville Blue is reserved for one deep emphasis band.
On light surfaces, body text is Greenville Blue or Deep Night, never Triumph Green.
Brand track: promo accentsOn flyers, ads, and customer-facing pages the hero accent is the green gradient, not a flat fill. It runs light to dark, and it never flips. Accents are for highlights only, never a large background, and never both accents in one layout.
Greenville Liberty runs Greenville Blue plus Liberty Yellow and Liberty Sky. Those two are the sister club's colours and do not enter a Triumph layout. On dashboards, yellow of any kind means one thing only: a genuine warning state.
Brandpad assigns Fabiola Capitals to its headings. On a data-dense screen that fights the data, and any weight above 400 synthetic-bolds the single-weight file into mush. So Fabiola is restricted to one hero (.h-display, rendered at the real Brandpad spec) and every working heading is Inter at a genuine heavy cut weight.
--text-label · 600 · --track-label · muted. Eyebrows, section numbers, KPI and stat labels, table headers, nav links: one spec, no exceptions.--text-xs is the absolute floor. Footnotes, deltas, badges, dense cells sit there; nothing sits below it..subsection-label at --text-lg minimum..eyebrow-accent.--text-* tokens.0.65rem clone of .kpi-label is the canonical sin.--track-* tokens are the only values.@font-face (ascent, descent, line gap) is baked into tokens.css, so Fabiola already sits correctly.margin-left: -(lsb)em keyed to that line's first glyph. The measured per-glyph table is in tokens.css.font-synthesis: none.translateY. If it looks wrong, fix it in tokens.css, not on the page.Three container widths and drop-in grids so a dashboard uses the monitor instead of stranding a skinny column in the middle. A single fluid spacing scale keeps the vertical rhythm consistent everywhere.
.split, .split-wide, .prose-cols, or a grid. They collapse on their own when the columns would get too narrow to read..prose-cols across the canvas instead of a 64ch ribbon with an empty half beside it.<br> to fix a bad wrap. Fix the width.This is enforced, not just written down. Above 1000px a .section-header that carries a description stops being a stack and becomes two columns automatically: the number and title on the left, the supporting copy on the right, sharing a baseline. Every section header on this page and on every dashboard that links components.css reclaims that dead half with no page edits. Headings also carry text-wrap: balance and body copy carries text-wrap: pretty by default, so a heading that has to break splits into even lines rather than dumping one orphan word per line. If a phrase must never break at all, give it .phrase. If balance still leaves you a one-word column, the container is too narrow: widen it.
Reading width. Prose, single-column docs.
Dashboard width. KPI rows, charts, multi-column data.
Full bleed with gutters. Dense tables, seat maps.
Responsive by default, collapse to one column on narrow screens.
Sharp edges, lift on hover (never scale), one accent per layout. Everything below is a class in components.css.
.toast-stack.Tabular mono figures, accent-keyed KPIs, dense tables with totals, and Chart.js wired to the palette through the tok() helper.
For pages that ARE the event (matchday guides, one-pagers): the Main lockup, a quiet Inter kicker, the fixture in poster Fabiola, and an info rail where Light Green marks the one live fact. The scale boundary is hard: poster Fabiola belongs to title-card pages only; data dashboards keep .h-display at the calibrated size.
Match 9 of 17 · 2026 USL League One
No bespoke CSS. Every block above, composed the way a live Triumph dashboard composes them. This is the bar a page hits on the first try.
Brand-correct, screen-tuned, every time.
Pick the lockup for the space and the variant for the background. Never recolor, stretch, rotate, or add effects.











The Pattern element above is the weave. How to place it is 08 Pattern, and getting it wrong is the most common brand error in the whole system.
Lockup lawtriumph-main-on-dark.png).The interlocking-T weave is the brand's one texture, and it is the element most often used wrong. It is a graphic element, not a texture, not wallpaper, not a watermark. It reads as woven fabric only when the motif is big, the field runs edge to edge, and it carries the house −8° tilt. The tilt is wanted; square reads wrong. What goes wrong is scale and inconsistency, not angle. This section is law for both systems.
<!-- ONE field: cover, no-repeat, tilted -8deg --> <section class="weave weave--navy"> <!-- content is a z-index:1 sibling, handled by the class --> </section> /* the whole class, for reference */ .weave::before { position: absolute; /* vw, never % - a % inset starves on short bands */ inset: calc(-1 * 12vw); background-position: center; background-size: cover; background-repeat: no-repeat; transform: rotate(-8deg); /* the house tilt */ } .weave--flat::before { inset: 0; transform: none; } /* square, opt-in */ .weave--light::before { background-image: url("triumph-pattern-field.png"); opacity: .07; } .weave--navy::before { background-image: url("triumph-pattern-field-white.png"); opacity: .09; }
cover holds the motif scale identical across every band, as long as the band is shorter than about 1.19× the viewport width. Every normal section is.Every band on a page gets the same asset, same method, same scale, same rotation. The only two things allowed to differ between surfaces are the colourway and the opacity. Keep a page to two surfaces, one light and one dark. A third near-white or Morning Mist band reads as drift. Separate two adjacent same-colour sections with a hairline rule, not with a new background colour. Float dense content, tables especially, on a solid panel above the field so the weave never fights the text.
cover and no-repeat, every time.background-size to make a fine texture. Size is not a knob.body or any global background.Legacy note: .brand-field and .brand-zone repeat-tile the weave. That, not their tilt, is why they are deprecated. They stay only so already-shipped dashboards keep rendering. Do not use them in new work, and migrate a page to .weave whenever you touch it. The hero of this page runs on .weave.
Tokens keep a page on brand. They do not make it good. These are the composition rules, learned the hard way on real pieces, and they apply to a flyer and a dashboard alike.
On a flyer, ad, or customer-facing page: no stadium shot, no crowd, no action photo behind a headline, unless that piece specifically called for one. Default to graphic and typography only: the weave, the logos, colour, and type. If a layout feels empty, that is a composition problem, and a photo is not the fix.
Fill it with weave, scale, or colour blocking instead. Dashboards may use a photo break as a section divider.
The single most common failure is content huddled in the middle with a lake of dead space around it. Composition has to reach into every region. Anchor things to top and bottom, push type to real margins, let a hero word or the weave run full bleed.
Test: if it survives being cropped to its middle third, you did not use the space.
It is BEER GARDEN on one line, not BEER over GARDEN. A column of single stacked words looks broken every time. If a phrase will not fit, scale the type down or widen the block first. Only then break it, and break into balanced multi-word lines at a natural boundary.
text-wrap: balance on headings. Give hero text room to be wide, not tall.
A symmetric stack of centred text on centred blocks reads amateur. Pick an axis and commit to it, then break it deliberately once. Left-aligned type with one full-bleed element carries more authority than everything centred.
Applies to section headers and KPI rows too, not just flyers.
On a printed or social piece: no buttons, no pulsing dots, no live countdowns, no boxed dashboard chrome. Those are interface, and they read as clip art on a graphic. The inverse holds too, a dashboard does not need a hero headline the size of a poster.
Know which artefact you are making before you start.
Before a figure goes on anything a client sees, read it the way a sceptic would. A stat that flatters on first read and embarrasses on second is worse than no stat. Check that the number, the label, and the timeframe actually agree.
Cross-check totals against the source. Never invent a figure.
Broken and cramped. One word per line is never the answer.
Phrase intact on one line, scaled to fit its container.
Soccer connects people across cultures, languages, and backgrounds, creating community, opportunity, and a sense of home.
var(--color-accent).translateY and a border tint.cover, no repeat.<br> was used to patch a wrap.--color-band-navy band.--surface-frost / --surface-frost-deep.cover and no-repeat, never tiled.The most-used tokens as raw values. The full set lives in tokens.css.
/* canvas + elevation - neutral navy-graphite ladder */ --color-bg: #0b1017; --color-surface-1: #121821; --color-surface-2: #1a212c; --color-surface-3: #232c39; --color-surface-4: #2e3846; /* text */ --color-text: #f2fafa; --color-text-soft: #d2e6e5; /* accents (one per layout) */ --color-accent: #61a631; --color-blue: #69b3e7; --color-pop: #97d700; /* type + layout */ --font-display: Fabiola Capitals; --font-sans: Inter; --font-mono: JetBrains Mono; --text-label: clamp(0.905rem, 0.86rem + 0.22vw, 1.00rem); --track-label: 0.08em; --track-kicker: 0.12em; --track-subhead: 0.05em; --content-wide: 1440px; --space-section: clamp(2.25rem, 1.8rem + 1.9vw, 3.75rem); /* left nav rail */ --rail-w: 60px; --rail-w-open: 248px; --rail-hover-delay: 0.12s; /* Chart.js: canvas cannot read vars, use the helper. Read from document.body, not documentElement - the CVD remap lives on body[data-cvd], and body inherits every :root token anyway */ const tok = n => getComputedStyle(document.body).getPropertyValue(n).trim();