Screens
The system for anything a member or a guest actually uses: the member portal, account pages, and public web. It loads the shared core tokens and the same weave the printed work uses, then adds the components a screen needs. Every rule here came out of a page that shipped.
It does not replace the other three.
Flyers, ads, and single printed sheets stay on Print. Multi-page proposals, reports, and guides go to Documents. Internal data dashboards stay on the dashboard system at the root of this domain. Screens is the door that opens only for something a member or a guest actually uses.
/
A member or a guest using a screenScreens/screens/
A flyer, an ad, a poster, or one printed sheet, graphic-ledPrint/print/
A multi-page proposal, report, or guide read as a PDFDocuments/documents/
More than one page, a running head, or page numbers means Documents. One sheet built from type, colour blocks, and the weave means Print.
Eleven rules.
Each one exists because breaking it produced a page that got rejected. They are ordered by how often that happened.
Two grounds. Never a third.
Navy and fog. That is the whole palette of section backgrounds. A panel on fog is white, a panel on navy is navy. --mist is a chip colour and an avatar colour; the moment it fills a band the width of the content column it reads as a third ground and the page looks broken.
Sections alternate in content blocks, not one by one. Group the sections that tell one story onto one ground, and flip at the story break.
One continuous field.
The weave runs the length of the page as a single field. It is not a texture restarted per section. components.css handles it with --field-y and field.js writes the value.
Paint the weave yourself and you get a hard reset at every boundary: the tiles do not meet, and the seam reads as an accidental header bar halfway down the page. The ink flips at a seam. The grid never does.
Text never sits on the weave. The field shows behind header content only: the app bar, the hero, and a band head. Numbers, legends, lists, and prose sit on a white panel or flat ground, and the footer is flat navy under its lime rule.
No box in a box in a box.
A filled or bordered panel may hold text and controls. It may not hold another filled panel that holds another filled panel.
A long list is one surface with dividing rules, not one card per row. A grid of cells shares one hairline: gap: 1px over a hairline ground, with the cells carrying no borders of their own, because two rules in the same place is a smudge. If a row needs a status, give it a bar or a mark, not a tinted inner box.
Colour lives in marks, not in type.
Triumph Green and Lime never set type on a light surface. Green lands near 2.8:1 on fog and Lime near 1.7:1, so both fail at any size and Lime is close to invisible.
The status word stays navy. The colour goes in the mark under it and the swatch in the legend above the list. That is what .status-word, .status-marks, and .legend are for.
Lime and Green are both accents here. Lime is the accent on navy (eyebrows, hovers, link underlines, focus, the avatar chip, the app-bar rule); Green is the accent on fog and on white paper (rules, buttons, hovers). The dashboards accent law does not apply to this system.
Less on the page, more in the sheet.
A surface shows a heading and the one value somebody came for, then Details. Every other fact, the paragraph, and the links live in the shared detail sheet.
There is one sheet per page, and tiles, ledger rows, and cards all open it. That is what makes the rule affordable instead of hostile.
Collections collapse. They never enumerate.
Anything that renders per item has to degrade before it runs off the edge. Names in prose list up to three, then switch to a count: Seat 10 and 11, Seat 10, 11, and 12, 10 seats. Repeated marks run one per item up to six, then one per distinct outcome. Any wrapper that renders N children gets flex-wrap and a width cap.
Write the test. Assert 1, 2, 3, 6, and 10. The worked example in the member portal caught a missing serial comma inside its own fix.
Copy.
Oxford comma, always. Zero em-dashes; use commas, periods, semicolons, and parentheses. Literal labels: No scan, not "Didn't make it".
Say which of two things happened. A seat leaves a pass when a transfer is sent, not when it is claimed, so the row says which one it was.
Motion is tokenized.
Never hardcode a duration or a curve. --duration-fast for hovers, --duration for the sheet, the app bar, and the ledger fold, --duration-slow for the hero build, and --ease-glide for every structural move.
.reveal is the one entrance; do not invent a second. The app bar hides on scroll down only at 720px and under, because on desktop the pointer has no other way back to it. Reduced motion is mandatory and the kill lives in tokens.css.
Type sizes come from tokens. Subheaders are headings.
Every font-size is a --text-* token. Raw px and raw rem are banned in a stylesheet, in a style attribute, and inside a font: shorthand. --text-xs is the floor, not the label size: every uppercase label sits at --text-label with --track-label.
A subheader is a heading. Anything that heads a content group renders at --text-lg or larger, in sentence case, tracked at or under .06em. A 13px tracked mono-caps line above a group is the most-rejected type failure in the system.
Fabiola is the hero, and nothing else.
--font-display is Fabiola Capitals at 400, once per page, on the h1. Never on a subhead, never on body copy, and never on a readout: a tile numeral, a sheet heading, and a status value are readouts, so they are Inter 900.
Never in chrome either. Fabiola's caps sit low in the em box, so a flex-centred nav brand, button, badge, or avatar renders visibly off-centre. .hero-lockup h1 is the only consumer in this layer, and that holds on specimens, previews, and templates too.
The tells.
No tabs; use banded sections and let the page scroll. No card grids and no card stacks; a list is one surface with dividing rules. No badge that restates what the reader can already see. No blurple and no off-palette tints: the palette is seven colours, and a pale fill is --mist.
Literal labels, never a slogan and never an opening negative. Every page locks dark-mode inversion keyed light, and every page links the four favicon files from the deploy root.
Accessibility floors that are also design floors.
Tap targets are 44px, which a button tile gets for free and a bare link does not. A sticky bar that hides on scroll down comes back on scroll up. Scroll-lock a modal with position: fixed on body, never overflow: hidden, because iOS ignores the latter, and restore the offset on close. Anything that opens the sheet from an in-page anchor releases the lock first, or the jump lands behind a frozen body.
Fourteen components.
Small on purpose. A screen system that ships forty components is a system nobody holds in their head.
- .field .field-navy .field-fog
- Section ground plus the continuous weave. The only two grounds there are.
- .on-solid
- A fill under running text on a navy field, so long copy never sits directly on pattern.
- .appbar
- Sticky bar: mark, the section you are actually in, initials, and a lime rule. On a phone that is the whole bar.
- .grid-divided .on-navy
- Cells sharing one hairline. Works identically on both grounds.
- .tile
- Heading, one value, Details. The whole surface is the button.
- .ledger
- A long dated list on one surface, with month headings as divider rows inside it.
- .status-word .status-marks .legend
- Rule 3 in practice: navy word, coloured marks, swatches above the list.
- .sheet-scrim .sheet .facts
- The one detail sheet, with facts as label and value rather than a paragraph in a box. Opens and closes on a glide.
- .btn .btn-lime .btn-navy .btn-ol-navy .btn-ol-light
- One button family: 44px targets, a 1px lift, a focus ring that follows the ground.
- .kicker .featured
- Mono eyebrow with a square marker riding currentColor. One featured per page.
- .bignum
- Numeric lockup: mono value with hanging currency or unit, label under it.
- .hero-lockup
- The one Fabiola moment, sized by character count so the title owns the band.
- .reveal
- The one entrance: scroll-driven pop-in where supported, static everywhere else.
- details.ledger-month
- A collapsible month inside the ledger. The summary is the header row, and the fold animates where the browser can.
Previews.
Each page shows the component, the rule it enforces, and the version of it that gets rejected.
Continuous field
One weave down the whole page
Open 02App bar
Mark, current section, initials
Open 03Divided grid and tiles
Cells share one hairline
Open 04Ledger
A long dated list, one box
Open 05Status and scaling
Colour in marks, lists that collapse
Open 06Detail sheet
One sheet, every surface opens it
OpenFour files, in this order, and the contract.
Link them from this domain, or copy the folder into a project that has to self host. Self hosting is the rule for anything rendered behind office TLS inspection or exported to PDF.
<link rel="stylesheet" href="https://design.greenvilletriumph.club/screens/tokens.css"> <link rel="stylesheet" href="https://design.greenvilletriumph.club/screens/components.css"> <script defer src="https://design.greenvilletriumph.club/screens/field.js"></script> <script defer src="https://design.greenvilletriumph.club/screens/screen.js"></script>
--field-y per section so the weave is one field. No dependencies. Re-runs on load, resize, font swap, and reflow.data-sheet opens, data-sheet-close closes. Safe on pages with no app bar and no sheet.Fonts load through @font-face inside tokens.css, so nothing needs a Google Fonts link. The live example is the season ticket member portal, which is where all of this came from.