The system for anything a member or a guest actually uses: the member portal, account pages, and public web. It extends v2 rather than replacing it, loading the same tokens and the same weave, then adding the components a screen needs. Every rule here came out of a page that shipped.
Print, flyers, PDFs, and anything handed to a client stay on v2. Internal data dashboards stay on the dashboard system at the root of this domain. v3 is the third door, and it only opens for screens a member or a guest sees.
Each one exists because breaking it produced a page that got rejected. They are ordered by how often that happened.
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.
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.
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.
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. On navy, Lime is correct for eyebrows, accents, and the avatar chip.
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.
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.
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.
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.
Small on purpose. A screen system that ships forty components is a system nobody holds in their head.
Each page shows the component, the rule it enforces, and the version of it that gets rejected.
One weave down the whole page
Open 02Mark, current section, initials
Open 03Cells share one hairline
Open 04Nineteen rows, one box
Open 05Colour in marks, lists that collapse
Open 06One sheet, every surface opens it
OpenLink 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/pattern.css"> <link rel="stylesheet" href="https://design.greenvilletriumph.club/screens/components.css"> <script src="https://design.greenvilletriumph.club/screens/field.js"></script>
--field-y per section so the weave is one field. No dependencies. Re-runs on load, resize, font swap, and reflow.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.