/* Generated by script/build_tokens.rb from design/tokens.json. Do not edit.
 *
 * Regenerate:  ruby script/build_tokens.rb
 * Verify:      ruby script/build_tokens.rb --check
 */

:root {
  /* Brand green. The baseline also contained #1aaf11 in four places — a 2-point difference in the blue channel, visually identical, treated here as a typo and unified. */
  --gp-color-green: #1aaf13;
  /* Was --dk-green. Form control text, progress borders. */
  --gp-color-green-dark: #1a8754;
  /* Border paired with green on the checkpoint buttons. */
  --gp-color-green-border: #198510;
  --gp-color-white: #ffffff;
  --gp-color-black: #000000;
  /* Body background. */
  --gp-color-page: #fefefe;
  /* Chud checkpoint banner. */
  --gp-color-alert-yellow: #ffff00;
  /* Bootstrap's .alert-secondary background, which is what a sale notification has always been painted. Kept as the opaque fill for floating surfaces that must not let anything through — see surface/plate. */
  --gp-color-plate: #e2e3e5;
  /* Bootstrap's --bs-danger, and the border an invalid form control already gets today: .form-control.is-invalid, applied by bootstrap_form whenever the record has an error on that attribute. */
  --gp-color-red: #dc3545;
  /* The text colour of .alert-danger in the vendored Bootstrap — the error summary shared/_errors renders. Also ships today; used here for error text because #dc3545 is 4.49:1 on the page background, a hair under AA. */
  --gp-color-red-dark: #842029;
  /* Bootstrap's .bg-light. Recorded because it is what the chud status card actually renders: baseline:285 paints the card with surface/glass, and .bg-light on the same element carries !important and wins. The card reads as glass in the CSS and ships opaque. */
  --gp-color-bootstrap-light: #f8f9fa;
  /* Bootstrap's .border. The stat chips carry .border, whose !important beats the white hairline at baseline:176 — so the chip border is this grey, not border/glass. 1.48:1 on the page background, which fails WCAG 1.4.11; kept because it is what ships, not because it is right. */
  --gp-color-bootstrap-border: #dee2e6;
  /* Bootstrap's .text-muted. The stat chip label carries .text-muted, whose !important beats the #6c6c6c at baseline:186 — so grey-700 is NOT the colour that label renders, despite its description. Importance beats specificity. */
  --gp-color-bootstrap-muted: #6c757d;
  /* Bootstrap's --bs-body-color, inherited by <body>. Nothing in Setting[custom_css] sets a text colour on the attendee body, so this — not text/body's black — is what every ordinary paragraph and heading on the attendee surface actually paints. Measured on /vouchers at 390px: the h1 and the blurb both compute rgb(33, 37, 41). */
  --gp-color-bootstrap-body: #212529;
  /* Bootstrap's .form-control border. Distinct from bootstrap-border (#dee2e6, the .border utility) and it is the one a text input actually gets. No page block in the row restyles the border on .form-control, so all four form pages ship this grey hairline — measured rgb(206, 212, 218) on /vouchers. The Figma Field component draws border/glass instead, which is the value .gp-field__control was reconciled AWAY from; that component is wrong and is tracked separately. */
  --gp-color-bootstrap-input-border: #ced4da;
  --gp-color-grey-100: #ededed;
  /* Light stop of the stat chip's gradient (baseline:166-170); grey-200 is the dark stop. */
  --gp-color-grey-150: #e6e6e6;
  --gp-color-grey-200: #dcdcdc;
  --gp-color-grey-300: #d0d0d0;
  --gp-color-grey-400: #cbcbcb;
  --gp-color-grey-600: #6e6e6e;
  /* Muted label text. */
  --gp-color-grey-700: #6c6c6c;
  /* Label text in #shop (baseline:894). NOT what the three form pages ship — their labels inherit Bootstrap's body colour, #212529 — so .gp-field__label deliberately sets no colour. */
  --gp-color-grey-800: #3c3c3c;
  --gp-color-glass-fill: rgba(255, 255, 255, 0.65);
  --gp-color-glass-fill-strong: rgba(255, 255, 255, 0.78);
  --gp-color-glass-fill-faint: rgba(255, 255, 255, 0.15);
  /* The login form controls (baseline:730 — pre-cut snapshot; the #login block was removed from the row 2026-09-20, so that line number has no successor and now addresses an unrelated rule), and the only glass fill in the row that is not one of the three above. Thinner than glass-fill on purpose: those fields sit directly on the sky photograph and the type inside them is green-dark, so the plate has to read as glass rather than as a panel. */
  --gp-color-glass-fill-soft: rgba(255, 255, 255, 0.4);
  --gp-color-glass-border: rgba(255, 255, 255, 0.75);
  --gp-color-glass-border-bright: rgba(255, 255, 255, 0.8);
  --gp-color-glass-sheen: rgba(255, 255, 255, 0.1);
  /* Checkpoint UI polish plan (2026-09-17), Figma 193:1542/193:1913/193:1535/193:2013. Stat pill values, level name, progress fill, and the Buy More Chuds button fill all read this one value from get_design_context. */
  --gp-color-blue: #1c96e5;
  /* What the checkpoint paints while GrossProphets-StoryBKG-Clouds-9x16.jpg is still arriving: the start of the gradient under the image, which the image covers the moment it lands. Deliberately NOT checkpoint-sky-top (#283d81, the photograph's own top edge). Corey, 2026-09-21: "before clouds loaded from server bkg was solid dark blue, it was hard to read. Can it be something closer to white." The card is glass -- it takes its legibility from whatever is behind it -- and dark ink over #283d81 measures 1.63:1, so the whole card read as a smudge until the JPEG arrived. This is the LIGHTEST blue that still holds the white wordmark above it at WCAG AA: 4.79:1 against white, against 10.12:1 before, while dark ink rises from 1.63 to 3.44 before the glass lightens it further. Going lighter than this means giving the wordmark a darker colour for the duration, which is a second state to maintain for a second and a half of loading. checkpoint-sky-top stays what it is, because .takeover__tint--top feeds it to iOS Safari as the toolbar colour and that has to match the PHOTOGRAPH, not the placeholder. */
  --gp-color-checkpoint-sky-preload: #5470b8;
  /* The top edge of GrossProphets-StoryBKG-Clouds-9x16.png, sampled (sips down to 9x16, then read). Not a Figma value: the fallback the checkpoint takeover and /checkpoint paint while that 8MB image is still arriving, so the page underneath does not show through a takeover that is meant to be opaque, and the white heading has something dark behind it. Pair with sky-cloud. */
  --gp-color-checkpoint-sky-top: #283d81;
  /* The cloud bank in the lower half of the same image, sampled the same way. See checkpoint-sky-top. */
  --gp-color-sky-cloud: #e8eceb;
  /* The bottom edge of the checkpoint's clouds (GrossProphets-StoryBKG-Clouds-9x16.jpg, the last 4 of its 1920 rows averaged in a canvas, round 5). What iOS 26 Safari is given to tint its bottom toolbar with while the takeover is open — it samples background-color, never the image (application.css, .takeover__tint). */
  --gp-color-sky-bottom: #dde6e9;
  /* The quiz card's hairline, added 2026-09-21 when Corey moved the quiz off its own black scrim and onto the shared sky. Measured in the iOS 26.3 Simulator on that change: the card is opaque white and its TEXT was never at risk (15.4:1 for the question, 4.7:1 for the muted subtitle, both unchanged), but the card's own edge fell from a black scrim to 1.15:1 against the cloud bank - a white card on white cloud, with only the 40% box-shadow holding the silhouette, and the bottom corners dissolved. 50% black, because the border composites over the card's own background (background-clip is border-box, so BOTH sides of the line are struck against white) and 45% rendered as #8c8c8c - 3.36:1 against the card but 2.92:1 against the cloud, sampled off the Simulator's own pixels rather than computed. 50% renders #808080: 3.54:1 and 3.43:1, clearing 3:1 (WCAG 1.4.11) on both sides. Not white-35 or any of the glass rims - those are made to read against the sky's blue, and this one has to read against its white. */
  --gp-color-card-edge-on-sky: rgba(0, 0, 0, 0.5);
  /* PATTERN B, the panel (round 19). The colour overlay on a panel's veil, painted over its blur: color/checkpoint-sky-top at 10%, the value Corey tuned on his phone 2026-09-20 in public/glass-test-transitions.html revision 8 against both backgrounds the veil has to work over - the white page and the checkpoint's own sky. Its slider offered 0 and he did not take it. This is not round 13's 55% black coming back: that one was removed because the drawer's glass read grey through it, and a tenth of the sky's own blue does not do that. */
  --gp-color-panel-scrim: rgba(40, 61, 129, 0.1);
  /* Product card commission-box label ("Commission Earned By"), Figma 143:320. Fills the gap in the grey scale between grey-400 (#cbcbcb, 203) and grey-600 (#6e6e6e, 110) — not a near-miss of either. */
  --gp-color-grey-500: #858585;
  /* Bootstrap's --bs-success. Product card price text, Figma 143:320. NOT the same value as green-dark (#1a8754) — one point off in the green channel, unlike the green/#1aaf11 typo this file already unified, this is Bootstrap's own colour and stays a separate token. */
  --gp-color-bootstrap-success: #198754;
  /* Inset highlight, checkpoint status/person-selector card (Figma 193:1542) and the Buy More Chuds button. Distinct from glass-sheen (0.1) and white-14/white-35 below — the checkpoint UI polish plan's shadow layers use several close-but-different white alphas and each is its own measured value. */
  --gp-color-white-13: rgba(255, 255, 255, 0.13);
  /* Inset highlight, first layer, checkpoint amount stepper and Send buttons (Figma 193:1535). */
  --gp-color-white-14: rgba(255, 255, 255, 0.14);
  /* Product card border, Figma 143:320. */
  --gp-color-white-30: rgba(255, 255, 255, 0.3);
  /* Checkpoint card borders (status card, person-selector card, amount stepper/Send buttons) and the second inset layer on the stepper buttons' shadow. The most reused new alpha in the checkpoint UI polish plan. */
  --gp-color-white-35: rgba(255, 255, 255, 0.35);
  /* Amount stepper fill layer (Figma 193:1535) and the product card's first inset shadow layer (Figma 143:320) — same value, two components. */
  --gp-color-white-50: rgba(255, 255, 255, 0.5);
  /* Person-selector card fill layer, active states (idle/selected/sent), Figma 193:1913 etc. */
  --gp-color-white-76: rgba(255, 255, 255, 0.76);
  /* Product card commission box background, Figma 143:320. */
  --gp-color-white-88: rgba(255, 255, 255, 0.88);
  /* Person-selector card fill layer, inactive state, Figma 193:1182. */
  --gp-color-black-7: rgba(0, 0, 0, 0.07);
  /* Product card outer shadow colour, Figma 143:320. */
  --gp-color-black-10: rgba(0, 0, 0, 0.1);
  /* Outer shadow colour shared by every checkpoint glass surface — the status/person-selector card, the amount stepper and Send buttons, and the Buy More Chuds button. Same colour, different offsets per component; see the shadow tokens below. */
  --gp-color-black-15: rgba(0, 0, 0, 0.15);
  /* grey-700 (#6c6c6c) at 40% alpha. Disabled Send button fill, Figma 193:1535. */
  --gp-color-grey-700-40: rgba(108, 108, 108, 0.4);
  /* Stat pill fill layer, checkpoint status card, Figma 193:1542. */
  --gp-color-pill-fill: rgba(192, 192, 192, 0.3);
  /* Level-progress track background, checkpoint status card, Figma 193:1542. Distinct from surface/track (color.white), which is the broadcast board's meter track. */
  --gp-color-progress-track: rgba(192, 192, 192, 0.15);
  /* Glass v2: the send card's tint, under blur 10 with vibrancy. Round 12 (Corey: over the white clouds the card read solid): from white at .5 to a faint cool tint at .22, so the blurred clouds show through and the frost reads against white by its hue; the rim, edge and depth carry the separation instead of opacity. Round 14 (item 8): the status card split off its own glass-tint-status below — this token stays the send card's, unchanged. */
  --gp-color-glass-tint-card: rgba(226, 236, 252, 0.22);
  /* Glass v2 (round 14, item 8; Corey: the status card is "not white enough / a little too much blue showing through"): the status bar's own tint, whiter than the send card's glass-tint-card (.22). Tried .4 first (script/measure_live.js screenshot, round 14): still read pale blue — the status card sits against the sky's darkest band, near the top, unlike the send card lower down over lighter cloud, so it needs more opacity than even the sheet's .55 to read white there. Landed on .62 after a second screenshot. Judgment call, not a measured value. */
  --gp-color-glass-tint-status: rgba(226, 236, 252, 0.62);
  /* Glass v2 (round 12): the −/+ fill, under their own blur 10. Was color/white-35; the same cool tint as glass-tint-card, a little stronger for a 48px control. */
  --gp-color-glass-tint-control: rgba(226, 236, 252, 0.3);
  /* Glass v2 (round 11): the Buy More Chuds drawer sheet's tint. Recommended preset. */
  --gp-color-glass-tint-sheet: rgba(255, 255, 255, 0.55);
  /* Glass v2 (round 11): the tint card and sheet fall back to where there is no backdrop-filter, or the reader asked for reduced transparency — opaque enough to read over the clouds with nothing blurred. */
  --gp-color-glass-tint-solid: rgba(255, 255, 255, 0.92);
  /* Glass v2: the lift on the TRUE edge frost. Preset white .12; round 11 white .08 (Corey: less frost); round 12 the cool tint of glass-tint-card, so the frosted edge reads over white clouds. */
  --gp-color-glass-frost-tint: rgba(226, 236, 252, 0.1);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The share button's disc fill. Between white-35 and white-50 and not either of them: the disc has to read as a control against the card's own tint, not against the sky. */
  --gp-color-white-42: rgba(255, 255, 255, 0.42);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The share button's disc border. Distinct from border/glass-bright (.8) and glass-border (.75) — a 30px disc sitting ON a glass card needs a quieter edge than the card's own. */
  --gp-color-white-70: rgba(255, 255, 255, 0.7);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The status card's own text ink — the attendee's name and the three big values. Darker and cooler than text/body's pure black would be warm; measured on the prototype, not inherited from the page. */
  --gp-color-status-ink: #1b1f2a;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The F2 label tone: 10px tracked caps under each value, and the "NEXT LEVEL" caption. 4.64:1 over the sky's darkest band through the card's tint (measured, round 15). #5a6474 measures 3.76 there, which is why the labels are not lighter than this. */
  --gp-color-status-label: #4e5664;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. Accent TEXT on the checkpoint's card. --gp-color-blue itself measures 1.4-2.8:1 on this card and is unusable as text; the bar fill, the monogram and the effects keep the brand blue, only ink darkens. */
  --gp-color-status-accent-ink-blue: #084e82;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The same darkening of --gp-color-green, for the homepage and attendees/edit cards. */
  --gp-color-status-accent-ink-green: #095206;
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. The toast's second line — a refusal's reason, an operator's message. One step lighter than color/status-ink, which the headline keeps, so the two lines read as headline and detail without a second type size doing all the work. */
  --gp-color-toast-body: #3c4454;
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. The ¢ struck into a coin, on the toast's icon and on every disc in the burst. Dark brown rather than black: it is meant to read as part of the metal. */
  --gp-color-coin-ink: #7a4d00;
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. The bright inner rim of a coin, drawn as an inset ring rather than a border so it does not change the disc's size. See shadow/coin. */
  --gp-color-coin-rim: rgba(255, 249, 214, 0.9);
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. The shaded lower-right inside a tumbling disc, which is what gives it thickness as it turns. The toast's static icon does not take it. */
  --gp-color-coin-relief: rgba(150, 92, 0, 0.35);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The progress track's groove. Not color/progress-track (grey at .15, the old card's): the new bar is tinted with the sky's deep blue so it reads as a recess in the glass rather than a grey strip on it. */
  --gp-color-status-track: rgba(40, 61, 129, 0.12);
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The level-up's gold burst, colour 1 of five. These are read back off the stylesheet by src/status_effects.js, because a canvas cannot take a var() the way a CSS rule can — they are tokens all the same. The body gold every other one is judged against. */
  --gp-color-burst-gold-1: #f6c94a;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The level-up's gold burst, colour 2 of five. These are read back off the stylesheet by src/status_effects.js, because a canvas cannot take a var() the way a CSS rule can — they are tokens all the same. The deepest of the five: the shadowed faces of the burst. */
  --gp-color-burst-gold-2: #efab12;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The level-up's gold burst, colour 3 of five. These are read back off the stylesheet by src/status_effects.js, because a canvas cannot take a var() the way a CSS rule can — they are tokens all the same. The lightest: the ones catching the light. */
  --gp-color-burst-gold-3: #ffe27a;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The level-up's gold burst, colour 4 of five. These are read back off the stylesheet by src/status_effects.js, because a canvas cannot take a var() the way a CSS rule can — they are tokens all the same. The one warm ORANGE in an otherwise yellow set — the same amber the site already uses. Five yellows with no warm note in them read as a single flat colour in motion. */
  --gp-color-burst-gold-4: #f4a045;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The level-up's gold burst, colour 5 of five. These are read back off the stylesheet by src/status_effects.js, because a canvas cannot take a var() the way a CSS rule can — they are tokens all the same. Near-white cream, so a few particles read as highlights rather than confetti. */
  --gp-color-burst-gold-5: #fff3c4;
  /* Avatar chip. Hue 175°, 5.47:1 against white text. 58° from the nearest character hue. */
  --gp-color-avatar-teal: #0f766e;
  /* Gradient top-left stop for avatar-teal: the base mixed 30% toward white. */
  --gp-color-avatar-teal-light: #579f9a;
  /* Avatar chip. Hue 201°, 5.93:1 against white text. 84° from the nearest character hue. */
  --gp-color-avatar-ocean: #0369a1;
  /* Gradient top-left stop for avatar-ocean: the base mixed 30% toward white. */
  --gp-color-avatar-ocean-light: #4f96bd;
  /* Avatar chip. Hue 263°, 7.10:1 against white text. 43° from the nearest character hue. */
  --gp-color-avatar-violet: #6d28d9;
  /* Gradient top-left stop for avatar-violet: the base mixed 30% toward white. */
  --gp-color-avatar-violet-light: #9968e4;
  /* Avatar chip. Hue 345°, 6.29:1 against white text. 39° from Bachman's mauve — the tightest gap in the palette, and the only slot the hue circle leaves between Bachman at 306° and AMJ at 31°. */
  --gp-color-avatar-rose: #be123c;
  /* Gradient top-left stop for avatar-rose: the base mixed 30% toward white. */
  --gp-color-avatar-rose-light: #d25976;
  /* Avatar chip, neutral. 10.35:1 against white text. */
  --gp-color-avatar-slate: #334155;
  /* Gradient top-left stop for avatar-slate: the base mixed 30% toward white. */
  --gp-color-avatar-slate-light: #707a88;
  /* Avatar chip, pale — the only one carrying DARK text (color/black, 16.73:1). It exists because the hue circle is exhausted: the characters own orange, green and mauve, leaving only 152-271° and a sliver near 345°. A pale chip differs in lightness rather than hue so it cannot collide with any character colour, present or future. It needs a dark ring: it sits 1.26:1 against white. */
  --gp-color-avatar-bone: #e7e5e4;
  /* Gradient top-left stop for avatar-bone. NOT the 30%-toward-white mix the other five stops use: that formula gives #eeedec, which is indistinguishable from the #e7e5e4 base it is supposed to lighten because bone starts only a few percent short of white already. Chosen by hand instead, a few steps further up the same near-white run. */
  --gp-color-avatar-bone-light: #fafaf9;
  /* The top of the /login sky. Measured, not picked: the average of the top 1% of app/assets/images/gp-sky-portrait.jpg, which is #273b7f, and of gp-sky-landscape.jpg, which is the same to the byte. It is .gp-sky's background-COLOR under the photograph, and it has two jobs -- the colour the page holds while the JPEG is still arriving, and the one iOS 26 Safari tints the status bar with. Safari never reads background images, gradients or pseudo-elements; with viewport-fit=cover and no fixed element at the top edge it falls back to the body's background-color, which is this. Without it the sky runs under a WHITE status bar. */
  --gp-color-sky-top: #273b7f;
  /* 8 uses in the baseline. */
  --gp-dimension-radius-card: 16px;
  /* Navbar capsule. */
  --gp-dimension-radius-nav: 35px;
  /* Was 5rem. 9 uses. Any value past half the control height rounds identically, so px is safe here. */
  --gp-dimension-radius-pill: 80px;
  --gp-dimension-radius-round: 999px;
  /* 6 uses. Buttons, form controls, balance pill. */
  --gp-dimension-control-height: 42px;
  /* Shop inputs and selects. */
  --gp-dimension-control-height-sm: 35px;
  /* Stat chip (baseline:172). Fixed, so a long level name wraps out of it rather than growing it. */
  --gp-dimension-chip-height: 60px;
  /* Level progress bar (baseline:247). Fixed px inside a fluid card — it can overflow a narrow one. Recorded as a parity defect to carry, not a value to reuse. */
  --gp-dimension-meter-width: 300px;
  /* Form column on login, vouchers, edit-info and referrals. */
  --gp-dimension-form-width: 350px;
  --gp-dimension-blur-glass: 2px;
  /* PATTERN B, the panel (round 19). The blur behind a panel - the three participation panels' veil and the Buy More Chuds drawer's scrim, which round 19 makes the same layer. Was dimension/blur-scrim at 6px, the drawer's alone (round 9, Corey: a liquid-glass scrim; round 13: blur only, it no longer darkens). 3px is Corey's tuned value from public/glass-test-transitions.html revision 8, and it is deliberately light: a panel is a card OVER the page, so the page has to stay readable as the thing it is sitting on. The radius is FIXED and the layer's opacity is what animates. Re-filtering a full-screen backdrop every frame to animate the radius is the expensive way to the same picture, and in the prototype it was what made everything else stutter. */
  --gp-dimension-blur-panel: 3px;
  /* PATTERN B, the panel (round 19). How far a panel's card, or the drawer's sheet, rises as it arrives and sinks as it leaves. From public/glass-test-transitions.html revision 8's modalIn/modalOut. On the drawer it replaces Bootstrap's full-height slide-up; the drag-dismiss still travels the sheet's whole height, because that one is following a finger rather than playing a transition. */
  --gp-dimension-panel-lift: 28px;
  /* Backdrop blur behind the product card (Figma 143:320, backdrop-blur 10) and the buy-chuds drawer, which the plan gives the same glass as the card. No Figma frame exists for the drawer; the value is the card's. */
  --gp-dimension-blur-product-card: 10px;
  /* Checkpoint amount stepper (−/+) and Send buttons, Figma 193:1535. Close to radius-nav (35px) but a distinct authored value, not a typo of it. */
  --gp-dimension-radius-checkpoint-control: 31px;
  /* Buy More Chuds button, Figma 193:1542: a pill (33px is over half the 44px button). Round 11 tried 12px corners; round 12 (Corey): back to the pill. */
  --gp-dimension-radius-buy-chuds-button: 33px;
  /* Round 12: the performer row's inline padding (.gp-checkpoint__avatars), and so the slide track's inset from the card's content box — the track spans the three avatars as a unit, first avatar's left edge to the last one's right. One geometry source for both. */
  --gp-dimension-checkpoint-row-inset: 28px;
  /* Round 12 (Corey: "I meant vertically centered avatars"). With nobody chosen and the controls closed, the send card's avatar row is lifted by this and the card's bottom padding grows by the same, so the photos sit centred between the prompt and the card's bottom without the card changing height. Measured at 402 wide (CDP, the demo): prompt to photo 41px, photo to card bottom 31px — half the 10px difference each way makes 36/36. The open state keeps its geometry. */
  --gp-dimension-checkpoint-row-lift: 5px;
  /* Product card (Figma 143:320) and the buy-chuds drawer's top corners, which the plan specifies as matching the card. */
  --gp-dimension-radius-product-card: 20px;
  /* Product card's Buy button, Figma 143:320. Exceeds half the control's 35px height, so it renders identically to radius-round (999px) — recorded as the value Figma actually authored rather than simplified to the token that would look the same, same reasoning as dimension/meter-width. */
  --gp-dimension-radius-product-buy-button: 320px;
  /* Glass v2 (round 11): the backdrop blur on the send card and the drawer sheet. The same 10 the −/+, the track and the sheet already had (blur-product-card), kept as its own token so the glass can move without the product card. */
  --gp-dimension-glass-blur: 10px;
  /* Glass v2 (round 11): the TRUE edge frost's heavier blur. Preset 24; Corey: less frost (amendment 2). */
  --gp-dimension-glass-frost-blur: 16px;
  /* Glass v2 (round 11): the 9-slice width of the frost mask (app/assets/images/glass/frost-edge.svg), drawn 1:1 — equal to number glass/frost-slice. The visible band is ~14px; the rest is the rounded corner and the blur's falloff. Card and sheet must be at least twice this in each direction. */
  --gp-dimension-glass-frost-width: 50px;
  /* Glass v2 (round 11): the specular rim. Preset 1.5px; Corey: thinner (amendment 1). */
  --gp-dimension-glass-rim-width: 1px;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The status card's padding step. Used twice: the card's own side padding and the progress block's further padding, so the track sits 36px in from each card edge. It also placed the monogram until round 17, which moved that to status-avatar-inset (19px) so the disc could line up with the name's cap top. */
  --gp-dimension-status-inset: 18px;
  /* Round 17 (Corey: the monogram should span the text block, top edge level with the top of the name, bottom edge level with the bottom of the level title). Measured live 2026-09-20 at a 390px viewport, both skins: the name's cap top sits 19.46px below the card's border box and the level title's baseline 49.39px below it, so the disc is 29.93px tall — 30px, landing 0.46px above the cap and 0.39px above the baseline. Was 48px (round 16, prototype revision 10), which overhung the title by 6px. */
  --gp-dimension-status-avatar: 30px;
  /* Round 17. The monogram's inset from the card's top-left, kept EQUAL on both axes so the disc stays optically square to the corner (Corey: "if that means moving it closer to the left-hand side of the card, that's fine too, to keep it even"). It is the name's measured cap top, 19.46px below the card's border box, rounded. Separate from status-inset (18px), which the card's own padding and the progress block still use and which no longer has anything to do with where the monogram sits. */
  --gp-dimension-status-avatar-inset: 19px;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The progress bar. The old card's was 6.4px; the new one carries a shimmer and a flare (round 16-B) and needed the height. */
  --gp-dimension-status-bar-height: 8px;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The share button's tap target, around a 30px disc. Apple's minimum, and the reason the button is bigger than what it draws. */
  --gp-dimension-status-share-target: 44px;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The share button's visible disc inside its 44px target. */
  --gp-dimension-status-share-disc: 30px;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The shimmer that runs the bar and then rides the growing end. Wide and very soft rather than a hard glint: at 8px tall, a narrow band reads as a defect travelling along the bar. */
  --gp-dimension-status-sweep-width: 64px;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. How far the shimmer overhangs the track top and bottom. The band is taller than the groove so its soft edges are cut off by the groove's own clip instead of fading inside it. */
  --gp-dimension-status-sweep-bleed: 3px;
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The ring flare fired where the shimmer arrives. Scaled from 0.4 to 2.4 as it fades, so it is drawn at the size it looks right settling at, not at its peak. */
  --gp-dimension-status-glint-size: 18px;
  /* PATTERN A (round 19): how far the content drifts DOWN into place as it fades in. On the WRAPPER (.takeover__drift), never on the column itself — application.css moves .gp-checkpoint with `top` deliberately, so it does not become a stacking context for the status bar's plus-lighter fills. The wordmark travels 0.7 of this. */
  --gp-dimension-curtain-drift-in: 24px;
  /* And how far it drifts UP as it leaves — further than it came in, because it is leaving at speed rather than settling. The wordmark travels 0.75 of this. */
  --gp-dimension-curtain-drift-out: 36px;
  /* The navbar's OWN measured height/offset, WITHOUT any safe-area inset. Measured live at 390x844, signed in, with script/measure_live.js: nav.navbar's getBoundingClientRect() bottom edge is 79.1875px (top: 10px, height: 69.1875px; the navbar is position: relative, not fixed, so this only matters unscrolled). Rounded up for breathing room. Consumed by the mobile sale toast's top offset (sale-toast-layer branch) as calc(env(safe-area-inset-top) + var(--gp-dimension-navbar-clearance)) -- consumers MUST add env(safe-area-inset-top) themselves. This token cannot include it: it was measured in headless Chrome, where every env() reads 0, and PR #209 (main) later added viewport-fit=cover, so a real notched phone pushes the navbar down by that inset and a toast pinned to the bare token alone would sit back on the navbar-brand wordmark -- the bug this token exists to prevent. */
  --gp-dimension-navbar-clearance: 80px;
  /* The gap under the login form, between the Join Show button and the footer (Corey, 2026-09-21: the button sat too close). Was 30px, a literal on `#login form`. Not a general spacing step -- this file has no spacing scale and this is the one measured gap. */
  --gp-dimension-form-gap-bottom: 72px;
  /* Above the logged-out logo on /login, on top of the safe-area inset. Was 40px when first added 2026-09-21; tightened to 24px the same day, because the whole page has to fit a phone without scrolling and the logo's gap was the cheapest 16px to find. */
  --gp-dimension-logo-gap-top: 24px;
  /* The gap between a .gp-toast-stack and the edges of the viewport, and between the stack and the top of the screen. Stacked ON TOP of env(safe-area-inset-*) rather than instead of it: the layout carries viewport-fit=cover, so on a notched iPhone the status bar inset is added to this and on a desktop this is the whole margin. 16px, matching the 1rem the other fixed box on this surface uses (#notifications' .p-3, application.css) -- the two are pinned to opposite corners and should read as the same margin. */
  --gp-dimension-toast-inset: 16px;
  /* Between two stacked .gp-toast alerts. All four /login arrival messages are mutually exclusive, so two toasts only ever stack when a controller sets a notice alongside an alert; the value matters anyway because the stack is a flex column and Bootstrap's .alert margin-bottom is overridden in it. */
  --gp-dimension-toast-gap: 8px;
  /* The vertical rhythm between the logo, the operator's login_info copy and the form on /login. Replaces Bootstrap's .my-3 (16px) and the row's .mt-5 (16px) on that page only. Corey, 2026-09-21: 'less space between logo and that so the whole form fits without scrolling'. Measured: the document was 883px against an 844 viewport, and a real iPhone loses ~90px more to Safari's chrome. */
  --gp-dimension-login-stack-gap: 8px;
  /* ATTENDEE UI ONLY. Corey, 2026-09-14: the stage screen and broadcast board use Grange and Richmond and no IBM Plex Sans at all; the attendee UI is all IBM Plex Sans. Do not bind board or stage text to this token — they are two type systems, not one with an exception. The repository DOES load IBM Plex Sans now, as of 2026-09-15: app/assets/stylesheets/fonts.css declares the twelve faces and serves the woff2 files from app/assets/fonts, linked into the three attendee layouts and deliberately not into admin. Before that, the only loader was line 1 of the operator's custom_css row, which meant emptying the row would have taken the typeface off every attendee page in the same motion. The row's @import still resolves later in the cascade and still wins while the row is live, so fonts.css is inert WHILE THAT LOADER SUCCEEDS -- which is narrower than inert and is the point. When the @import or one of its font resources fails, those faces are not merely outranked, they do not exist, and the local ones are selected instead: the page then renders IBM Plex Sans where today it falls through to Helvetica Neue. That is a rendering difference from today, in exactly the bad-wifi scenario the file exists for. Emptying the row is the full handover. Regenerate it with script/fetch_webfont.rb, which derives every descriptor from that same @import rather than letting anyone author one. The four emoji families are NOT optional padding: the operator's row sets --bs-font-sans-serif to exactly this ten-entry stack (baseline:6), and admin's --gpa-font-family-base carries the same ten. This token shipped with six, dropping them, which broke the project's own rule that a token models what ships. It was inert -- nothing binds var(--gp-font-family) -- so the fix changes no rendering, but binding a six-entry stack during the migration would have quietly removed emoji coverage from every attendee page. */
  --gp-font-family: "IBM Plex Sans", "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  /* TWO NAMES ON PURPOSE. Lowercase "grange" is the Adobe web project (https://use.typekit.net/wsd4utv.css), which renames every family to its own slug; capital "Grange" is the CoreSync install on a signed-in Mac. Neither alone covers both machines, so both are in the chain with the slug first. Verified against the served CSS: it defines grange, grange-condensed and grange-extended at weights 400 and 700 only, normal and italic. NO HEAVY over the link — asking for 800 gets a synthesised bold, and on a CoreSync machine gets a REAL Grange Heavy, so the same page would render differently depending on whether the venue is signed into Creative Cloud. Pin stage weights to 700. The link is loaded on performers/status only, never in minimum.html.erb, which is shared with /order_notifications. Stage only; never an attendee surface. */
  --gp-font-family-grange: grange, Grange, "Helvetica Neue", Arial, sans-serif;
  /* Condensed is a SEPARATE FAMILY over the web link, not a width axis — font-stretch: condensed does nothing against the Typekit font and you must name "grange-condensed". The second name, capital Grange, is the CoreSync install, where font-stretch DOES select the condensed cut, so a declaration wanting condensed should set this family AND font-stretch: condensed. Needed because the board's big numbers overflow at normal width: the mockup sets a four-digit total 84px wide at 60px and Grange Bold is nearer 140px. */
  --gp-font-family-grange-condensed: grange-condensed, Grange, "Helvetica Neue", Arial, sans-serif;
  /* NOT IN THE WEB PROJECT. https://use.typekit.net/wsd4utv.css serves only the three Grange families — zero Richmond of any kind — so unlike Grange this resolves ONLY on a machine with Richmond Display installed locally through CoreSync, and falls back to Helvetica everywhere else, including over the link. A different situation from Grange, and the reason this token is riskier than it looks. The family is "Richmond Display", NOT "Richmond": system_profiler SPFontsDataType reports Family: Richmond Display with 14 styles. This token said "Richmond" until 2026-09-14 and could never have matched. Nothing binds it yet. */
  --gp-font-family-richmond: "Richmond Display", "Helvetica Neue", Arial, sans-serif;
  /* Checkpoint amount input and the "+N" sent line, Figma 193:1535/193:2013 ("IBM Plex Mono Bold 32"). Only weight 700 is loaded — see fonts.css — so bind this token only where the weight is bold; there is no regular-weight face to fall through to besides the system monospace stack. */
  --gp-font-family-mono: "IBM Plex Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --gp-font-weight-regular: 400;
  --gp-font-weight-medium: 500;
  --gp-font-weight-semibold: 600;
  --gp-font-weight-bold: 700;
  --gp-font-weight-heavy: 800;
  /* DEAD AS SHIPPED — kept because the value is real in the row and deleting it would hide that. All three uses are `.form-label { font-size: 0.6rem }` inside #login, [id$="-edit"] and #referrals-new, and every one of them is beaten by `.mb-3 label { font-size: 0.75rem }` in the same block: (0,1,1,1) over (0,1,1,0). bootstrap_form wraps every non-inline field in .mb-3, so a form label on those pages renders at 0.75rem and this token has never painted a pixel. .gp-field__label uses font/size-small. Delete this and the matching Figma variable after 4 October. */
  --gp-font-size-label: 0.6rem;
  /* Stat chip label (baseline:191). Deliberately px, not rem: the baseline sets it in px and 9px is below the point where a rem conversion is faithful. Smaller than any sane minimum — recorded as shipped, not endorsed. */
  --gp-font-size-micro: 9px;
  /* Level NAME in the first stat chip (baseline:236), which is smaller than the numeric values beside it because the names run long — "Senior Product Pioneer" at 24px would not fit 30% of a phone width. */
  --gp-font-size-chip: 13px;
  --gp-font-size-small: 0.75rem;
  --gp-font-size-body: 0.875rem;
  /* Checkpoint banner, participation body copy. */
  --gp-font-size-lead: 1.125rem;
  /* Product name, panel headers. */
  --gp-font-size-h2: 1.5rem;
  /* Page titles. */
  --gp-font-size-h1: 2rem;
  /* Broadcast board only. Read at distance, never on a phone. */
  --gp-font-size-display: 6rem;
  /* 10px. Checkpoint status card's "Next Level: " caption and the Buy More Chuds button label, Figma 193:1542. */
  --gp-font-size-caption: 0.625rem;
  /* 11px. UNUSED SINCE ROUND 16 — it sized the level name inside the checkpoint status card's stat pill (Figma 193:1542), and the card Corey approved in round 16 has no pills: the level name moved into the head at font/size-status-level-name (13px). Kept rather than deleted because the Figma variable still exists and a token vanishing is harder to explain than one that says why it is idle. */
  --gp-font-size-pill-level: 0.6875rem;
  /* 20px. Shared by the stat pill value and the person-selector card's idle/selected title (Figma 193:1913/193:1535), the product card name (Figma 143:320), and the buy-chuds drawer title. */
  --gp-font-size-title: 1.25rem;
  /* 16px. The Buy More Chuds button's label, round 4 — Figma's Medium 10 scaled with the button to 44 tall (Corey: bigger). */
  --gp-font-size-buy-chuds: 1rem;
  /* 16px. Product card price, Figma 143:320. */
  --gp-font-size-price: 1rem;
  /* 12.8px, as Figma specified it (get_design_context, not rounded to 12 or 13). Product card's "Commission Earned By" label, Figma 143:320. */
  --gp-font-size-commission-label: 0.8rem;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. 24px. The three big values — level, performance points, chuds. */
  --gp-font-size-status-value: 1.5rem;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. 17px. The attendee's name in the card's head. */
  --gp-font-size-status-name: 1.0625rem;
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. 13px. The level name under the attendee's name. Replaces size-pill-level (11px), which styled the level name inside the stat pill the round-16 card removed. */
  --gp-font-size-status-level-name: 0.8125rem;
  /* Round 17. 12px. The monogram's two letters. Was 19px in a 48px disc (round 16, prototype revision 10); the disc is 30px now that it spans the text block, and this keeps the same 0.4 of its diameter rather than filling it. */
  --gp-font-size-status-avatar: 0.75rem;
  --gp-shadow-glass: 0px 8px 32px 0px rgba(31, 38, 135, 0.2), inset 0px 4px 20px 0px rgba(255, 255, 255, 0.3);
  --gp-shadow-card: 0px 2px 6px 0px rgba(0, 0, 0, 0.08), inset 0px 1px 2px 0px rgba(0, 0, 0, 0.06);
  /* The ::after highlight. Written 16 times in the baseline, but only 11 attach to an element that can render a pseudo-element (baseline:704 — pre-cut snapshot; the #login block was removed from the row 2026-09-20, so that line number has no successor — and :951 target <input>, and the three .btn-primary::after copies target submit inputs), and 2 of the survivors use drop-shadow(10px 3px 6px #888) brightness(122%) instead. All 11 carry an IDENTICAL box-shadow — the 2 variants differ only in their filter chain and a misspelt --backdrop-filter — so THIS token has 11 live copies. The wider pseudo-element recipe has 9. Still the most duplicated thing in the file. */
  --gp-shadow-rim: inset -10px -8px 0px -11px rgba(255, 255, 255, 1), inset 0px -9px 0px -8px rgba(255, 255, 255, 1);
  /* Outer shadow, checkpoint status card and person-selector card ("same two shadows as the status card" — plan, Figma 193:1542). Pair with checkpoint-card-inset. Colour is color/black-15 both layers, written literally per format_shadow's contract (shadow layer colours are not alias-resolved). */
  --gp-shadow-checkpoint-card: -11.15px -10.392px 48px -12px rgba(0, 0, 0, 0.15), -1.858px -1.732px 12px -8px rgba(0, 0, 0, 0.15);
  /* Inset highlight paired with checkpoint-card. Colour is color/white-13 both layers. */
  --gp-shadow-checkpoint-card-inset: inset 2.146px 2px 9.24px rgba(255, 255, 255, 0.13), inset 1.217px 1.134px 4.62px rgba(255, 255, 255, 0.13);
  /* Outer shadow, checkpoint amount stepper (−/+) buttons and the Send button ("same border + shadows" as the stepper — plan, Figma 193:1535). Pair with checkpoint-control-inset. Colour is color/black-15 both layers. */
  --gp-shadow-checkpoint-control: -3px -3px 48px -12px rgba(0, 0, 0, 0.15), -1.858px -1.732px 12px -8px rgba(0, 0, 0, 0.15);
  /* Round 4, Corey: the checkpoint's -/+ buttons and slide track in the cards' glass language — a bright top edge and a soft inner glow. Not in Figma; a judgment call, drawn from color/white and color/white-50. */
  --gp-shadow-checkpoint-glass-inset: inset 0px 1px 0px #ffffff, inset 0px 0px 8px rgba(255, 255, 255, 0.5);
  /* Inset highlight paired with checkpoint-control. First layer is color/white-14, second is color/white-35 — the two layers are deliberately different alphas, unlike checkpoint-card-inset. */
  --gp-shadow-checkpoint-control-inset: inset 2.146px 2px 9.24px rgba(255, 255, 255, 0.14), inset 1.217px 1.134px 4.62px rgba(255, 255, 255, 0.35);
  /* Outer shadow, Buy More Chuds button, Figma 193:1542. Pair with buy-chuds-button-inset. Colour is color/black-15 both layers; offsets differ from checkpoint-card and checkpoint-control, which is why this is its own token despite the shared colour. */
  --gp-shadow-buy-chuds-button: 12.676px -15.983px 48px -12px rgba(0, 0, 0, 0.15), 2.113px -2.664px 12px -8px rgba(0, 0, 0, 0.15);
  /* Inset highlight paired with buy-chuds-button. Colour is color/white-13 both layers, same value as checkpoint-card-inset. */
  --gp-shadow-buy-chuds-button-inset: inset -2.44px 3.077px 9.24px rgba(255, 255, 255, 0.13), inset -1.384px 1.745px 4.62px rgba(255, 255, 255, 0.13);
  /* Outer shadow, product card, Figma 143:320. Single layer, unlike the checkpoint shadows above. Pair with product-card-inset. Colour is color/black-10. */
  --gp-shadow-product-card: 0px 8px 32px 0px rgba(0, 0, 0, 0.1);
  /* Inset highlight paired with product-card. First layer is color/white-50, second is color/glass-sheen (0.1) — same rgba(255,255,255,.1) already used elsewhere, reused here rather than duplicated as a new primitive. */
  --gp-shadow-product-card-inset: inset 0px 1px 0px 1px rgba(255, 255, 255, 0.5), inset 0px -1px 0px 1px rgba(255, 255, 255, 0.1);
  /* Glass v2: the inner top highlight and bottom shade. Preset 1.5px at .9/.25; round 11 1px at .6/.15 (amendment 1); round 12 (Corey: less specular) the highlight .4 and softened by a 1px blur, the same on −/+ and the thumb. */
  --gp-shadow-glass-edge: inset 0px 1px 1px rgba(255, 255, 255, 0.4), inset 0px -1px 1px rgba(40, 61, 129, 0.15);
  /* Glass v2 (round 12): glass-edge's top highlight alone, for the drawer sheet, which has no bottom edge (Corey: no bottom border on the panel). Keep equal to glass-edge's first layer. */
  --gp-shadow-glass-edge-top: inset 0px 1px 1px rgba(255, 255, 255, 0.4);
  /* Glass v2 (round 14, item 4; Corey: chromatic edge too dark on the card and the avatars' bottoms, subtle on the sheet and −/+). Cause: glass-edge's bottom shade, rgba(40,61,129,.15), is identical on every surface, but nothing dilutes it evenly — the sheet's tint is .55 and the −/+'s is .3, opaque enough to soften it, while the card's tint is only .22 (round 12 thinned it deliberately) and the avatar's rim/shine paint ABOVE the photo (z-index 1, no __blur layer at all) with nothing in front of it. Same top highlight, the dark layer halved (.15→.08) for card and avatar only; glass-edge itself is untouched, so the sheet, −/+ and thumb keep reading as they do. */
  --gp-shadow-glass-edge-muted: inset 0px 1px 1px rgba(255, 255, 255, 0.4), inset 0px -1px 1px rgba(40, 61, 129, 0.08);
  /* Glass v2 (round 11): the colour fringe at the side edges, cyan left, magenta right. Preset .25/.2, lowered with the rim. */
  --gp-shadow-glass-fringe: inset 1px 0px 0px rgba(0, 255, 255, 0.15), inset -1px 0px 0px rgba(255, 0, 200, 0.12);
  /* Glass v2 (round 11): card and sheet depth, sky-tinted. Preset. */
  --gp-shadow-glass-depth: 0px 1px 2px rgba(40, 61, 129, 0.3), 0px 14px 34px -10px rgba(40, 61, 129, 0.45);
  /* Glass v2 (round 11): −/+ and the slide thumb. The card's depth scaled to a 48px control. */
  --gp-shadow-glass-depth-control: 0px 1px 2px rgba(40, 61, 129, 0.25), 0px 8px 18px -8px rgba(40, 61, 129, 0.4);
  /* Glass v2 (round 11): Buy More Chuds, flatter (amendment 3). */
  --gp-shadow-glass-depth-button: 0px 1px 2px rgba(40, 61, 129, 0.2), 0px 4px 10px -6px rgba(40, 61, 129, 0.3);
  /* Glass v2: CHEAP edge frost on −/+ — an inset glow, no second backdrop pass. Preset .7; round 11 .55 (amendment 2); round 12 .3, so the control is not a white disc over white clouds. */
  --gp-shadow-glass-frost-cheap-control: inset 0px 0px 9px 1px rgba(255, 255, 255, 0.3);
  /* Glass v2 (round 11): the least frost of all, on Buy More Chuds (amendment 2). */
  --gp-shadow-glass-frost-cheap-button: inset 0px 0px 6px rgba(255, 255, 255, 0.15);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The monogram's dimension: a lit top edge, a shaded bottom, and a white ring holding it off the card. Composed in components.css with shadow/glass-depth-control, which carries the drop — a shadow token cannot reference another. */
  --gp-shadow-status-avatar: inset 0px 1px 1px rgba(255, 255, 255, 0.4), inset 0px -1px 2px rgba(0, 0, 0, 0.13), 0px 0px 0px 2px rgba(255, 255, 255, 0.8);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The progress groove: pressed in at the top, with a white lip below it. */
  --gp-shadow-status-track: inset 0px 1px 2px rgba(40, 61, 129, 0.18), 0px 1px 0px rgba(255, 255, 255, 0.5);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The share disc's lit top edge. Composed in components.css with shadow/glass-depth-control, the same depth the -/+ controls carry. */
  --gp-shadow-status-share: inset 0px 1px 1px rgba(255, 255, 255, 0.55);
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. A coin's drop shadow and its bright rim in one, so the toast's icon and a flying disc are struck the same way. The drop uses the sky's deep blue rather than black — every surface these land on is the cloud image. */
  --gp-shadow-coin: 0px 1px 3px rgba(40, 61, 129, 0.45), inset 0px 0px 0px 1.5px rgba(255, 249, 214, 0.9);
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. Added to shadow/coin on the flying discs only: the shaded inside edge that reads as thickness while the disc tumbles through rotateY. The toast's icon never turns, so it does not take it. */
  --gp-shadow-coin-relief: inset -2px -3px 4px rgba(150, 92, 0, 0.35);
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. Under the "+¢N" pill that rises over the Chuds value. The checkpoint's blue accent at 0.45 — literal rather than a reference to color/blue because a shadow needs the channels, and this effect only ever plays on the checkpoint's card this round (the homepage's green card is not wired). */
  --gp-shadow-status-bubble: 0px 3px 8px rgba(28, 150, 229, 0.45);
  --gp-zIndex-reminder: 1057;
  /* The participation opt-in ask (participation/_opt_in, .takeover--ask). BELOW the quiz (1055) on Corey's ruling, 2026-09-21: being asked whether you would like to take part at some point tonight is the least urgent thing that can be on screen, and a question the room is answering mid-air outranks it. It shares Pattern B's look with being chosen (.takeover--panel, 1060) and no longer shares its rung -- that split is the whole reason this token exists. */
  --gp-zIndex-participation-ask: 1052;
  /* Being live on stage (participation/_live) AND standby (participation/_standby, .takeover--blocking), which Corey put on the same rung 2026-09-21: waiting to be called is the same order of urgency as being called, and neither may be covered by anything the attendee could be doing instead. Above the buy-chuds drawer (1075) on the same ruling -- the drawer used to tie with being on stage. Below the admin toasts (1080, application.css), which takeover_layering_spec pins as the ceiling for every takeover. */
  --gp-zIndex-participation-live: 1077;
  --gp-zIndex-overlay: 1050;
  /* The buy-chuds drawer's scrim (.gp-drawer-scrim, round 10: the drawer's own element, not Bootstrap's .modal-backdrop). Above the checkpoint takeover (1065, application.css), which the drawer opens from, and one step below the sheet. */
  --gp-zIndex-buy-chuds-drawer-backdrop: 1070;
  /* The buy-chuds drawer itself, one step above its own backdrop. See buy-chuds-drawer-backdrop. */
  --gp-zIndex-buy-chuds-drawer: 1075;
  /* The toast lane (.gp-toasts). Round 16 (cluster C) replaced the checkpoint's own bottom-of-screen lane with the system one at the TOP, and took it from 1080 to 1100: it is above the takeover (1065) and above the buy-chuds drawer that opens over it (1075), because a refusal the attendee cannot see is no refusal, and 1100 leaves a step free above the drawer for anything that has to sit between them. The site's other notifications stay at overlay (1050), under every takeover, deliberately. */
  --gp-zIndex-checkpoint-toast: 1100;
  /* ABOVE EVERYTHING, on Corey's ruling 2026-09-22: "toasts should just always be the absolute top line, z layer over everything, there are just some views where we don't show them." So the ladder no longer decides whether a toast is seen -- the suppression rules next to #notifications in application.css do, and they are a list of screens rather than a number. 1095 clears being on stage (1077) and the admin flash (1080); the checkpoint's own lane stays above it at 1100, because that one answers a tap the attendee just made and a sale does not. Was 1072, and before that 1070, tied with the buy-chuds scrim. */
  --gp-zIndex-sale-toast: 1095;
  /* The flash toast stack (.gp-toast-stack). 1090 since 2026-09-22, up from 1050: Corey ruled that toasts are the top line, over everything, and that the screens a toast must NOT appear on are a list rather than a rank. It was ranked with #notifications under every takeover on the argument that a takeover is inescapable by design and a dismissible box over one is a way out the show did not intend -- a good argument, and it said "do not show it here" a second time, in a second place, where the suppression rules beside #notifications already said it. Two statements of one rule can disagree; one cannot. So this cleared the takeovers AND joined that suppression list in the same commit, because with the rank no longer burying it a missing entry there is a toast over a takeover rather than merely a wrong number. Below the sale toast (1095) and the checkpoint lane (1100), both of which report something that just happened to this attendee. */
  --gp-zIndex-toast: 1090;
  /* Vibrancy: saturate(180%) on the card's and sheet's blur. Preset. */
  --gp-glass-saturate: 1.8;
  /* Vibrancy: brightness on the card's and sheet's blur. Preset 1.08; round 12 (Corey): 1.0, so white clouds do not get whiter under the glass. */
  --gp-glass-brightness: 1;
  /* The edge frost's saturate. Preset. */
  --gp-glass-frost-saturate: 1.5;
  /* The edge frost's brightness. Preset 1.1; round 12: 1.0 with the card's, so the frosted edge does not whiten white clouds. */
  --gp-glass-frost-brightness: 1;
  /* Slice inset into app/assets/images/glass/frost-edge.svg, in its own px. Change it only with the SVG: the corner tile must hold the whole rounded corner and its blur. */
  --gp-glass-frost-slice: 50;
  /* The fill is transparent until the thumb passes this much of its travel. */
  --gp-slide-fill-start: 0.4;
  /* The fill is at full opacity from this much of the travel on. Round 13, Corey: ramp to 100% of travel, not 60% (was reaching full opacity too early). */
  --gp-slide-fill-full: 1.0;
  /* The specular rim, painted into a 1px ring by a mask (and the slide thumb's border). Preset alphas .95/.4/.1/.5; round 11 .7/.3/.08/.35 (amendment 1); round 12 (Corey: less specular) the light end .45/.18/.05, the sky-dark end kept for separation. */
  --gp-gradient-glass-rim: linear-gradient(135deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0.18) 35%, rgba(255, 255, 255, 0.05) 60%, rgba(40, 61, 129, 0.35));
  /* Glass v2 (round 14, item 4; Corey: chromatic edge too dark on the card and the avatars' bottoms). Same rim as glass-rim, with the sky-dark end halved (.35→.18) — see glass-edge-muted's description for why the card and the avatars show that end unmuted while the sheet, −/+ and thumb do not. Card and avatar only; glass-rim itself is untouched. */
  --gp-gradient-glass-rim-muted: linear-gradient(135deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0.18) 35%, rgba(255, 255, 255, 0.05) 60%, rgba(40, 61, 129, 0.18));
  /* The sheen down card, sheet, −/+ and avatars. The preset stepped from .1 to 0 between 33% and 34% — a hard line; this eases out instead (amendment 5). Round 12 (Corey: less specular): about half round 11's .4/.2/.08/.02. */
  --gp-gradient-glass-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.11) 18%, rgba(255, 255, 255, 0.04) 40%, rgba(255, 255, 255, 0.01) 60%, rgba(255, 255, 255, 0) 78%);
  /* Buy More Chuds: a soft sheen on the opaque blue, lighter than the glass surfaces' (amendment 3). Round 12: lowered with the rest (.22 → .14). */
  --gp-gradient-glass-sheen-button: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.06) 25%, rgba(255, 255, 255, 0.01) 55%, rgba(255, 255, 255, 0) 70%);
  /* The slide thumb's sheen over the performer's colour — the thumb, not the track, carries the glass (amendment 4). Round 12: lowered with the rest (.45 → .28). */
  --gp-gradient-glass-sheen-thumb: linear-gradient(180deg, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0.11) 30%, rgba(255, 255, 255, 0.03) 60%, rgba(255, 255, 255, 0) 80%);
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The hairline between two stats: a vertical shade that fades out at both ends rather than a ruled line. Paired in components.css with a white highlight on its right (color/white-50), the same two-tone edge the glass uses elsewhere. */
  --gp-gradient-status-divider: linear-gradient(rgba(40, 61, 129, 0), rgba(40, 61, 129, 0.22) 30%, rgba(40, 61, 129, 0.22) 70%, rgba(40, 61, 129, 0));
  /* Round 16 (cluster A), from public/glass-test-status.html revision 10 — the status card Corey approved. The gloss on the progress fill, laid over the accent colour so one rule serves both the blue and the green card. */
  --gp-gradient-status-fill-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.35), transparent 60%);
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The shimmer itself. Much more translucent than the prototype's first pass (.45 at the peak, not .95) and it lives in the TRACK rather than inside the fill, so it can ride the growing end instead of crossing a bar that has already finished moving. */
  --gp-gradient-status-sweep: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.18) 30%, rgba(255, 255, 255, 0.45) 50%, rgba(255, 255, 255, 0.18) 70%, transparent);
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The ring flare, fired where the shimmer arrives. White at the core through the card's own accent at the edge — --status-accent is the card's own local variable, which the blue and the green skins each set, so one gradient serves both. color-mix rather than an rgb() triple because the skins set a colour, not three numbers — a second token carrying the same blue again as "29, 150, 229" would be that value written twice. */
  --gp-gradient-status-glint: radial-gradient(circle, #ffffff 0 22%, rgba(255, 255, 255, 0.8) 32%, color-mix(in srgb, var(--status-accent) 50%, transparent) 55%, transparent 72%);
  /* Round 16 (cluster B), from public/glass-test-status.html revision 10 — the points bar and the level-up Corey approved. The foot of the level-up's confetti canvas. The canvas overhangs the card so the glass cannot clip the particles, and this fades its bottom out so they dissolve rather than stop dead on an invisible line. */
  --gp-gradient-status-burst-mask: linear-gradient(to bottom, #000000 58%, rgba(0, 0, 0, 0.35) 84%, transparent 100%);
  /* Round 16 (cluster C), from public/glass-test-status.html revision 10 — the toast system and the D2 coin burst Corey approved. A coin's face: the highlight off-centre at 34%/28% so every disc in the burst catches the light from the same place, which is what stops eleven of them looking like eleven separate objects. The same face on the toast's 26px icon and on the 12.5px discs. */
  --gp-gradient-coin-face: radial-gradient(circle at 34% 28%, #fffbe3 0 12%, #ffd75e 45%, #f0ab13 78%, #b87708 100%);
  /* count_controller.js reads this off the element (--gp-motion-count-duration) the same way stage_board_controller reads --stage-tween — see app/javascript/src/count_to.js. */
  --gp-motion-count-duration: 600ms;
  /* Width transition on the status card's level-progress fill. */
  --gp-motion-progress-fill: 400ms;
  /* Every CSS transition on the checkpoint's person-selector card: ring colour and greyscale, the title, the stepper and slide's opacity. Movement (the row's recentring, the chosen avatar's growth) is Motion springs in checkpoint_controller.js, not this. 250ms is auto-animate's own default, from before that change. */
  --gp-motion-checkpoint-state: 250ms;
  /* The checkpoint's stepper and slide opening when somebody is chosen and closing when nobody is (Motion, checkpoint_controller.js), height and opacity together on one ease-in-out. Round 5, Corey on an iPhone: smoother than round 4's 300ms, which read as a snap. */
  --gp-motion-checkpoint-controls: 450ms;
  /* The same controls closing when the chosen avatar is tapped again (round 12, Corey: "Deselect person drawer close faster"). A send's close keeps checkpoint-controls. fit_controller.js brings the wordmark and mascot back over the same time. */
  --gp-motion-checkpoint-controls-deselect: 280ms;
  /* The checkpoint's -/+ and the Buy More Chuds button sinking as a finger presses (round 8, Corey: press and hover with colour and motion). Quick, so the press is felt at once. */
  --gp-motion-press: 120ms;
  /* The same buttons coming back up after a press, on an overshooting curve (components.css) — the light bounce of a spring, in CSS. */
  --gp-motion-press-release: 380ms;
  /* How long a stat's floating "−N" or "+N" leads the number it announces (round 9, Corey: the bubble slightly before the count). checkpoint_controller.js#send. */
  --gp-motion-bubble-lead: 170ms;
  /* After a send lands, the gap between the chuds balance starting to count down and the points and level bar starting to go up (checkpoint_controller.js#send, src/display_gate.js). Round 4, Corey: in sequence, not together. Round 18 put the controls' close inside that gap, so this is now the MINIMUM the send waits rather than the whole of it — the beat, then checkpoint-close-after-send, then the 450ms close, then the points. */
  --gp-motion-stats-stagger: 300ms;
  /* The beat checkpoint_controller.js#send waits before clearSelection() starts closing the controls. Round 13, Corey: "slightly longer after performance points circle float before closing drawer" — so it began as the pause after the "+N" points bubble. Round 18 moved the close to BEFORE the points bubble and the bar (Corey: "Drawer should close before the amount floats up on the performance points and the bar animates etc."), so the same 400ms is repurposed rather than retuned: it is now the beat between the chuds balance starting to count down and the panel starting to move, long enough that the close reads as deliberate rather than snapping shut on the balance's first tick. A judgment call, not measured. */
  --gp-motion-checkpoint-close-after-send: 400ms;
  /* PATTERN B, the panel (round 19). A panel's veil fading in and out, and its card sinking out - 300ms in AND out, the value Corey tuned on his phone 2026-09-20 in public/glass-test-transitions.html revision 8. Was motion/drawer-scrim, at the same 300ms because Bootstrap 5.0.2's offcanvas transition is 0.3s; it is now the whole pattern's number, shared by the three participation panels and the drawer. */
  --gp-motion-panel: 300ms;
  /* PATTERN B, the panel (round 19). The card's rise on the way IN, longer than the veil's fade because it is a spring: cubic-bezier(0.22, 1.3, 0.36, 1), which overshoots. The prototype asked Motion for bounce 0.26 at visualDuration 360ms and carried its own no-network fallback rendering that as this curve over visualDuration x 1.3, which is where 468 comes from. The fallback is what ships, because a panel has to be able to arrive in the page's first paint - before the module graph exists, and so before Motion does. Leaving is not a spring and takes motion/panel. */
  --gp-motion-panel-lift: 468ms;
  /* PATTERN B, the panel (round 19), under prefers-reduced-motion. The veil and the card cross-fade over this and nothing moves. A fade rather than nothing at all, so the panel still reads as arriving - the prototype's reduced path. */
  --gp-motion-panel-reduced: 120ms;
  /* The checkpoint's chip flying from the end of the slide into the chosen avatar after a send (Motion, checkpoint_controller.js#fly). x is linear and y eases in over this one duration, which is what makes the path an accelerating arc. Round 3, a judgment call. */
  --gp-motion-chip-flight: 650ms;
  /* PATTERN A, the curtain (round 19). How long the sky takes to travel a full screen height on the way IN — the value Corey tuned on his phone 2026-09-20, public/glass-test-transitions.html revision 8. The entrance eases at its END (it arrives and settles): cubic-bezier(0.2, 0.7, 0.2, 1), written into the keyframes in application.css. It REPLACES takeover-enter, whose fade-and-rise the curtain now does instead. */
  --gp-motion-curtain-in: 540ms;
  /* The same travel on the way OUT. The same length as the entrance but NOT the same curve: the exit eases at its START and leaves at full speed — cubic-bezier(0.55, 0, 1, 1), which ends on the diagonal. An ease-out here is the stutter Corey saw as the sky crept off the top. src/curtain.js runs this one rather than CSS, because the exit has to hold the turbo stream's render until it has finished. It replaces takeover-exit. */
  --gp-motion-curtain-out: 540ms;
  /* The content's own fade on the way in, behind the curtain. Shorter than the curtain and started late (curtain-content-in-delay), so the column is still arriving while the sky is still travelling. */
  --gp-motion-curtain-content-in: 420ms;
  /* The content's fade on the way out. */
  --gp-motion-curtain-content-out: 360ms;
  /* How long the content waits after the curtain starts coming in: the sky LEADS on the way in. The wordmark takes 0.7 of this delay, so it arrives a beat before the column does. */
  --gp-motion-curtain-content-in-delay: 180ms;
  /* How long the content LEADS the curtain by on the way out — the mirror of curtain-content-in-delay, and the reason the two are separate tokens rather than one offset carrying a sign. Corey tuned it as a negative content offset ("content offset OUT", −80ms, negative = leads); the sky waits this long before it starts up the screen. */
  --gp-motion-curtain-content-out-lead: 80ms;
  /* THE CHECKPOINT ONLY. How long the takeover stays after the window has shut, so the room is told it is over rather than having the screen taken away under them: the toast, a muted deselect and the column draining to grey (round 19). checkpoint_controller.js holds against a Date.now() deadline rather than a timer, because a backgrounded tab throttles setTimeout. */
  --gp-motion-curtain-hold: 3000ms;
  --gp-surface-page: var(--gp-color-page);
  --gp-surface-glass: var(--gp-color-glass-fill);
  --gp-surface-glass-strong: var(--gp-color-glass-fill-strong);
  /* A control sitting on photography rather than on the page background. Only #login uses it today. */
  --gp-surface-glass-soft: var(--gp-color-glass-fill-soft);
  --gp-surface-alert: var(--gp-color-alert-yellow);
  /* An opaque floating surface. Glass in shape — same radius, rim and shadow — but solid, for anything that sits over video or over content it must not reveal. The sale toast is the case that forced it: /order_notifications is a transparent browser source, so any alpha at all lets the footage through. */
  --gp-surface-plate: var(--gp-color-plate);
  /* The solid dark block — the +N Chuds chip on a sale toast. */
  --gp-surface-ink: var(--gp-color-black);
  /* The unfilled part of a meter. On the broadcast board the accent fill grows along this as chuds arrive. */
  --gp-surface-track: var(--gp-color-white);
  --gp-text-body: var(--gp-color-black);
  --gp-text-muted: var(--gp-color-grey-700);
  --gp-text-label: var(--gp-color-grey-800);
  --gp-text-field: var(--gp-color-green-dark);
  --gp-text-on-dark: var(--gp-color-white);
  /* Text over photography — the login cloud plate, the broadcast board. Same value as on-dark today but a different contract: on-dark is safe by construction, on-image is only safe with a scrim behind it. */
  --gp-text-on-image: var(--gp-color-white);
  /* Validation message under a field. Darker than border/error on purpose: #dc3545 is 4.49:1 on the page background and this is small text, #842029 is 9.38:1. Same reasoning as border/focus being black. */
  --gp-text-error: var(--gp-color-red-dark);
  --gp-action-primary: var(--gp-color-green);
  --gp-action-primary-border: var(--gp-color-green-border);
  --gp-action-on-primary: var(--gp-color-white);
  /* performers.icon_color defaults to #FFFFFF, which is invisible against surface/track and against white text. Anything rendering an accent must fall back to this when icon_color is blank or white. */
  --gp-accent-fallback: var(--gp-color-green);
  /* Bachman's character colour. The runtime value comes from performers.icon_color on the record, NOT from here — accent_colour in app/helpers/performers_helper.rb renders the database string directly. Recorded because the avatar palette is defined by its hue distance from the three character colours, and a distance to an untokenised literal is not reviewable. Source: the bound Figma variable on stage board frame 102:253. */
  --gp-accent-bachman: #A1659B;
  /* AMJ's character colour, and the reason the avatar palette carries no orange. Runtime value comes from performers.icon_color. NOTE: Figma frame 131:646 shows #FF9500 for AMJ — that is iOS system orange from a community kit and is wrong; the bound variable on 102:253 is authoritative. */
  --gp-accent-amj: #F4A045;
  /* Todd's character colour. Runtime value comes from performers.icon_color. Sits 23° from color/green, which is why no green entered the avatar palette. */
  --gp-accent-todd: #76BA43;
  --gp-border-glass: var(--gp-color-glass-border);
  --gp-border-glass-bright: var(--gp-color-glass-border-bright);
  /* Focus ring. Black rather than brand green on purpose: a focus indicator needs 3:1 against BOTH the control it outlines and the surface behind it, and green-dark against green is nowhere near that. Black is 4.9:1 on the green button and 21:1 on the page. */
  --gp-border-focus: var(--gp-color-black);
  /* Border of an invalid control. Matches what .form-control.is-invalid already paints, so a .gp-field and a Bootstrap field in the same form do not disagree about what invalid looks like. */
  --gp-border-error: var(--gp-color-red);
  --gp-radius-pill: var(--gp-dimension-radius-pill);
  --gp-radius-card: var(--gp-dimension-radius-card);
  --gp-radius-nav: var(--gp-dimension-radius-nav);
  --gp-control-height: var(--gp-dimension-control-height);
  --gp-control-height-sm: var(--gp-dimension-control-height-sm);
}
