/* ==========================================================================
   Self-hosted faces. Converted from the supplied OTFs to WOFF2 (fonttools).
   Lab Grotesque : 816 glyphs — full Latin. Safe for UI and body.
   FT System     : 66 glyphs — A-Z a-z 0-9 . , ONLY. Display use only, and it
                   cannot set most of this app's copy. See CONTEXT.md.
   ========================================================================== */
@font-face { font-family: "Lab Grotesque"; src: url("assets/fonts/lab-grotesque-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Lab Grotesque"; src: url("assets/fonts/lab-grotesque-700.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Lab Grotesque Mono"; src: url("assets/fonts/lab-grotesque-mono-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Lab Grotesque Mono"; src: url("assets/fonts/lab-grotesque-mono-700.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }

@font-face { font-family: "Season Mix"; src: url("assets/fonts/season-mix-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Season Mix"; src: url("assets/fonts/season-mix-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "Season Mix"; src: url("assets/fonts/season-mix-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap; }
:root {
  /* Every page starts at the same x: the rail's width plus the page gutter.
     Views without a rail leave the gutter empty rather than pulling left. */
  /* Device safe areas. env() is correct on a real handset; the fallbacks match
     an iPhone 17's status bar and home indicator so the prototype sits where the
     shipped app would. */
  /* Shape. Navigation started as the one rounded family and the product has
     followed it, so the radius is a token rather than an exception. */
  --r-pill: 999px;
  --r-field: 12px;
  /* Alpha ladder. Overlays and text on media were using twenty six different
     alphas; five steps cover every case, so "white at 60" means one thing. */
  --a-1: .12;  --a-2: .24;  --a-3: .40;  --a-4: .60;  --a-5: .82;
  /* Frost: one tint and one blur for every full-screen overlay. */
  --frost-tint: rgb(232 232 232 / .34);
  --frost-blur: blur(34px) saturate(118%);
  --r-card: 8px;   /* map nodes */
  --r-chip: 7px;   /* icon chips */
  --safe-top: max(env(safe-area-inset-top), 52px);
  --safe-bottom: max(env(safe-area-inset-bottom), 30px);
  --rail-w: 190px;
  --page-gutter: clamp(24px, 6vw, 90px);
  /* documentation grid: racks share one gap, prose columns share another */
  --doc-gap: 16px;
  --doc-col-gap: 40px;

  /* ------------------------------------------------------------------
     Maestro brand — the "Maestro Brand Colors" board is canonical.
     Six values, exactly as specified. Where the D2 artwork differs
     (#EFEFEF for gray, #201E1A for off-black) the BOARD wins.
     Provenance + contrast table: 01_Operations/03_Clients/04_Active/
     fictive-kin/05_Deliverables/maestro/brand-token-layer/
     ------------------------------------------------------------------ */

  /* The six */
  --off-white:   oklch(1.0000 0 0);            /* #FFFFFF */
  --gray:        oklch(0.9370 0 0);            /* #EAEAEA */
  --off-black:   oklch(0.1730 0 0);            /* #101010 */
  --accent:      oklch(0.9240 0.0984 116.9);   /* #E1EFA3 lemon */
  --purple:      oklch(0.7840 0.0870 301.2);   /* #C2ACE8 light purple */
  --brand:       oklch(0.2349 0.0713 24.5);    /* #390C0C burgundy red */

  /* Roles, bound to the six. Names unchanged so existing usage keeps working. */
  --surface: var(--off-white);
  --canvas:  var(--gray);
  --ink:     var(--off-black);
  --raised:  oklch(0.9800 0 0 / .82);
  --muted:   oklch(0.3942 0 0);                /* #464646 */
  --quiet:   oklch(0.5800 0 0);
  --line:    oklch(0.9000 0 0 / .9);
  --pressed: oklch(0.9350 0 0 / .85);

  /* On burgundy */
  --on-brand:       var(--off-white);
  --on-brand-muted: var(--gray);
  --brand-raised:   oklch(0.3191 0.0604 21.0);
  --brand-hovered:  oklch(0.3191 0.0604 21.0);
  --brand-pressed:  oklch(0.1750 0.0560 24.5);
  --gray-hovered:   oklch(0.8950 0 0);
  --gray-pressed:   oklch(0.8550 0 0);
  /* Map state colours come from the brand palette, not from invented hues. The
     four states the map needs are already in it: lemon, burgundy, purple and a
     neutral for what is not built. Glyph colour is set per state because lemon
     and purple need ink on them and burgundy needs white. */
  --s-entry:  var(--accent);
  --s-built:  var(--brand);
  --s-shared: var(--purple);
  --s-todo:   var(--quiet);
  --chassis:        oklch(0.12 0 0);            /* device shell, not a brand color */
  --media-ground:   oklch(0.22 0 0);            /* behind photography before it loads */
  --hairline:       oklch(1 0 0 / .5);          /* light hairline on tinted grounds */
  --brand-scrim:    oklch(0.2349 0.0713 24.5 / .5);

  /* Semantics. Lemon measures 1.23:1 on white, so it is never a mark on the
     white ground — it is legal on burgundy or off-black only. */
  --live:     oklch(0.55 0.15 30);
  --signal:   oklch(0.52 0.13 25);
  --notation: var(--purple);
  --harmony:  var(--ink);

  --sans: "Lab Grotesque", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --mono: "Lab Grotesque Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  /* The display face. There is no serif in this system — this is FT System
     Blank. Lab Grotesque sits behind it purely as a glyph fallback: FT System
     Trial carries only A-Z a-z 0-9 . , so accents, colons, dashes, apostrophes
     and question marks would otherwise drop to a system font. */
  /* Season Mix, Displaay Type Foundry. 501 glyphs, missing none of the accents,
     question marks or dashes that broke the previous display face inside names
     like Măcelaru. Currently a TRIAL cut: see the licensing note before this
     ships anywhere client-facing. */
  --display: "Season Mix", "Lab Grotesque", ui-sans-serif, sans-serif;
  --ease: cubic-bezier(.22, 1, .36, 1);
}

* { box-sizing: border-box; }
html {
  /* Some views scroll and some do not. With a classic scrollbar that changes
     the viewport width, which moves anything centred — including the nav.
     Reserve the gutter so the bar cannot shift between views. */
  scrollbar-gutter: stable; background: var(--canvas); color: var(--ink); font-family: var(--sans); }
body { margin: 0; min-width: 320px; }
button { color: inherit; font: inherit; }
/* One focus treatment, everywhere: 2px ink, offset 2px. Five rules ran at 3px
   and one at 2px, and the input stacked an inset ring on top of its outline, so
   focus read as a heavy double frame rather than a state. */
button:focus-visible { outline: 1.5px solid var(--harmony); outline-offset: 2px; }
[hidden] { display: none !important; }
.icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}
.icon-chevron { width: 15px; height: 15px; color: var(--quiet); }

.prototype-bar {
  min-height: 64px;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 0;
  background: transparent;
  position: sticky;
  top: 0;
  z-index: 20;
}
.prototype-mark { display: inline-flex; align-items: center; margin-right: 12px; color: var(--brand); }
/* One outlined lockup, referenced by <use>, inheriting currentColor so it works
   on white and on burgundy. Source: assets/maestro-lockup.svg (511x92). */
/* The lockup ships with 8 units of clearspace padded into its viewBox, because
   the artwork itself has none: its ink sits on the box edge. Ratio follows the
   padded box, not the artwork. */
.lockup { display: block; width: auto; aspect-ratio: 638 / 85; fill: currentColor; }
.prototype-mark .lockup { height: 18px; }
.splash-stack .lockup { height: 34px; }
.identity-intro h1 .lockup { height: 46px; }
/* Mark slots inside the app. Home speaks as the product; the pass is the one
   artifact a member hands to another person, so it carries the mark too.
   Organisation codes (CSO on event IDs) stay in the data, not in these slots. */
.app-header > .lockup { height: 13px; flex: none; margin-top: 7px; color: var(--brand); }
.pass-top .lockup { height: 11px; flex: none; color: currentColor; }
.prototype-meta { color: var(--muted); font-size: 12px; }
/* The one rounded family in the system is navigation: the in-app bar is a pill,
   so the view switch is the same object at desk scale rather than a second
   exception. The mark sits inside the pill, so the bar carries one object
   centred rather than two things pushed to opposite edges. */
/* One spacing value everywhere: the pill's padding and every gap inside it are
   the same 8px, so the mark is inset from the edge exactly as far as the tabs
   are from each other. No drop shadow — the hairline and the blur separate it
   from the page. */
.nav-pill { display: flex; align-items: center; gap: 26px;
  padding: 8px 8px 8px 22px; border-radius: var(--r-pill);
  background: color-mix(in oklch, var(--off-white) 60%, transparent);
  backdrop-filter: blur(26px) saturate(170%);
  -webkit-backdrop-filter: blur(26px) saturate(170%);
  box-shadow: none; }
.nav-pill .prototype-mark { margin: 0; flex: none; display: inline-flex;
  align-items: center; color: var(--brand); }
.nav-pill .prototype-mark .lockup { height: 15px; }
/* One grid track per view, all 1fr, so every segment is the width of the
   longest label instead of the width of its own words. */
.view-switch { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 8px; padding: 0; border: 0; border-radius: var(--r-pill);
  background: transparent; }
.view-switch button { position: relative; min-height: 40px; border: 0;
  border-radius: var(--r-pill); padding: 0 18px; font-family: var(--sans);
  font-size: 11px; font-weight: 500; letter-spacing: .005em;
  white-space: nowrap; cursor: pointer;
  color: var(--muted); background: transparent;
  box-shadow: none;
  transition: color 160ms ease, background-color 220ms var(--ease),
    box-shadow 220ms var(--ease); }
.view-switch button:hover { color: var(--ink);
  background: rgb(255 255 255 / .60); }
/* The active tab must not change weight. Tracks are sized from the widest
   label, so bolding a long one grew every track, which resized the pill, which
   moved it — the bar drifted as you tabbed around. The white fill and the ink
   colour carry the state instead. */
.view-switch button.is-active { color: var(--ink);
  background: var(--off-white); box-shadow: none; }
.view-switch button:focus-visible { outline: 1.5px solid var(--harmony);
  outline-offset: 2px; }


.stage {
  min-height: calc(100vh - 64px);
  padding: 24px clamp(20px, 6vw, 96px) 32px;
  display: grid;
  place-items: center;
}
.phone {
  width: auto;
  aspect-ratio: 402 / 874;
  height: min(874px, calc(100vh - 78px));
  min-height: 700px;
  border: 8px solid var(--chassis);
  border-radius: 55px;
  background: var(--surface);
  position: relative;
  overflow: hidden;
  box-shadow: 0 30px 90px rgb(20 20 18 / .14), inset 0 0 0 1px var(--hairline);
}
.screen { height: 100%; overflow: auto; scrollbar-width: none;
  padding: calc(var(--safe-top) + 6px) 18px 104px; }
.screen::-webkit-scrollbar { display: none; }
.splash-screen {
  position: absolute;
  /* inset already sizes this to the padding box; the extra width/height was
     resolving against a different box and pushing 7px past the frame. */
  inset: 0;
  border-radius: 47px;
  padding: 0;
  overflow: hidden;
  background: #080808;
  color: white;
}
.splash-screen > img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  /* The canon frame is landscape and the device is portrait, so cover crops the
     sides. Hold the hand and the bow, which sit centre-right of the frame. */
  object-position: 62% center;
  filter: contrast(1.04);
  /* Matches the 2s hold: an animation that outlives the screen never lands. */
  animation: splash-focus 2s var(--ease) both;
}
.splash-shade {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgb(0 0 0 / .18) 0%, transparent 34%),
    linear-gradient(to top, rgb(0 0 0 / .86) 0%, rgb(0 0 0 / .08) 46%, transparent 68%);
}
.splash-stack {
  position: absolute;
  left: 22px;
  right: 22px;
  top: 50%;
  transform: translateY(-42%);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.splash-stack p {
  margin: 10px 0 0;
  color: rgb(255 255 255 / .82);
  font-size: 15px;
  letter-spacing: .005em;
}
/* No button: the splash advances on its own and the whole screen is the
   fallback control, so nothing needs a visible affordance. */
.splash-screen { cursor: pointer; }
/* A button that sits on a photograph: white ground, ink label, same pill. */
/* Buttons that sit on a photograph are glass, not paint: translucent white over
   a blur, the same treatment the navigation pill uses, so the image reads through
   them instead of being blocked out. */
/* Glass, not paint. At 72% over a bright part of the photograph it read as a
   solid white pill; at 52% with a heavier blur the image moves through it and a
   hairline gives the edge definition the lower opacity costs. */
.btn--onmedia { background: color-mix(in srgb, var(--off-white) 52%, transparent);
  color: var(--ink); backdrop-filter: blur(26px) saturate(180%);
  -webkit-backdrop-filter: blur(26px) saturate(180%);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .40); }
.btn--onmedia:active { background: color-mix(in srgb, var(--off-white) 70%, transparent); }
.app-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 26px; padding-top: 22px; }
.app-header.simple { align-items: center; }
.date { color: var(--muted); font-size: 12px; letter-spacing: .07em; text-transform: uppercase; margin: 0 0 9px; }
.app-header h1, .sheet-page h1 { font: 36px/1.0 var(--display); letter-spacing: -.028em; margin: 0; font-weight: 400; }
.app-header.simple h1 { font-size: 32px; }
.app-header.simple .date { margin-bottom: 6px; }
.avatar, .round-action {
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--hairline); background: var(--raised); cursor: pointer; flex: none; transition: transform 160ms ease, background-color 180ms ease;
}
.avatar:active, .round-action:active { transform: scale(.94); }
.avatar { font-size: 12px; font-weight: 650; }


.status-strip { min-height: 38px; border: 1px solid var(--hairline); border-radius: 0; display: flex; align-items: center; gap: 8px; padding: 0 12px; background: var(--raised); font-size: 10px; margin-bottom: 12px; }
.status-strip strong { margin-left: auto; font-weight: 600; }
.status-glyph { width: 15px; height: 15px; color: var(--signal); stroke-width: 1.8; }

.feature-art, .event-hero { height: 168px; position: relative; display: flex; align-items: center; justify-content: center; background: linear-gradient(145deg, var(--media-ground), var(--media-ground)); color: white; overflow: hidden; }
.feature-art::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to bottom, rgb(10 8 4 / .40), transparent 48%, rgb(10 8 4 / .12)); pointer-events: none; }
.feature-image { width: 100%; height: 100%; object-fit: cover; object-position: center 57%; transition: transform 700ms var(--ease), filter 700ms var(--ease); }
.feature:hover .feature-image { transform: scale(1.018); filter: saturate(1.04); }
.event-hero::before { content: ""; position: absolute; width: 210px; height: 210px; border: 1px solid var(--hairline); border-radius: 50%; transform: translate(-80px, 45px); box-shadow: 56px -38px 0 -1px transparent, 56px -38px 0 0 rgb(255 255 255 / .24); }
.feature-letter { font: italic 110px/.8 var(--display); opacity: .92; transform: translateY(-2px); animation: letter-drift 8s ease-in-out infinite alternate; }
.event-id { position: absolute; z-index: 1; left: 31px; top: 16px; font-size: 11px; letter-spacing: .05em; opacity: .92; }
.feature-copy { padding: 15px 16px 17px; }
.feature-copy p, .event-copy > p:first-child { margin: 0 0 4px; color: var(--muted); font-size: 13px; }
.feature-copy h2 { font: 22px/1.04 var(--display); margin: 0 0 6px; letter-spacing: -.025em; font-weight: 400; }
.feature-copy > span { color: var(--muted); font-size: 11px; display: block; }
.primary-action, .secondary-action { border: 0; min-height: 44px; border-radius: var(--r-pill); padding: 0 17px; cursor: pointer; font-size: 12px; letter-spacing: .025em; font-weight: 600; transition: transform 150ms ease, opacity 150ms ease; }
.primary-action:active, .secondary-action:active { transform: scale(.985); opacity: .86; }
.primary-action { color: white; background: var(--brand); }
.secondary-action { background: var(--pressed); }
.feature-copy .primary-action { width: 100%; margin-top: 14px; }

.section-block { margin-top: 24px; }
.section-heading { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 9px; }
.section-heading h2 { font-size: 13px; letter-spacing: .025em; margin: 0; }
.section-heading span, .section-heading button { color: var(--muted); border: 0; background: none; font-size: 11px; }
.section-heading button { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: flex-end; padding: 0 4px; margin: -14px -4px -14px 0; cursor: pointer; }
.action-row { width: 100%; min-height: 68px; border: 1px solid var(--hairline); background: var(--raised); border-radius: 0; padding: 10px 12px; display: grid; grid-template-columns: 38px 1fr auto; align-items: center; gap: 10px; text-align: left; cursor: pointer; transition: transform 180ms var(--ease), background-color 180ms ease; }
.action-row:active, .compact-grid button:active { transform: scale(.985); }
.row-icon { width: 38px; height: 38px; display: grid; place-items: center; border-radius: 0; background: var(--pressed); font-size: 19px; }
.action-row strong, .action-row small { display: block; }
.action-row strong { font-size: 13px; }
.action-row small { color: var(--muted); margin-top: 3px; font-size: 11px; }
.compact-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.compact-grid button { border: 1px solid var(--hairline); background: var(--raised); border-radius: 0; min-height: 82px; padding: 13px; text-align: left; cursor: pointer; transition: transform 180ms var(--ease); }
.compact-grid strong { display: block; font: 25px/1 var(--display); margin-bottom: 8px; font-weight: 400; }
.compact-grid span { font-size: 11px; color: var(--muted); }

.bottom-nav { position: absolute; left: 0; right: 0; bottom: 0; min-height: 72px; background: var(--gray); border: 0; border-radius: 0; padding: 0 0 env(safe-area-inset-bottom, 0px); display: grid; grid-template-columns: repeat(4, 1fr); z-index: 5; box-shadow: none; }
.bottom-nav button { border: 0; background: none; border-radius: 0; font-size: 12px; color: var(--quiet); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; cursor: pointer; transition: color 180ms ease, transform 160ms ease; }
.bottom-nav button .icon { width: 22px; height: 22px; stroke-width: 1.6; }
.bottom-nav button:active { transform: scale(.94); }
/* current tab is tinted, not underlined -- the iOS convention, and it keeps
   the no-lines direction intact. */
.bottom-nav button.is-active { background: none; color: var(--brand); font-weight: 650; }
.bottom-nav button.is-active .icon { stroke-width: 2; }

.segments { display: flex; background: var(--gray); border: 0; border-radius: var(--r-pill); padding: 3px; margin-bottom: 14px; }
.segments button { flex: 1 1 0; min-width: 0; border: 0; background: none; min-height: 32px; padding: 0 12px; border-radius: var(--r-pill); font-size: 11px; letter-spacing: .025em; color: var(--muted); white-space: nowrap; transition: background-color 180ms ease, color 180ms ease; }

.segments button.is-active { background: var(--off-white); color: var(--ink); font-weight: 650; }


.detail-bar { min-height: 44px; display: grid; grid-template-columns: 44px 1fr 44px; align-items: center; text-align: center; margin: -5px 0 10px; font-size: 12px; letter-spacing: .02em; font-weight: 600; }
.detail-bar button { width: 44px; height: 44px; border: 0; background: transparent; font-size: 22px; cursor: pointer; }
.event-hero { margin: 0 -16px; height: 220px; }
/* Scrim instead of a text-shadow under .event-id. Fades to nothing well inside
   its own box so it never meets the image on a hard 1px line. */
.event-hero::after { content: ""; position: absolute; inset: 0 0 auto; height: 92px;
  background: linear-gradient(to bottom, rgb(0 0 0 / .55) 0%, rgb(0 0 0 / .28) 42%, rgb(0 0 0 / 0) 100%);
  pointer-events: none; }
.event-copy { padding-top: 18px; }
.event-copy h1 { font: 32px/.98 var(--display); margin: 0 0 12px; letter-spacing: -.03em; font-weight: 400; }
.event-meta { color: var(--muted); font-size: 12px; line-height: 1.55; }
.inline-actions { display: flex; gap: 8px; margin: 20px 0 28px; }
.program { border-top: 1px solid var(--line); padding-top: 18px; }
.program h2 { font-size: 15px; margin: 0 0 10px; }
.program div { display: flex; justify-content: space-between; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.program span { color: var(--muted); }

.sheet-page { padding-top: 14px; }
.sheet-page > h1 { margin-bottom: 26px; max-width: 10ch; }
.choice { width: 100%; min-height: 78px; border: 0; border-top: 1px solid var(--line); background: transparent; display: grid; grid-template-columns: 46px 1fr auto; gap: 12px; align-items: center; text-align: left; cursor: pointer; }
.choice:last-of-type { border-bottom: 1px solid var(--line); }
.choice-icon { width: 46px; height: 46px; border-radius: var(--r-field); background: var(--raised); display: grid; place-items: center; font-size: 20px; }
.choice strong, .choice small { display: block; }
.choice strong { font-size: 15px; }
.choice small { color: var(--muted); font-size: 12px; margin-top: 4px; }
.support-note { color: var(--muted); font-size: 11px; line-height: 1.5; padding: 14px; background: var(--raised); border: 1px solid var(--hairline); border-radius: 0; margin-top: 18px; }

.tier { background: color-mix(in oklch, var(--signal), white 86%); color: var(--brand); border-radius: 0; padding: 7px 10px; font-size: 11px; font-weight: 700; }

.benefit-list { border-top: 1px solid var(--line); }
.benefit-list button { width: 100%; min-height: 78px; border: 0; border-bottom: 1px solid var(--line); background: transparent; display: flex; justify-content: space-between; align-items: center; text-align: left; cursor: pointer; }
.benefit-list strong, .benefit-list small { display: block; }
.benefit-list strong { font-size: 15px; }
.benefit-list small { color: var(--muted); font-size: 12px; margin-top: 4px; }
.benefit-list button > span:last-child { background: var(--pressed); border-radius: var(--r-pill); padding: 8px 11px; font-size: 12px; }

.profile-block { display: flex; align-items: center; gap: 12px; padding: 4px 0 24px; }
.large-avatar { width: 56px; height: 56px; border-radius: 50%; background: var(--pressed); display: grid; place-items: center; font: 20px/1 var(--display); }
.profile-block strong, .profile-block small { display: block; }
.profile-block strong { font-size: 16px; }
.profile-block small { color: var(--muted); font-size: 12px; margin-top: 4px; }
.settings-list { border-top: 1px solid var(--line); }
.settings-list button { width: 100%; min-height: 54px; padding: 0; border: 0; border-bottom: 1px solid var(--line); background: transparent; display: flex; justify-content: space-between; align-items: center; font-size: 14px; cursor: pointer; }
.settings-label { display: inline-flex; align-items: center; gap: 12px; }
.settings-label .icon { width: 16px; height: 16px; color: var(--muted); }

/* Same page margins as the identity and system views: the map is a page,
   not a canvas pinned to the window edge. Width stays uncapped so four columns
   keep their room. */
.map-view { padding: 70px var(--page-gutter) 96px; max-width: none; margin: 0; }
/* Header, legend and plate were on four different columns: 280, 90, 260, 290.
   One rule puts them all on the same one. */
.map-view > .map-header,
.map-view > .map-legend,
.map-view > .sitemap-layout { margin-left: var(--rail-w); margin-right: 0; max-width: 1180px; }
/* label sits above the title it labels; description is subordinate, not a peer */
.map-header { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, 380px);
  grid-template-areas: "label desc" "title desc"; column-gap: 48px; align-items: start; margin-bottom: 30px; }
.map-header p { grid-area: label; }
.map-header h1 { grid-area: title; }
.map-header > span { grid-area: desc; align-self: end; }
.map-header p { margin: 0 0 6px; font-family: var(--sans); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.map-header h1 { font: 46px/1 var(--display); margin: 0; letter-spacing: -.025em; }
.map-header > span { color: var(--muted); font-size: 11px; line-height: 1.55; max-width: 46ch; }
.sitemap-layout { display: block; }
.sitemap-scroll { overflow-x: auto; padding-bottom: 18px; scrollbar-color: var(--line) transparent; }
.sitemap {
  width: 100%;
  max-width: 1180px;
  border-radius: var(--r-field);
  padding: 30px 30px 36px;
  border: 0;
  border-radius: 0;
  background-color: color-mix(in oklch, var(--raised) 60%, transparent);
  position: relative;
}
/* Cards after the org-chart reference: an icon in its own column, the name, a
   detail line, then the kind on a rule beneath. Smaller than before, and
   left-aligned, because centred text in a card this size reads as a label
   rather than a record. */
/* Two-part card, after the reference: a header row carrying the icon and the
   name, a hairline, then the detail in its own zone. The padding lives on the
   parts rather than the card, so a node with no detail is just the header. */
.site-node { width: 150px; min-height: 48px; padding: 0; border-radius: var(--r-card);
  display: grid; grid-template-columns: 35px minmax(0, 1fr); column-gap: 10px;
  row-gap: 0; align-content: start; text-align: left; border: 1px solid transparent;
  background: var(--raised); font-size: 11px; font-weight: 600;
  position: relative; z-index: 2; }
.site-node .node-ico { grid-column: 1; grid-row: 1; align-self: center;
  width: 24px; height: 24px; padding: 5px; box-sizing: border-box;
  border-radius: var(--r-chip); margin: 10px 0 10px 11px;
  color: var(--glyph, var(--off-white));
  background: var(--dot, var(--quiet)); }
.site-node .node-t { grid-column: 2; grid-row: 1; align-self: center;
  font-weight: 600; line-height: 1.25; padding: 10px 11px 10px 0; }
.site-node[data-kind] .node-t { padding-right: 74px; }
.site-node .sub { grid-column: 1 / -1; grid-row: 2; padding: 9px 11px 10px;
  border-top: 1px solid rgb(0 0 0 / .07); }
.site-node[data-route] { cursor: pointer; }
.site-node[data-route]:hover { background: var(--off-white); }
.site-node[data-route]:hover .node-ico { opacity: 1; }
.site-node[data-route]:focus-visible { outline: 1.5px solid var(--harmony); outline-offset: 2px; }

/* The four destinations are the spine of the app, so they read as a different
   class of thing: filled, not outlined, with the bottom-nav slot they occupy. */
.node-primary[data-nav-slot] { background: var(--ink); color: var(--off-white);
  border-color: var(--ink); }
.node-primary[data-nav-slot] .node-ico { color: var(--off-white);
  background: rgb(255 255 255 / .12); }
.node-primary[data-nav-slot]::before { content: "NAV " attr(data-nav-slot);
  position: absolute; top: 50%; transform: translateY(-50%); right: 12px;
  padding: 2px 8px; border-radius: var(--r-pill); background: rgb(255 255 255 / .12);
  font-family: var(--sans); font-size: 8.5px; font-weight: 600;
  letter-spacing: .07em; color: rgb(255 255 255 / .82); line-height: 1.5; }
.node-primary[data-nav-slot]:hover { background: var(--ink); }
.site-node small { margin-left: 7px; padding: 3px 5px; border-radius: 0; background: var(--ink); color: white; font-size: 7px; text-transform: uppercase; }
/* State is a badge, not a hairline. Four low-chroma borders at 1px were not
   telling each other apart, so each state now carries a filled dot in its own
   hue beside its label, and the hues are pushed far enough apart in hue angle
   to be distinguishable: amber 85, teal 195, violet 300, blue 250. */
.node-root     { border-color: var(--s-entry);  --dot: var(--s-entry);  --glyph: var(--ink); }
.node-detail   { border-color: var(--s-built);  --dot: var(--s-built);  --glyph: var(--off-white); }
.node-auth     { border-color: var(--s-shared); --dot: var(--s-shared); --glyph: var(--ink); }
.node-function { border-color: var(--s-todo);   --dot: var(--s-todo);   --glyph: var(--off-white); border-style: dashed; }
.node-root, .node-detail, .node-auth, .node-primary[data-nav-slot] { border-color: transparent; }
.node-primary { --dot: var(--off-white); }
/* The dot rides on the label's own background box, because ::before is the
   connector stem and ::after is the label. No third pseudo needed. */

.ia-children .site-node::before { content: ""; position: absolute; left: 50%;
  top: -26px; width: 0; height: 26px; margin-left: -0.5px;
  border-left: 1px dashed var(--quiet); }
.tree-root { display: flex; flex-direction: column; align-items: center; }
.root-line { height: 24px; border-left: 1px solid var(--quiet); }
.entry-path { display: flex; align-items: center; justify-content: center; gap: 8px; }
.tree-arrow { color: var(--quiet); font-size: 12px; transform: rotate(-90deg); }
.shell-trunk { height: 28px; border-left: 1px solid var(--quiet); }
.branch-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 28px; position: relative; padding-top: 1px; }
.branch-grid::before { content: ""; position: absolute; left: 110px; right: 110px; top: 0; border-top: 1px solid var(--quiet); }
.tree-branch { display: flex; flex-direction: column; align-items: center; position: relative; }
.branch-stem { height: 28px; border-left: 1px solid var(--quiet); }
.child-stack { margin-top: 22px; display: grid; gap: 20px; }
.child-stack .site-node::before { content: ""; position: absolute; left: 50%; top: -21px; height: 20px; border-left: 1px solid var(--quiet); }
.utility-row { margin-top: 54px; padding-top: 20px; border-top: 1px solid var(--quiet); display: flex; justify-content: flex-end; align-items: center; gap: 12px; }
.utility-row > span { color: var(--muted); font-size: 9px; margin-right: auto; }

.map-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px; margin: 0 0 22px;
  padding-top: 14px; border-top: 1px solid var(--line); }
.map-tools { margin-left: auto; display: flex; gap: 8px; }
.map-tools button { min-height: 34px; padding: 0 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--off-white); color: var(--ink);
  font-size: 11px; font-weight: 500; cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease; }
.map-tools button:hover { background: var(--gray); border-color: var(--quiet); }
.map-tools button:hover { background: var(--gray); }
.map-tools button.is-done { background: var(--brand); color: var(--off-white); border-color: var(--brand); }
.legend-key { display: inline-flex; align-items: center; gap: 8px; color: var(--muted); font-size: 11px; }
.legend-key .swatch { width: 10px; height: 10px; border-radius: 50%; border: 0;
  background: var(--dot, var(--quiet)); flex: none; }
/* border-color is intentionally NOT set here: the shorthand would reset it to
   currentColor and outrank the .node-* classes, flattening every key to gray. */
.legend-key .swatch.node-function { border-style: solid; }
.legend-key .swatch.node-primary { background: var(--ink); }

.ia-root { display: flex; flex-direction: column; align-items: center; max-width: 1120px; margin-inline: auto; }
.ia-root .site-node { width: 190px; }
.ia-root-stem { height: 24px; border-left: 1px solid var(--quiet); }
.ia-columns { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; position: relative; padding-top: 1px;
  max-width: 1120px; margin-inline: auto; }
.ia-columns::before { content: ""; position: absolute; left: calc((100% - 60px) / 8); right: calc((100% - 60px) / 8); top: 0; border-top: 1px solid var(--quiet); }
.ia-branch { display: flex; flex-direction: column; align-items: center; }
.ia-branch-stem { height: 24px; border-left: 1px solid var(--quiet); }
.ia-branch > .node-primary { width: 100%; min-height: 48px; border-radius: var(--r-pill);
  grid-template-columns: 24px minmax(0, 1fr); align-content: center;
  column-gap: 10px; padding: 8px 16px; }
.ia-branch > .node-primary .node-ico { grid-row: 1; align-self: center; }
.ia-branch > .node-primary .node-t { align-self: center; }
.ia-branch > .node-primary .node-t { padding-right: 0; }
.node-primary[data-nav-slot]::before { right: 14px; }
.ia-children { display: grid; gap: 26px; margin-top: 22px; align-content: start; width: 100%; }
.ia-children .site-node { width: 100%; }
.ia-children .site-node { --stem: 1; }
.ia-children .site-node::marker { content: ""; }
.ia-children .site-node { width: 100%; }
.sitemap .site-node { line-height: 1.25; }
.sitemap .site-node .sub { font-size: 9.5px; font-weight: 450; letter-spacing: 0; color: var(--muted); text-transform: none; line-height: 1.35; }
.ia-band { margin-top: 52px; padding-top: 0; border-top: 0; max-width: 1120px; margin-inline: auto; }
.ia-band header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 16px; gap: 16px; }
.ia-band header span { color: var(--muted); font-size: 10px; }
.ia-band header strong { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
.ia-feature-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.ia-feature-grid .site-node { width: 100%; }
.ia-shared { grid-template-columns: repeat(3, 1fr); }

.ia-utilities { margin-top: 40px; border-top-color: var(--quiet); }
.ia-utilities .site-node { font-weight: 500; color: var(--muted); }

/* ---- Typed nodes, after the flow-canvas reference -----------------------
   In the reference every node carries a small label above it saying what kind
   of step it is, sitting on the connector so it breaks the line. Adopted here
   for the types that are easy to misread — the entry, the four destinations,
   the shared screens and anything not built. The ordinary in-prototype screen
   stays unlabelled: repeating one word down thirty nodes is noise, not signal.
   Curved edges and card shadows did not come across: they carry six nodes and
   turn to spaghetti at forty-five, and the system has neither radii nor
   shadows. */
.site-node[data-kind]::after {
  content: attr(data-kind);
  position: absolute;
  top: 9px;
  right: 10px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--dot, var(--quiet)) 15%, transparent);
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--dot, var(--quiet)) 55%, var(--ink));
  white-space: nowrap;
  line-height: 1.5;
}


/* Direction on the spine only. Six arrowheads read as flow; forty-five read as
   clutter, and a top-down tree is not ambiguous about which way it runs. */
.ia-root-stem, .ia-branch-stem { position: relative; }
.ia-root-stem::after, .ia-branch-stem::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -1px;
  width: 5px;
  height: 5px;
  border-right: 1px solid var(--quiet);
  border-bottom: 1px solid var(--quiet);
  transform: translate(-50%, 0) rotate(45deg);
}

.identity-view { display: grid; grid-template-columns: 190px minmax(0, 1fr); align-items: start; }
.identity-subnav {
  position: sticky;
  top: 64px;
  height: calc(100vh - 64px);
  padding: 30px 18px;
  /* No plate and no rule. The rail is a list on the page, not a panel beside
     it: the quiet type and the space around it do the separating, so any fill
     here reads as a lighter column laid over the canvas. */
  background: none;
}
.identity-subnav > span { display: block; color: var(--muted); font-size: 10px; margin: 0 0 24px 21px; }
/* A quiet list, not a set of tabs. No plate: the current section is marked by
   a dot in its own gutter, everything else sits back in --quiet. Numerals stay
   because the sections are an ordered sequence, but they carry no emphasis. */
.identity-subnav nav { display: grid; gap: 12px; }
.identity-subnav a { position: relative; padding: 0 0 0 21px; display: block;
  border-radius: 0; color: var(--quiet); text-decoration: none;
  font-size: 15px; line-height: 1.3; transition: color 160ms ease; }
.identity-subnav a:hover { color: var(--muted); }
.identity-subnav a.is-current { color: var(--ink); font-weight: 500; }
.identity-subnav a.is-current::before { content: ""; position: absolute;
  left: 4px; top: .52em; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; }
.identity-content { width: 100%; max-width: calc(1180px + var(--page-gutter) * 2); margin: 0;
  padding: 70px var(--page-gutter) 96px; }
.identity-content [id] { scroll-margin-top: 90px; }
.identity-intro { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, 380px);
  grid-template-areas: "label desc" "title desc"; column-gap: 48px; align-items: start; }
.identity-intro > p { grid-area: label; }
.identity-intro h1 { grid-area: title; }
.identity-intro > div { grid-area: desc; align-self: end; }
.identity-intro > p { margin: 0 0 8px; font-family: var(--sans); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.identity-intro h1 { margin: 0; font: 76px/.86 var(--display); letter-spacing: -.035em; }
.identity-intro > div { display: grid; gap: 10px; }
.identity-intro strong { font: 22px/1.08 var(--display); font-weight: 400; }
.identity-intro span { color: var(--muted); font-size: 11px; line-height: 1.55; max-width: 48ch; }
/* An intro with no description column does not reserve one. The grid holds
   "label desc" / "title desc" and a minmax(220px, 380px) second track, so
   dropping the div without this leaves a 380px gutter and squeezes the h1 into
   the first column for no reason. Scoped by absence rather than by id, so the
   identity intro — which still has its description — is untouched. */
.identity-intro:not(:has(> div)) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "label" "title";
}
.identity-rule { display: none; }
.identity-section { display: grid; grid-template-columns: 180px 1fr; gap: 28px; padding: 44px 0; }
.identity-section > header { display: grid; grid-template-columns: 30px 1fr; align-content: start; }
.identity-section > header span { color: var(--quiet); font: 15px/1 var(--display); }
.identity-section > header h2 { margin: 0; font-size: 12px; }
.principle-list { display: grid; grid-template-columns: 1fr 1fr; column-gap: var(--doc-col-gap); }
.principle-list article { min-height: 96px; padding: 14px 0; }
.principle-list b { font-size: 13px; }
.principle-list p { margin: 6px 0 0; color: var(--muted); font-size: 11px; line-height: 1.5; }
.type-system { grid-template-columns: 180px 1fr 1fr; }
.type-specimen { padding-top: 12px; }
.type-specimen > span { color: var(--muted); font-size: 11px; }
.display-specimen p { margin: 38px 0 0; font: 64px/.9 var(--display); letter-spacing: -.035em; }
.ui-specimen p { margin: 48px 0 4px; font-size: 24px; font-weight: 650; letter-spacing: -.025em; }
.ui-specimen small { color: var(--muted); font-size: 10px; }
.color-rack { display: grid; grid-template-columns: repeat(6, 1fr); min-height: 190px; }
.color-rack article { padding: 14px; display: flex; flex-direction: column; justify-content: space-between; border: 1px solid var(--line); border-right: 0; font-size: 10px; }
.color-rack article:last-child { border-right: 1px solid var(--line); }
.color-rack code { font: 9px var(--sans); opacity: .6; }
.swatch-ink { background: var(--ink); color: white; }
.swatch-stage { background: var(--off-white); }
.swatch-lobby { background: var(--gray); }
.swatch-brand { background: var(--brand); color: white; }
.swatch-purple { background: var(--purple); color: var(--ink); }
.swatch-accent { background: var(--accent); color: var(--ink); }
.component-board { display: grid; grid-template-columns: 1fr 1fr; gap: var(--doc-gap); }
.component-board > article { padding: 16px; background: var(--raised); display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 18px; }
.component-label { color: var(--muted); font-size: 11px; }
.identity-status { width: 100%; margin: 0; }
.icon-specimens { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--doc-gap); align-items: center; }
.icon-specimens .icon { width: 21px; height: 21px; }
.voice-card p { margin: 0 0 10px; font: 23px/1 var(--display); }
.voice-card small { color: var(--muted); font-size: 11px; line-height: 1.4; max-width: 42ch; }
.token-pair > div, .icon-system > div { min-width: 0; }
.spacing-rack, .shape-rack { display: grid; grid-template-columns: repeat(5, 1fr); }
.spacing-rack article, .shape-rack article { min-height: 150px; padding: 16px 12px 12px; border-right: 1px solid var(--line); display: grid; grid-template-rows: 1fr auto auto; align-items: end; }
.spacing-rack article:last-child, .shape-rack article:last-child { border-right: 0; }
.spacing-rack i { display: block; width: var(--size); height: var(--size); max-width: 48px; max-height: 48px; background: var(--signal); align-self: center; justify-self: center; }
.spacing-rack code, .shape-rack code { font: 11px var(--mono); }
.spacing-rack span, .shape-rack span { color: var(--muted); font-size: 11px; margin-top: 4px; }
.shape-rack { grid-template-columns: repeat(4, 1fr); }
.shape-rack i { width: 64px; height: 64px; align-self: center; justify-self: center; background: var(--pressed); border: 1px solid var(--line); }
.shape-tile { border-radius: var(--r-field); }
.shape-control { border-radius: var(--r-pill); }
.shape-card { border-radius: 0; }
.shape-round { border-radius: 50%; }
.token-note { color: var(--muted); font-size: 12px; margin: 22px 0 0; }
.token-note code { color: var(--ink); font: 11px var(--mono); }
.voice-system .voice-card { padding: 20px 20px 22px; background: var(--raised); }
@media (max-width: 900px) {
  /* The pill cannot wrap without stopping being a pill, so past the point where
     five segments fit it scrolls inside its own shape. Without this the last
     two views run off-screen and cannot be reached at all. */
  .prototype-bar { gap: 12px; }
  /* Centred, the pill would overflow both edges once the segments stop fitting,
     taking the mark off-screen on the left. Cap it to the bar and let the
     segments scroll inside it; the mark stays put. */
  .nav-pill { max-width: 100%; min-width: 0; }
  .nav-pill .prototype-mark { flex: none; }
  .view-switch { min-width: 0; overflow-x: auto; scrollbar-width: none;
    -ms-overflow-style: none; -webkit-overflow-scrolling: touch; }
  .view-switch::-webkit-scrollbar { display: none; }
  .view-switch button { flex: none; }

  .prototype-meta { display: none; }
  .stage { padding: 18px; align-items: flex-start; }
  .phone { height: calc(100vh - 100px); max-height: 844px; }
  .map-header { grid-template-columns: 1fr; gap: 10px; }
  .sitemap-layout { grid-template-columns: 1fr; }
  .identity-intro { grid-template-columns: 1fr; align-items: start; }
  .identity-section, .type-system { grid-template-columns: 1fr; }
  .identity-view { grid-template-columns: 150px minmax(0, 1fr); }
  .identity-content { padding-inline: 26px; }
  .spacing-rack { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 740px) {
  .view-switch { mask-image: linear-gradient(to right, #000 0, #000 90%, transparent 100%); }
}

@media (max-width: 520px) {
  .prototype-bar { padding: 9px 10px; }
  .prototype-mark .lockup { height: 15px; }
  .view-switch button { padding: 0 9px; }
  .stage { padding: 0; }
  .phone { width: 100%; height: calc(100vh - 55px); min-height: 600px; border: 0; border-radius: 0; }
  .screen { height: calc(100% - 76px); padding-top: 18px; }
  .map-view { padding: 28px 12px 60px; }
  .map-header h1 { font-size: 34px; }
  .identity-view { display: block; }
  .identity-subnav { top: 55px; height: auto; padding: 7px 10px; border-right: 0; border-bottom: 1px solid var(--line); overflow-x: auto; z-index: 10; }
  .identity-subnav > span { display: none; }
  .identity-subnav nav { display: flex; width: max-content; }
  .identity-subnav a { min-height: 32px; padding: 0 10px; font-size: 12px;
    display: flex; align-items: center; }
  .identity-subnav a.is-current::before { display: none; }
  .identity-subnav nav { gap: 0; }
  .identity-content { padding: 38px 16px 64px; }
  .identity-intro h1 { font-size: 58px; }
  .principle-list, .component-board { grid-template-columns: 1fr; }
  .color-rack { grid-template-columns: 1fr 1fr; }
  .color-rack article { min-height: 110px; border-right: 1px solid var(--line); }
  .spacing-rack, .shape-rack { grid-template-columns: 1fr 1fr; }
}

.screen.entering { animation: screen-in 280ms var(--ease); }
@keyframes screen-in {
  from { opacity: 0; transform: translateY(7px) scale(.995); }
  to { opacity: 1; transform: none; }
}
@keyframes letter-drift {
  from { transform: translate3d(-3px, -2px, 0) rotate(-1deg); }
  to { transform: translate3d(4px, 1px, 0) rotate(.7deg); }
}
@keyframes splash-focus {
  from { transform: scale(1.035); filter: contrast(1.06) blur(2px); }
  to { transform: scale(1); filter: contrast(1.06) blur(0); }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* Lab Grotesque Mono is now available; token specimens use it. */
code, .identity-view code, .type-specimen code { font-family: var(--mono); }

/* Hit areas reach 44px without changing what is drawn. */
.view-switch button::after,
.identity-subnav a::after { content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%); height: 32px; }

/* Photography — specimens run wide so the falloff is visible; captions follow
   the house rule for captions: monospace, all caps. */
.photo-lede { max-width: 46ch; margin: 0 0 22px; font-size: 15px; line-height: 1.5; color: var(--ink); }
/* The frames are the argument, so they run the full container width and cross
   back over the label column rather than sitting in the text measure. */
.photo-system .photo-canon, .photo-system .photo-specimens {
  margin-left: calc(-1 * (180px + 28px)); }
/* Specimens that do not meet the brief are marked, not quietly shown as if they do. */
.photo-specimens figure.is-offbrief img { opacity: 1; }
.photo-specimens figure.is-offbrief figcaption { color: var(--brand); }
.photo-canon { margin: 0 0 22px; }
.photo-canon img { display: block; width: 100%; height: auto; border-radius: 0; }
.photo-canon figcaption { margin-top: 8px; font-family: var(--sans); font-size: 10px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink); }
.photo-specimens { display: grid; grid-template-columns: 1fr 1fr; gap: var(--doc-gap); margin-bottom: 28px; }
.photo-specimens figure { margin: 0; }
.photo-specimens img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: 0; }
.photo-specimens figcaption { margin-top: 8px; font-family: var(--sans); font-size: 10px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--quiet); }
.photo-rules { display: grid; grid-template-columns: 1fr 1fr; column-gap: var(--doc-col-gap); }
.photo-rules article { padding: 14px 0; }
.photo-rules b { font-size: 13px; }
.photo-rules p { margin: 6px 0 0; color: var(--muted); font-size: 11px; line-height: 1.5; }

/* ---- Events: grouped by month, each event stating its own facts ---------
   Separation is a change of ground, not a rule. Metadata is mono. Every card
   declares the member's relationship to the event, which is the thing the row
   format could not say. */
.event-groups { display: flex; flex-direction: column; gap: 24px; }
.event-month { margin: 0 0 10px; font-family: var(--sans); font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.event-card { background: var(--raised); padding: 14px; display: grid;
  grid-template-columns: 38px 1fr; gap: 14px; margin-bottom: 8px; }
.event-card:last-child { margin-bottom: 0; }
.event-when { text-align: center; padding-top: 2px; }
.event-when b { display: block; font: 23px/1 var(--display); color: var(--ink); }
.event-when span { display: block; margin-top: 3px; font-family: var(--sans);
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--quiet); }
.event-body { min-width: 0; }
.event-time { margin: 0; font-family: var(--sans); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.event-title { display: block; width: 100%; margin: 5px 0 0; padding: 0; border: 0;
  background: none; text-align: left; font: 17px/1.15 var(--display);
  color: var(--ink); cursor: pointer; }
.event-venue { margin: 6px 0 0; font-family: var(--sans); font-size: 11px; color: var(--muted); }
.event-rel { margin: 10px 0 0; font-size: 12px; font-weight: 650; color: var(--ink); }
.event-rel.is-open { font-weight: 400; color: var(--muted); }
.event-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.event-actions button { min-height: 40px; padding: 0 16px; border: 0; border-radius: var(--r-pill);
  background: var(--gray); color: var(--ink); font-family: var(--sans); font-size: 11px;
  letter-spacing: .04em; cursor: pointer; transition: background-color 160ms ease; }
.event-actions button.is-primary { background: var(--brand); color: var(--off-white); }

/* ---- Mark + Grayscale (mirroring id-idler's section vocabulary) --------- */
.mark-specimens { display: grid; grid-template-columns: 1fr 1fr; gap: var(--doc-gap); margin-bottom: 24px; }
.mark-plate { display: grid; place-items: center; min-height: 132px; padding: 24px; background: var(--raised); color: var(--brand); }
.mark-plate.is-brand { background: var(--brand); color: var(--off-white); }
.mark-plate .lockup { height: 34px; }
.mark-rules { display: grid; grid-template-columns: 1fr 1fr; column-gap: var(--doc-col-gap); }
.mark-rules article { padding: 14px 0; }
.mark-rules b { font-size: 13px; }
.mark-rules p { margin: 6px 0 0; color: var(--muted); font-size: 11px; line-height: 1.5; }

.gray-rack { display: grid; grid-template-columns: repeat(6, 1fr); gap: 1px; }
.gray-rack article { display: flex; flex-direction: column; gap: 0; }
.gray-rack i { display: block; height: 76px; }
.gray-rack code { margin-top: 9px; font-family: var(--mono); font-size: 11px; color: var(--ink); }
.gray-rack span { margin-top: 3px; font-size: 11px; color: var(--quiet); line-height: 1.4; }

.token-group { margin: 0 0 12px; font-family: var(--sans); font-size: 11px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--quiet); }
.token-system .token-group:not(:first-child) { margin-top: 30px; }

/* ---- Accessibility · Composition · Motion ------------------------------ */
.ratio-rack { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--doc-gap); margin: 16px 0 26px; }
.ratio-rack article { padding: 12px 14px; background: var(--raised); display: flex; flex-direction: column; gap: 4px; }
.ratio-rack b { font: 19px/1 var(--display); font-variant-numeric: tabular-nums; color: var(--ink); }
.ratio-rack span { font-size: 11px; line-height: 1.4; color: var(--muted); }
.ratio-rack .is-pass b, .ratio-rack article.is-pass b { color: var(--ink); }
.ratio-rack article.is-fail { background: color-mix(in oklch, var(--brand) 8%, transparent); }
.ratio-rack article.is-fail b { color: var(--brand); }

.access-rules { display: grid; grid-template-columns: 1fr 1fr; column-gap: var(--doc-col-gap); }
.access-rules article { padding: 14px 0; }
.access-rules b { font-size: 15px; }
.access-rules p { margin: 6px 0 0; color: var(--muted); font-size: 13px; line-height: 1.5; }

.comp-rack { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--doc-gap); margin-bottom: 24px; }
.comp-rack article { padding: 12px 14px; background: var(--raised); display: flex; flex-direction: column; gap: 3px; }
.comp-rack b { font: 19px/1 var(--display); font-variant-numeric: tabular-nums; }
.comp-rack code { font-family: var(--mono); font-size: 11px; color: var(--ink); }
.comp-rack span { font-size: 11px; color: var(--muted); line-height: 1.4; }

/* numbered rail, standards-site convention: number and label, number quiet */
.identity-subnav a { font-variant-numeric: tabular-nums; }

/* Every section's body starts on the same line as its label. Bodies begin with
   different elements -- a rack, a note, a bare div -- each carrying its own top
   margin, so without this no two sections aligned the same way. */
.identity-section > :not(header) { margin-top: 0; }
.identity-section > :not(header) > :first-child { margin-top: 0; }
.identity-section > :not(header) > :first-child.token-note { margin-top: 0; }

/* ---- Token table (Atlassian naming convention, Maestro values) ---------- */
.tok-rack { display: flex; flex-direction: column; margin-bottom: 30px; }
.tok-rack article { display: grid; grid-template-columns: 200px minmax(112px, 132px) 132px 1fr;
  gap: var(--doc-gap); align-items: center; padding: 9px 0; }
.tok-rack code { font-family: var(--mono); font-size: 12px; color: var(--ink); }
.tok-rack i { font-style: normal; display: flex; align-items: center; white-space: nowrap; overflow: hidden; }
.tok-rack b { font-family: var(--mono); font-size: 11px; font-weight: 400;
  color: var(--muted); font-variant-numeric: tabular-nums; }
.tok-rack span:last-child { font-size: 12px; color: var(--muted); line-height: 1.4; }
.tok-chip { width: 26px; height: 18px; display: block; box-shadow: inset 0 0 0 1px var(--line); }
.tok-bar { height: 8px; display: block; background: var(--brand); }
.token-table .token-group:not(:first-of-type) { margin-top: 26px; }

/* ---- Controls ----------------------------------------------------------
   .is-hover / .is-active / .is-focus force a state so the documentation can
   show every one at once. Real interaction uses the pseudo-classes beside them. */
.btn { display: inline-flex; white-space: nowrap; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 22px; border: 0; border-radius: var(--r-pill);
  font-family: var(--sans); font-size: 13px; font-weight: 600; letter-spacing: .03em;
  cursor: pointer; transition: background-color 160ms ease, color 160ms ease; }
.btn:focus-visible, .btn.is-focus { outline: 1.5px solid var(--harmony); outline-offset: 2px; }

.btn--primary { background: var(--brand); color: var(--off-white); }
.btn--primary:hover, .btn--primary.is-hover { background: var(--brand-hovered); }
.btn--primary:active, .btn--primary.is-active { background: var(--brand-pressed); }

.btn--secondary { background: var(--gray); color: var(--ink); width: 100%; }
.btn--secondary:hover, .btn--secondary.is-hover { background: var(--gray-hovered); }
.btn--secondary:active, .btn--secondary.is-active { background: var(--gray-pressed); }

.btn--tertiary { background: transparent; color: var(--ink); padding: 0 12px; }
.btn--tertiary:hover, .btn--tertiary.is-hover { background: var(--gray); }
.btn--tertiary:active, .btn--tertiary.is-active { background: var(--gray-pressed); }

.btn:disabled, .btn.is-disabled { background: var(--raised); color: var(--quiet); cursor: not-allowed; }
.btn--tertiary:disabled, .btn--tertiary.is-disabled { background: transparent; }

.field { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.field label { font-family: var(--sans); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--quiet); }
.field input { min-height: 46px; width: 100%; padding: 0 16px; border: 0; border-radius: var(--r-field);
  background: var(--gray); color: var(--ink); font-family: var(--sans); font-size: 15px;
  transition: background-color 160ms ease, box-shadow 160ms ease; }
.field input::placeholder { color: var(--quiet); }
.field input:hover, .field input.is-hover { background: var(--gray-hovered); }
.field input:focus, .field input.is-focus { outline: 1.5px solid var(--harmony); outline-offset: 2px; background: var(--off-white); box-shadow: none; }
.field input:disabled, .field input.is-disabled { background: var(--raised); color: var(--quiet); cursor: not-allowed; }
.field.is-error input { box-shadow: inset 0 0 0 1px var(--brand); background: var(--off-white); }
.field .field-msg { font-size: 12px; color: var(--brand); }
.field-set { display: grid; gap: 14px; margin-top: 4px; }
.section-block:has(.field-set) .section-heading { margin-bottom: 16px; }

.ctl-matrix { display: flex; flex-direction: column; gap: 34px; }
.ctl-row { display: grid; grid-template-columns: 104px repeat(5, 1fr); gap: 20px; align-items: start; }
.ctl-row > .ctl-name { font-size: 12px; font-weight: 650; color: var(--ink); padding-top: 13px; }
.ctl-cell { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
.ctl-cell > :first-child { align-self: stretch; }
.ctl-cell .btn--tertiary, .ctl-cell .btn--primary { align-self: flex-start; }
.ctl-cell i { font-style: normal; font-family: var(--sans); font-size: 10px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--quiet); }
.ctl-cell .field { width: 100%; }

/* ---- About: one page, centered ---------------------------------------- */
/* About has no subnav rail, so there is nothing to align a left edge to. The
   reading column centres in the viewport instead: no rail offset on the view,
   and no max-width capping it off-centre. */
.about-view { padding: 70px var(--page-gutter) 96px;
  max-width: none; margin: 0; }
.about-page { max-width: 62ch; margin: 0 auto; }
.about-eyebrow { margin: 0 0 10px; font-family: var(--sans); font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.about-kicker { margin: 0 0 8px; font-family: var(--sans); font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.about-page h1 { margin: 0 0 30px; font: 58px/.94 var(--display); letter-spacing: -.03em; }
/* The whole page sits at the muted grey rather than near-black. It is a
   read-through document, not a surface anyone acts on, so nothing in it needs
   to rank above the rest. --muted is 9.44:1 on this ground, well past AA. */
.about-page, .about-page p, .about-page h1, .about-page h2, .about-page h3,
.about-spine li { color: var(--muted); }
.about-page p { margin: 0 0 20px; font-size: 17px; line-height: 1.6; }
.about-spine { margin: 4px 0 24px; padding: 0; list-style: none; counter-reset: q;
  display: grid; gap: 0; }
.about-spine li { counter-increment: q; margin: 0;
  display: grid; grid-template-columns: 32px 1fr; gap: 4px; align-items: baseline;
  padding: 11px 0; border-bottom: 1px solid var(--line);
  font-size: 17px; line-height: 1.4; }
.about-spine li:first-child { border-top: 1px solid var(--line); }
.about-spine li::before { content: counter(q, decimal-leading-zero);
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  letter-spacing: .08em; color: var(--quiet); }
.about-page h3 { margin: 30px 0 10px; font-family: var(--sans); font-size: 11px;
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--quiet); }
.about-page h2 { margin: 42px 0 14px; font: 22px/1.15 var(--display); font-weight: 400;
  letter-spacing: -.02em; }
.about-owners { margin: 18px 0 22px; border-top: 1px solid var(--line); }
.about-owners > div { display: flex; justify-content: space-between; align-items: baseline;
  gap: 18px; padding: 11px 0; border-bottom: 1px solid var(--line); }
.about-owners dt { font-size: 13px; }
.about-owners dd { margin: 0; flex: none; font-family: var(--mono); font-size: 11px;
  color: var(--muted); font-variant-numeric: tabular-nums; }
.about-page p.about-foot { margin-top: 34px; font-size: 13px; line-height: 1.6; color: var(--quiet); }

/* ---- Register R2: components added for the P0 items -------------------- */
.patron-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: center;
  width: 100%; border: 0; border-radius: 0; padding: 12px; margin-bottom: 14px;
  background: var(--raised); text-align: left; cursor: pointer; }
.patron-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.patron-body strong { font-size: 13px; }
.patron-body small { font-size: 12px; color: var(--muted); }
.patron-meta { font-family: var(--sans); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--brand); }

.announce-rail { display: flex; gap: 8px; overflow-x: auto; margin-bottom: 14px;
  scrollbar-width: none; }
.announce-rail::-webkit-scrollbar { display: none; }
.announce { flex: 0 0 78%; text-align: left; border: 0; border-radius: 0; cursor: pointer;
  background: var(--gray); padding: 11px 12px; display: flex; flex-direction: column; gap: 3px; }
.announce em { font-style: normal; font-size: 11px; font-weight: 700; }
.announce span { font-size: 12px; line-height: 1.4; color: var(--muted); }

.own-line { margin: 8px 0 0; font-size: 11px; font-weight: 650; }
.own-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.own-actions .btn { min-height: 36px; font-size: 12px; padding: 0 12px; width: auto; }

.filter-row { display: flex; gap: 8px; overflow-x: auto; margin-bottom: 10px;
  scrollbar-width: none;
  /* The row bleeds into both gutters and fades across exactly those 18px, so a
     chip is only ever dimmed once it has scrolled out of the content column.
     A percentage stop cannot do this: it moves with the row's width and starts
     eating the chip that is still in view. */
  padding-left: 18px; padding-right: 18px; margin-left: -18px; margin-right: -18px;
  mask-image: linear-gradient(to right, transparent 0, #000 18px,
    #000 calc(100% - 18px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px,
    #000 calc(100% - 18px), transparent 100%); }
.filter-row::-webkit-scrollbar { display: none; }
.chip { flex: none; min-height: 32px; padding: 0 13px; border: 0; border-radius: var(--r-pill); cursor: pointer;
  background: var(--gray); color: var(--muted); font-size: 12px; display: inline-flex;
  align-items: center; gap: 6px; white-space: nowrap; }
.chip.is-on { background: var(--ink); color: var(--off-white); }
.chip .icon { width: 13px; height: 13px; }
.result-count { margin: 0 0 16px; font-family: var(--sans); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--quiet); }
.linkish { border: 0; background: none; padding: 0; cursor: pointer; color: var(--brand);
  font: inherit; text-decoration: underline; text-underline-offset: 2px; }
.event-card.is-unavailable .event-title { color: var(--muted); }

.plan-strip { display: flex; justify-content: space-between; align-items: center; gap: 12px;
  background: var(--raised); padding: 12px; }
.plan-strip b { display: block; font-size: 14px; }
.plan-strip small { font-size: 12px; color: var(--muted); }
.plan-flag { font-family: var(--sans); font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand); text-align: right; }

.incl-list { list-style: none; margin: 0; padding: 0; }
/* Two up. At half width the title and its state cannot sit on one line, so the
   state moves above the title and reads as the card's eyebrow. */
.incl-list { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; align-items: stretch; }
.incl-list li { display: flex; flex-direction: column; justify-content: flex-start; gap: 6px;
  padding: 12px 13px 13px; background: var(--gray); border: 0;
  border-radius: 14px; font-size: 14px; line-height: 1.25; }
.incl-list li > span { min-width: 0; }
.incl-list em { order: -1; font-style: normal; font-family: var(--sans); font-size: 10.5px;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.incl-list em.is-done { color: var(--brand); }
.incl-list em.is-open { color: var(--quiet); }

.renew-block { background: var(--raised); padding: 14px; }
.renew-block p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--muted); }
.renew-block b { color: var(--ink); }
.renew-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.renew-actions .btn { min-height: 38px; font-size: 12px; width: auto; padding: 0 16px; }

.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.stat-row > div { background: var(--raised); padding: 12px; display: flex; flex-direction: column; gap: 3px; }
.stat-row b { font: 24px/1 var(--display); }
.stat-row small { font-size: 11px; color: var(--muted); }

.journey { list-style: none; margin: 0; padding: 0; }
/* The season read as a strip rather than a list: each stop is a column, the
   month above the day, and the one you are on takes a filled chip. The show
   names are already listed under Included shows, so repeating them here was
   the same six rows twice. */
.journey { display: grid; grid-template-columns: repeat(6, 1fr); gap: 2px;
  margin: 0; padding: 0; list-style: none; }
.journey li { display: grid; justify-items: center; gap: 6px; padding: 8px 2px;
  border-radius: 10px; }
.journey em { font-style: normal; font-family: var(--sans); font-size: 10.5px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--quiet); }
.journey b { font-family: var(--sans); font-size: 15px; font-weight: 500;
  color: var(--quiet); font-variant-numeric: tabular-nums; }
.journey li.is-done b { color: var(--ink); }
.journey li.is-now { background: var(--brand); }
.journey li.is-now em { color: rgb(255 255 255 / .62); }
.journey li.is-now b { color: var(--off-white); font-weight: 700; }
.journey li.is-unset b { font-size: 12px; }

.kv-list > div { display: flex; justify-content: space-between; align-items: baseline; gap: 14px;
  padding: 10px 12px; background: var(--raised); margin-bottom: 4px; font-size: 14px; }
.kv-list span { color: var(--muted); }
.kv-list b { font-weight: 600; text-align: right; }
.kv-list b.is-open { color: var(--quiet); font-weight: 400; }

.tok-rack code.is-copyable { cursor: pointer; }
.tok-rack code.is-copyable:hover { color: var(--brand); }
.tok-rack code.is-copyable:focus-visible { outline: 1.5px solid var(--harmony); outline-offset: 2px; }
.tok-rack code.is-copied { color: var(--brand); }

/* ---- Home, restructured -------------------------------------------------
   Surfaces are unfilled. Separation comes from space and the occasional
   hairline instead of a stack of gray blocks. Imagery runs edge to edge. */
.patron-card { background: none; padding: 14px 0 18px; margin-bottom: 0; }

.announce-rail { gap: 0; margin: 10px 0 0; }
/* A count, not an unread count. Ink rather than burgundy: burgundy is reserved
   for the primary action and the active tab, and a badge is neither. */
/* The mark and the bell sit on their own bar above the greeting, so the header
   holds one row of chrome and one row of address rather than mixing them. */
.app-header:has(.app-bar) { display: block; }
.app-bar { display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 22px; }
.app-bar .lockup { height: 15px; color: var(--brand); }
/* A bottom drawer inside the device, not the page: it is absolute to the phone
   so it sits over the screen the way a sheet does on iOS. Opens on the trigger,
   closes on the scrim, on Escape, and on every action inside it. */
.sheet { position: absolute; inset: 0; z-index: 40; display: flex; align-items: flex-end; }
.sheet-scrim { position: absolute; inset: 0; background: rgb(16 16 16 / .40);
  backdrop-filter: blur(2px); }
.sheet-panel { position: relative; width: 100%; background: var(--off-white);
  border-radius: 18px 18px 0 0; padding: 10px 18px 26px; animation: sheet-in 320ms var(--ease); }
.sheet-grip { width: 36px; height: 4px; border-radius: var(--r-pill); background: var(--line);
  margin: 0 auto 16px; }
.sheet-eyebrow { margin: 0 0 6px; font-family: var(--sans); font-size: 11px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--quiet); }
.sheet-panel h2 { margin: 0 0 8px; font: 24px/1.1 var(--display); font-weight: 400;
  letter-spacing: -.02em; }
.sheet-body { margin: 0 0 16px; font-size: 14px; line-height: 1.5; color: var(--muted); }
.sheet-actions { display: grid; gap: 8px; margin-top: 14px; }
.sheet-actions .btn { width: 100%; min-height: 46px; }
.sheet-foot { margin: 14px 0 0; font-size: 13px; color: var(--muted); }
.attended-trigger { margin-top: 18px; width: 100%; min-height: 44px; }
@keyframes sheet-in { from { transform: translateY(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { .sheet-panel { animation: none; } }


/* A tray, not a jump: the review asked for the carousel to open in place. Every
   opener needs a closer, so this one closes on outside click and on Escape. */
/* The full-screen frost, used for the member pass. It is the one thing a member
   holds up to another person, so it gets the whole device: no chrome, no nav,
   the facts large, one pill to dismiss. Announcements did not need this much
   screen and now open their own list instead. */
.stage-over { position: absolute; inset: 0; z-index: 45;
  background: var(--frost-tint);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
  animation: stage-fade 240ms var(--ease); }
@keyframes stage-fade { from { opacity: 0; } to { opacity: 1; } }
.stage-inner { position: absolute; inset: 0; display: flex; flex-direction: column;
  justify-content: center; padding: 0 26px 26px; color: var(--ink); }
.stage-eyebrow { margin: 0 0 10px; font-family: var(--sans); font-size: 12px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.stage-name { margin: 0 0 26px; font: 38px/1.02 var(--display); font-weight: 400;
  letter-spacing: -.03em; }
.stage-facts { display: grid; gap: 16px; margin: 0; padding-top: 18px;
  border-top: 1px solid var(--line); }
.stage-facts div { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.stage-facts dt { font-family: var(--sans); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--quiet); }
.stage-facts dd { margin: 0; font-family: var(--mono); font-size: 15px; text-align: right; }
.stage-note { margin: 22px 0 0; font-size: 13px; line-height: 1.5;
  color: var(--muted); max-width: 34ch; }
.stage-close { position: absolute; left: 50%; bottom: calc(var(--safe-bottom) + 8px);
  transform: translateX(-50%); display: inline-flex; align-items: center; gap: 8px;
  min-height: 46px; padding: 0 22px; border: 0; border-radius: var(--r-pill);
  background: var(--ink); color: var(--off-white); font-family: var(--sans);
  font-size: 14px; font-weight: 600; }
.stage-close .icon { width: 15px; height: 15px; }
.show-pass { width: 100%; margin-bottom: 16px; }
@media (prefers-reduced-motion: reduce) { .stage-over { animation: none; } }

.bell { position: relative; border: 0; background: none; padding: 0; cursor: pointer;
  width: 34px; height: 34px; display: grid; place-items: center; margin-right: -6px; }
.bell .icon { width: 19px; height: 19px; }
.bell .count-badge { position: absolute; top: -1px; right: -3px; min-width: 15px;
  height: 15px; font-size: 11px; }

/* Home leads with the image running to the very top, chrome floating on it: the
   mark as a pill and the bell as a round button, the way the reference does. The
   screen drops its top padding so nothing sits above the photograph. */
/* The hero carries the media ground, not the screen. Putting it on the screen
   darkened everything below the photograph and turned the greeting and the rail
   headings into dark text on a dark field. */
.screen:has(> .hero-chrome) { padding-top: 0; }
/* Two fifths of the device. At a quarter the eyebrow ran under the logo pill;
   this gives the type its full size back and still leaves the majority of the
   screen to the membership content below. */
.hero { background: var(--media-ground);
  height: calc(min(874px, 100vh - 78px) * 0.40); min-height: 300px; }
.hero { position: relative; margin: 0 -18px; }
.hero-img { display: block; width: 100%; height: 100%; object-fit: cover;
  object-position: center 57%; background: var(--media-ground); }
.hero-veil { position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgb(10 8 4 / .82) 0%, rgb(10 8 4 / .60) 30%,
    rgb(10 8 4 / .12) 62%, rgb(10 8 4 / .24) 100%); }
/* The chrome sticks for the whole screen rather than riding the hero, so the
   mark and the bell stay reachable at any scroll position. It sits above the
   content and pulls the hero back up underneath itself. */
.hero-chrome { position: sticky; top: 0; z-index: 20; display: flex;
  align-items: center; justify-content: space-between;
  padding: var(--safe-top) 0 12px;
  /* Pull the hero fully under the chrome. At 64 it only covered part of the
     chrome's 101px and left a white band above the photograph. No negative
     horizontal margin: .screen scrolls, so it clips anything outside its box,
     which was cutting the left edge off the mark pill. */
  margin: 0 0 calc(-1 * (var(--safe-top) + 52px));
  pointer-events: none; }
.hero-chrome > * { pointer-events: auto; }
.hero-chrome > * { pointer-events: auto; }
/* Chrome runs dark on the photograph, the way the tab bar does, so the two
   floating objects on screen belong to the same family. Translucent ink over a
   blur rather than flat black, so the image still moves under them. */
.mark-pill { display: inline-flex; align-items: center; height: 40px; padding: 0 18px;
  border-radius: var(--r-pill); color: var(--off-white);
  background: color-mix(in srgb, var(--off-black) 82%, transparent);
  backdrop-filter: blur(22px) saturate(170%);
  -webkit-backdrop-filter: blur(22px) saturate(170%); }
.mark-pill .lockup { height: 13px; }
.round-btn { position: relative; width: 40px; height: 40px; border: 0;
  border-radius: var(--r-pill); color: var(--off-white);
  background: color-mix(in srgb, var(--off-black) 82%, transparent);
  backdrop-filter: blur(22px) saturate(170%);
  -webkit-backdrop-filter: blur(22px) saturate(170%);
  display: grid; place-items: center; }
.round-btn .icon { width: 18px; height: 18px; }
/* The alert is the one bright thing up here, which is what makes it read as an
   alert rather than as another piece of chrome. */
.round-btn .count-badge { position: absolute; top: -2px; right: -2px; min-width: 17px;
  height: 17px; font-size: 9px; background: var(--off-white); color: var(--off-black);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--off-black) 82%, transparent); }
.hero-body { position: absolute; left: 18px; right: 18px; bottom: 16px; }
.hero-when { margin: 0 0 8px; font-family: var(--sans); font-size: 12px;
  letter-spacing: .1em; text-transform: uppercase; color: rgb(255 255 255 / .82); }
.hero-title { margin: 0; font: 30px/1.05 var(--display); font-weight: 400;
  letter-spacing: -.028em; color: var(--off-white); }
.hero-where { margin: 8px 0 0; font-size: 13px; color: rgb(255 255 255 / .82); }
.hero-cta { display: inline-flex; align-items: center; justify-content: center;
  width: auto; min-height: 42px; padding: 0 24px; margin: 14px 0 0; border: 0;
  border-radius: var(--r-pill); color: var(--ink);
  background: color-mix(in srgb, var(--off-white) 88%, transparent);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  font-family: var(--sans); font-size: 13px; font-weight: 600; cursor: pointer; }
.home-date { margin: 20px 0 4px; font-family: var(--sans); font-size: 12px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.home-greet { margin: 0 0 4px; font: 24px/1.1 var(--display); font-weight: 400;
  letter-spacing: -.02em; color: var(--quiet); }

/* One statement of what they have: the package by name, the tier and tenure
   under it, then the four facts they would otherwise open a tab to find. The
   review asked for the subscription to be named specifically rather than left
   as a tier word. */
/* Memberships are a rail: a patron holds more than one relationship with the
   organisation and each is its own card. Bleeds right so the next card peeks,
   which is how the rail says there is more without a control saying so. */

/* Mandatory snap, and the snap line sits on the gutter, so a card always lands
   on position instead of stopping wherever the flick ran out. align-items keeps
   every card the height of the tallest. */
.member-rail { display: flex; align-items: stretch; gap: 10px; overflow-x: auto;
  overflow-y: hidden; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-padding-left: 0;
  scrollbar-width: none; margin: 0 -18px 0 0; padding: 2px 18px 4px 0;
  width: calc(100% + 18px); }
.member-rail::-webkit-scrollbar { display: none; }
/* Fades where it scrolls, so the next card is a peek rather than a cut. Same
   treatment as the filter row and the announcement rail. */
.member-rail { mask-image: linear-gradient(to right, #000 0, #000 68%, rgb(0 0 0 / .25) 92%, transparent 100%);
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 68%, rgb(0 0 0 / .25) 92%, transparent 100%); }
/* A column, so the facts row can be pushed to the foot of every card and the
   boards line up across the rail whatever length the title above them ran to. */
.member-card { flex: 0 0 82%; scroll-snap-align: start; display: flex;
  flex-direction: column; text-align: left;
  border: 0; border-radius: 14px; background: var(--gray);
  padding: 15px 16px 17px; margin: 0; cursor: pointer; }
/* The issuer and the state share the top line, the way they do on a physical
   card: who issued it on the left, whether it is live on the right. The type
   of membership drops to its own line beneath, so the three labels sit in
   three ranks instead of competing across one row.

   The issuer is set as type. No mark exists for these institutions in the
   build, and drawing an approximation of a real logo is worse than naming it;
   the real asset should come from the client. */
/* The issuer's name wraps to two lines on some cards and one on others, which
   put the type, the title and the board at a different height on each. The name
   holds a two-line box on every card, so what follows starts at one height
   whatever the institution is called. */
.member-head { display: flex; justify-content: space-between; align-items: flex-start;
  gap: 12px; }
.member-head .member-state { margin-top: 1px; }
.member-issuer { display: grid; gap: 1px; }
.member-issuer b { font-family: var(--sans); font-size: 13px; font-weight: 750;
  letter-spacing: -.01em; color: var(--ink); }
.member-issuer em { font-style: normal; font-family: var(--sans); font-size: 8.5px;
  font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
  color: oklch(0.6700 0 0); line-height: 1.25; min-height: 2.5em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.member-kicker { display: block; margin-top: 15px; font-family: var(--sans);
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--quiet); }
/* One hue for one state. Live takes the lemon; a cadence and a count are not
   states, so they stay neutral. */
.member-state { display: inline-flex; align-items: center; flex: none;
  padding: 4px 10px; border-radius: var(--r-pill); background: var(--pressed);
  font-family: var(--sans); font-size: 10.5px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); font-weight: 700;
  white-space: nowrap; }
.member-state.is-live { background: color-mix(in srgb, var(--accent) 62%, white);
  color: oklch(0.4200 0.0640 108); }
.member-name { display: block; margin: 5px 0 4px; font: 21px/1.08 var(--display);
  font-weight: 400; letter-spacing: -.02em; }
.member-meta { display: block; font-size: 13px; color: var(--muted); }
.member-board { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px;
  margin-top: auto; padding-top: 16px; }
.member-board > span { display: block; min-width: 0; }
.member-board em { display: block; font-family: var(--sans); font-size: 10.5px;
  font-style: normal; letter-spacing: .1em; text-transform: uppercase; color: var(--quiet); }
.member-board b { display: block; margin-top: 4px; font-size: 14px; font-weight: 400;
  color: var(--ink); }


/* The three subscription blocks are one thing, so they nest inside a module
   rather than sitting in the section stack as peers of Household and Season
   journey. The module carries the outer heading; each block carries its own. */
.sub-module { margin-top: 18px; }
.sub-module + .sub-module { margin-top: 26px; }
.sub-module-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 14px; margin-bottom: 8px; }
.sub-module-head h3 { margin: 0; font-family: var(--sans); font-size: 14px;
  font-weight: 650; letter-spacing: -.005em; text-transform: none; color: var(--ink); }
.sub-module-head span { color: var(--quiet); font-size: 12px; }

/* Grouped by when it matters rather than listed flat, and built on the same
   icon-row the settings and benefits lists use, so a notice reads like every
   other row in the app. No unread state: the register lists read and expiry
   behaviour as undefined and blocked on the CMS. */
.notice-list { border-top: 0; }
.notice-item:last-child { border-bottom: 0; }
.notice-item { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto;
  align-items: start; gap: 12px; width: 100%; text-align: left; border: 0;
  border-bottom: 1px solid var(--line); background: none; padding: 15px 0;
  cursor: pointer; }
.notice-item.is-static { cursor: default; }
.notice-item:not(.is-static):hover { background: var(--raised); }
.notice-ico { width: 26px; height: 26px; display: grid; place-items: center;
  background: var(--pressed); color: var(--ink); }
.notice-ico .icon { width: 14px; height: 14px; }
.notice-copy { display: grid; gap: 3px; min-width: 0; }
.notice-copy b { font-size: 15px; }
.notice-copy span { font-size: 13px; line-height: 1.45; color: var(--muted); }
.notice-item .icon-chevron { align-self: center; }

.section-heading h2 .linkish { font: inherit; letter-spacing: inherit;
  text-transform: inherit; color: inherit; padding: 0; text-decoration: none;
  display: inline-flex; align-items: center; gap: 5px; }
.section-heading h2 .linkish::after { content: ""; width: 5px; height: 5px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); opacity: .55; }
.section-heading h2 .linkish:hover { color: var(--ink); }
.feature-art { border: 0; padding: 0; cursor: pointer; width: 100%; }

/* After the reference: an image of the night, then what it is, when, where and
   what you hold. No rules between rows; the thumbnail already starts each one.
   The date takes the brand hue rather than the lemon, because this list sits on
   white where lemon is unreadable. */
.held-list { list-style: none; margin: 0; padding: 0; border-top: 0;
  display: grid; gap: 2px; }
.held-list li { display: block; padding: 0; border: 0; }
.held-list li > button { display: grid; grid-template-columns: 68px 1fr 16px;
  gap: 14px; align-items: center; width: 100%; padding: 8px 0; border: 0;
  background: none; text-align: left; cursor: pointer; }
.held-thumb { width: 68px; height: 68px; border-radius: 5px; object-fit: cover;
  background: var(--media-ground); }
.held-copy { display: grid; gap: 2px; min-width: 0; }
.held-list b { font-family: var(--sans); font-size: 14px; font-weight: 650;
  line-height: 1.25; letter-spacing: -.005em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.held-list em { font-style: normal; font-family: var(--sans); font-size: 12px;
  font-weight: 600; color: var(--brand); }
/* One line for where, truncating rather than wrapping, so every row is the
   same height and the column of thumbnails stays even. */
.held-list small { color: var(--muted); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.held-list .icon-chevron { color: var(--quiet); align-self: center; }
.count-badge { min-width: 17px; height: 17px; padding: 0 5px; border-radius: var(--r-pill);
  background: var(--ink); color: var(--off-white); font-family: var(--sans);
  font-size: 11px; font-weight: 600; display: inline-flex; align-items: center;
  justify-content: center; font-variant-numeric: tabular-nums; letter-spacing: 0; }
.section-heading .count-badge { min-width: 17px; height: 17px; padding: 0 5px; border-radius: var(--r-pill);
  background: var(--ink); color: var(--off-white); font-family: var(--sans);
  font-size: 11px; font-weight: 600; display: inline-flex; align-items: center;
  justify-content: center; font-variant-numeric: tabular-nums; letter-spacing: 0; }
.announce { flex: 0 0 82%; background: none; padding: 0 14px 0 0; }
.announce em { font-size: 11px; }



/* label column, the way the reference sets specs */
.section-heading h2 { font-family: var(--sans); font-size: 12px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); }
.section-heading span { font-size: 12px; }

/* The ask, the assist, then the utilities. Weight comes from the documented
   button atoms — filled, gray, unfilled — not from four underlined links of
   equal weight arranged as a keypad. */
.own-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 18px; }
.own-actions .btn { min-height: 46px; font-size: 13px; width: 100%; }
.own-actions .btn--tertiary { min-height: 40px; box-shadow: inset 0 0 0 1px var(--line); }

.action-row { background: none; border: 0; padding: 16px 0 0; }

/* ---- Stylization carried to the rest of the app -------------------------
   Content surfaces are unfilled and parted by a hairline. Controls keep a fill
   so they still read as pressable, and the pass card stays a solid object. */
.event-card { background: none; padding: 18px 0; margin-bottom: 0;
  border-bottom: 1px solid var(--line); }
.event-card:last-child { border-bottom: 0; }
.event-groups { gap: 8px; }
.event-month { margin: 18px 0 2px; }
.event-actions { gap: 18px; margin-top: 14px; }
.event-actions button { background: none; padding: 0; min-height: 32px;
  text-decoration: underline; text-underline-offset: 3px; }
.event-actions button.is-primary { background: var(--brand); color: var(--off-white);
  padding: 0 18px; min-height: 44px; text-decoration: none; }

.plan-strip { background: none; padding: 14px 0 18px; }

.renew-block { background: var(--gray); border-radius: 14px; padding: 15px 16px 16px; }
.renew-actions .btn--tertiary { text-decoration: underline; text-underline-offset: 3px; }

.stat-row { gap: 0; }
.stat-row > div { background: none; padding: 14px 0; border-bottom: 1px solid var(--line); }
.kv-list > div { background: none; padding: 13px 0; margin-bottom: 0;
  border-bottom: 1px solid var(--line); }
.kv-list > div:last-child { border-bottom: 0; }

.support-note { background: none; }
.profile-block { padding-bottom: 22px; }

/* ---- Floating navigation ------------------------------------------------
   The bar lifts off the screen and content passes beneath it, so the blur has
   something to act on. Kept square: the system has no corner radii, and a pill
   would be the only rounded object in the build. */
.bottom-nav {
  left: 14px;
  right: 14px;
  bottom: 14px;
  min-height: 64px;
  background: color-mix(in oklch, var(--off-white) 78%, transparent);
  backdrop-filter: blur(26px) saturate(150%);
  -webkit-backdrop-filter: blur(26px) saturate(150%);
  box-shadow: 0 12px 38px rgb(16 16 16 / .12), inset 0 0 0 1px oklch(0.173 0 0 / .10);
  padding: 0 0 env(safe-area-inset-bottom, 0px);
}
.bottom-nav button { font-size: 12px; gap: 4px; }
.bottom-nav button .icon { width: 21px; height: 21px; }
/* content clears the floating bar rather than running under its edge */
.screen { padding-bottom: 104px; }

/* Content dissolves under the floating pill instead of meeting it on a hard
   line. A blur, not a wash: the earlier white gradient painted a slab across the
   full-bleed hero. Masked so the blur ramps in rather than starting on an edge,
   which would just move the hard line up. */
.phone::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 104px; pointer-events: none; z-index: 4;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  mask-image: linear-gradient(to bottom, transparent 0%, rgb(0 0 0 / .35) 34%, #000 72%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgb(0 0 0 / .35) 34%, #000 72%); }
/* The splash has no navigation, so it needs no dissolve. */
.phone:has(.splash-screen:not([hidden]))::after { display: none; }


/* ---- Navigation, pill ---------------------------------------------------
   Text only, content width, centered, floating over content. The current
   destination is a filled segment inside the pill rather than a color change.
   The one rounded object in the build, taken from the reference deliberately. */
.bottom-nav {
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  bottom: 18px;
  width: max-content;
  max-width: calc(100% - 24px);
  min-height: 0;
  display: flex;
  gap: 2px;
  padding: 5px;
  border-radius: var(--r-pill);
  background: color-mix(in oklch, var(--ink) 82%, transparent);
  backdrop-filter: blur(26px) saturate(150%);
  -webkit-backdrop-filter: blur(26px) saturate(150%);
  box-shadow: 0 14px 40px rgb(16 16 16 / .24);
}
.bottom-nav button {
  flex: 0 0 auto;
  min-height: 40px;
  padding: 0 16px;
  border-radius: var(--r-pill);
  gap: 0;
  flex-direction: row;
  font-size: 14px;
  font-weight: 500;
  color: color-mix(in oklch, var(--off-white) 72%, transparent);
  white-space: nowrap;
}
.bottom-nav button .icon { display: none; }
.bottom-nav button.is-active {
  background: color-mix(in oklch, var(--off-white) 22%, transparent);
  color: var(--off-white);
  font-weight: 600;
}
.bottom-nav button:hover { color: var(--off-white); }

/* ---- Tonight's event as a card ------------------------------------------
   The one contained object on Home. Image sits inside the card rather than
   bleeding, and the actions belong to it. Square, hairline edge, no fill, so
   it stays an object without reintroducing a gray block. */
.feature {
  margin-top: 22px;
  border: 1px solid var(--line);
  background: var(--off-white);
  overflow: hidden;
}
.feature-art { margin: 0; height: 208px; }
.feature-copy { padding: 16px 16px 18px; }
.feature-copy h2 { font-size: 22px; margin: 2px 0 6px; }
.event-id { left: 14px; top: 13px; }

/* The foot fade exists to dissolve content under the floating pill. The splash
   has no navigation, so it has no fade either, and the frame runs edge to edge. */
.splash-screen { z-index: 6; }

/* .splash-screen also carries .screen, so it was inheriting the content-screen
   height and padding meant for scrolling views. It fills the device instead. */
.screen.splash-screen,
.splash-screen { inset: 0; width: 100%; height: 100%; padding: 0; }
.splash-screen > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* Pill fills its container rather than hugging the labels, with the four
   destinations sharing the width evenly. Denser glass. */
.bottom-nav {
  left: 14px;
  right: 14px;
  transform: none;
  width: auto;
  max-width: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  padding: 6px;
  background: color-mix(in oklch, var(--ink) 90%, transparent);
  backdrop-filter: blur(40px) saturate(170%);
  -webkit-backdrop-filter: blur(40px) saturate(170%);
  box-shadow: 0 16px 44px rgb(16 16 16 / .40);
}
/* The pill clears the home indicator, and content clears the pill. Both are
   derived from one safe-area token rather than a hand-tuned offset. */
.bottom-nav { bottom: var(--safe-bottom); }
.screen { padding-bottom: calc(104px + var(--safe-bottom)); }
.bottom-nav button { padding: 0 8px; min-height: 44px; font-size: 14px; }
/* content clears the pill: 44 + 12 padding + 18 inset + air */
.screen { padding-bottom: 112px; }

/* Dividers run the full width of the device while their text stays on the
   gutter, so a rule never stops short of the edge it is meant to reach. Only
   list peers are in here now — the one-off blocks are parted by space, and a
   width with no style left behind would resurface the moment one was set. */
.kv-list > div,
.stat-row > div { border-bottom-width: 1px; }

.kv-list > div, .stat-row > div,
.settings-list button, .benefit-list button,
.settings-list, .benefit-list, .held-list, .notice-list {
  margin-left: -18px;
  margin-right: -18px;
  padding-left: 18px;
  padding-right: 18px;
}
.stat-row { grid-template-columns: repeat(3, 1fr); }
.stat-row > div { margin: 0; padding-left: 0; padding-right: 0; }
.stat-row > div:first-child { margin-left: -18px; padding-left: 18px; }
.stat-row > div:last-child { margin-right: -18px; padding-right: 18px; }

/* the rail peeks the next card rather than looking clipped */
.announce-rail { margin-left: -18px; margin-right: -18px; padding-left: 18px;
  scroll-snap-type: x proximity; }
.announce { flex: 0 0 74%; scroll-snap-align: start; margin-right: 14px;
  padding-left: 0; padding-right: 0; }
.announce:last-child { margin-right: 18px; }

/* width:100% plus a negative margin shifts an element; it does not widen it.
   These need to grow by the gutter on both sides to reach the device edge. */
.kv-list > div, .stat-row > div,
.settings-list button, .benefit-list button,
.settings-list, .benefit-list, .held-list, .notice-list {
  width: calc(100% + 36px);
  box-sizing: border-box;
}
.announce-rail { width: calc(100% + 36px); }

/* The rail should bleed right so the next card peeks, and stay on the gutter at
   the left. The earlier negative left margin pushed its first card off-screen. */
.announce-rail { margin-left: 0; margin-right: -18px; padding-left: 0;
  width: calc(100% + 18px); }
.announce { padding: 14px 0; margin-right: 14px; }
.announce:last-child { margin-right: 18px; }

/* Controls are grey-filled and the documentation page is grey, so the default
   state vanished while hover showed only because hover is darker. The specimen
   sits on white, which is the ground these controls actually appear on. */
.ctl-matrix { background: var(--off-white); padding: 22px 20px; }
.ctl-row { align-items: start; }
.ctl-cell { justify-content: flex-start; }
.ctl-cell > :first-child { align-self: stretch; }
/* tertiary is a text button: keep it flush left so it does not read as a
   mis-set filled button next to the others */
.ctl-cell .btn--tertiary { align-self: flex-start; }
.ctl-cell .field { width: 100%; }
.ctl-cell .field input { width: 100%; }

/* ---- Composition, drawn ------------------------------------------------- */
.map-plate { background: var(--off-white); padding: 20px; margin-bottom: 26px; }
.map-plate p { margin: 10px 0 0; font-size: 11px; color: var(--muted); line-height: 1.5; }
.map-plate code { font-family: var(--mono); font-size: 11px; color: var(--ink); }

.map-doc-grid { display: grid; grid-template-columns: 180px 28px 1fr; height: 64px; }
.map-col { display: grid; place-items: center; font-size: 10px; color: var(--quiet);
  box-shadow: inset 0 0 0 1px var(--line); }
.map-col.is-label { background: color-mix(in oklch, var(--brand) 9%, transparent); }
.map-gutter { display: grid; place-items: center; }
.map-gutter i { display: block; width: 1px; height: 100%;
  background: repeating-linear-gradient(var(--line) 0 4px, transparent 4px 8px); }
.map-dims { display: grid; grid-template-columns: 180px 28px 1fr; margin-top: 6px; }
.map-dims span { font-family: var(--mono); font-size: 9px; color: var(--quiet); text-align: center; }

.map-two { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
.map-gap-demo { display: flex; align-items: stretch; height: 40px; }
.map-gap-demo i { flex: 1; box-shadow: inset 0 0 0 1px var(--line); }
.map-gap-demo i.wide { flex: 1; }
.map-gap-demo b { background: color-mix(in oklch, var(--brand) 16%, transparent); }

.map-tree { position: relative; padding-top: 22px; }
.map-tree-rail { position: absolute; top: 10px; left: calc((100% - 60px) / 8);
  right: calc((100% - 60px) / 8); border-top: 1px solid var(--brand); }
.map-tree-rail::before, .map-tree-rail::after { content: ""; position: absolute; top: -3px;
  width: 5px; height: 5px; background: var(--brand); }
.map-tree-rail::before { left: -2px; } .map-tree-rail::after { right: -2px; }
.map-tree-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.map-tree-cols span { height: 46px; box-shadow: inset 0 0 0 1px var(--line); position: relative; }
.map-tree-cols span::before { content: ""; position: absolute; left: 50%; top: -12px;
  height: 12px; border-left: 1px solid var(--brand); }
.map-note { max-width: 68ch; }

/* ---- Member pass, rebuilt as a credential -------------------------------
   The facts carry the card, because they are what a member reads out when a
   scanner fails. The code zone is honest about waiting on the credential
   system rather than drawing a shape that pretends to be scannable. */
.pass-card { background: var(--ink); color: white; border-radius: 16px; overflow: hidden;
  display: flex; flex-direction: column; min-height: 0; padding: 20px; gap: 0; }
.pass-top { display: flex; align-items: center; justify-content: space-between;
  font-family: var(--sans); font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; }
.pass-top span { opacity: .62; }
.pass-holder { margin-top: 26px; }
.pass-holder p { margin: 0 0 4px; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; opacity: .55; }
.pass-holder strong { display: block; font: 26px/1.05 var(--display); font-weight: 400; }

.pass-facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px;
  margin: 22px 0 0; padding: 18px 0 0; border-top: 1px solid rgb(255 255 255 / .12); }
.pass-facts div { min-width: 0; }
.pass-facts dt { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; opacity: .55; }
.pass-facts dd { margin: 5px 0 0; font-family: var(--mono); font-size: 14px; line-height: 1.3; }

.pass-code { margin-top: 22px; }
.pass-code-zone { min-height: 108px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; text-align: center;
  padding: 16px; background: rgb(255 255 255 / .12);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .12); }
.pass-code-zone span { font-size: 13px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; }
.pass-code-zone small { font-size: 12px; line-height: 1.4; opacity: .6; max-width: 30ch; }
.pass-state { display: flex; align-items: center; gap: 7px; margin: 12px 0 0;
  font-size: 12px; opacity: .7; }
.pass-state .icon { width: 13px; height: 13px; }

/* The rail is a carousel, so the next card should read as a peek rather than as
   chopped text. Cards narrow enough to show a real second item, text clamped so
   it never runs into the cut, and the edge fades instead of ending on a chop. */
.announce-rail {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 82%, transparent 100%);
  mask-image: linear-gradient(to right, #000 0, #000 82%, transparent 100%);
}
.announce { flex: 0 0 68%; }
.announce em, .announce span {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.announce em { -webkit-line-clamp: 1; }


/* Tap targets. The audit measured 23 controls under 44px, all on Events. Where
   the drawn control is smaller than a finger, a pseudo-element carries the hit
   area so the drawing stays the size the design wants. */
.chip, .filter-row button, .event-actions button, .segments button { position: relative; }
.chip::after, .filter-row button::after, .event-actions button::after,
.segments button::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%); height: 44px; }
.event-card .event-title { position: relative; }
.event-card .event-title::after { content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%); height: 44px; }

/* The remaining small ones: text links and any button a later rule shrank. */
.linkish { position: relative; }
.linkish::after { content: ""; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%); height: 44px; }
.btn, .primary-action, .secondary-action { min-height: 44px; }

.renew-actions .btn, .action-row .btn { min-height: 44px; }
/* The round chrome button is drawn at 38 to sit well on the image; the hit area
   reaches 44 without changing what is drawn. */
.round-btn::after { content: ""; position: absolute; inset: -3px; }

/* Section header, after the reference: a sentence-case heading with a quiet
   link opposite it, rather than a caps eyebrow with a count. */
.rail-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin: 22px 0 12px; }
/* One step darker than the greeting: at 19px this is normal text by WCAG and
   --quiet is 4.28:1, under the 4.5 floor. The greeting clears it at 24px, where
   3:1 applies. Same family, the size decides the step. */
.rail-head h2 { margin: 0; font: 19px/1.15 var(--display); font-weight: 400;
  letter-spacing: -.015em; color: var(--muted); }
.rail-more .icon { width: 13px; height: 13px; opacity: .65; }
.rail-more { font-family: var(--sans); font-size: 14px; color: var(--muted);
  text-decoration: none; display: inline-flex; align-items: center; gap: 5px;
  border: 0; background: none; min-height: 44px; padding: 0 0 0 12px; cursor: pointer; }


/* A dim lobby and an older reader: nothing inside the device goes under 11px.
   The system documents 16px body and 12px small; these were the last holdouts. */
.event-copy .inline-actions button { font-size: 12px; }
.access-note { font-size: 11px; }

/* Patron card, after the reference: a portrait plate badged with the mark, the
   name in display beneath it, the role under that. There is no photograph of a
   member in this prototype, so the plate says so rather than borrowing a face. */
.patron-profile { margin: 4px 0 8px; }
.patron-portrait { position: relative; aspect-ratio: 1 / 1; border-radius: var(--r-field);
  background: var(--media-ground); overflow: hidden; display: grid; place-items: center; }
.patron-badge { position: absolute; top: 14px; left: 14px; display: inline-flex;
  align-items: center; height: 30px; padding: 0 12px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--off-white) 72%, transparent);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%); color: var(--brand); }
.patron-badge .lockup { height: 12px; }
.patron-initials { font: 58px/1 var(--display); font-weight: 400; letter-spacing: -.03em;
  color: rgb(255 255 255 / .82); }
.patron-noimg { position: absolute; bottom: 14px; left: 14px; font-family: var(--sans);
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: rgb(255 255 255 / .60); }
.patron-name { margin: 18px 0 6px; font: 32px/1.04 var(--display); font-weight: 400;
  letter-spacing: -.025em; }
.patron-role { margin: 0; font-size: 13px; line-height: 1.5; color: var(--muted); }

/* ---- Atoms: scale and icon states ---------------------------------------
   The reference shows the size ladder before the state matrix, because a
   reader needs to know how big a control is before they care what it does
   when pressed. Sizes are the ones the product actually ships. */
.atom-group { margin: 30px 0 14px; font-family: var(--sans); font-size: 11px;
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--quiet); }
.atom-group:first-child { margin-top: 0; }
.btn-scale { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 28px;
  padding: 22px; background: var(--off-white); border: 1px solid var(--line);
  border-radius: var(--r-field); margin-bottom: 8px; }
.btn-scale figure { margin: 0; display: grid; gap: 10px; justify-items: start; }
.btn-scale figcaption { font-family: var(--sans); font-size: 11px; color: var(--ink);
  display: grid; gap: 2px; }
.btn-scale figcaption span { font-family: var(--mono); font-size: 10px; color: var(--quiet); }
.btn--sm { min-height: 36px; padding: 0 16px; font-size: 11px; }
.btn--lg { min-height: 50px; padding: 0 26px; font-size: 13px; }

/* The icon button as a documented control, with the same five states as the
   rest. Thin stroke on focus, matching every other control on the sheet. */
.round-btn--spec { border: 1px solid var(--line); background: var(--off-white); }
.round-btn--spec:hover, .round-btn--spec.is-hover { background: var(--gray); }
.round-btn--spec:active, .round-btn--spec.is-active { background: var(--gray-pressed); }
.round-btn--spec.is-focus { outline: 1.5px solid var(--harmony); outline-offset: 2px; }
.round-btn--spec:disabled, .round-btn--spec.is-disabled { background: var(--raised);
  color: var(--quiet); border-color: var(--line); cursor: not-allowed; }

/* The portrait plate is an upload target, not a placeholder. It says what it
   wants rather than reporting an absence. */
.patron-portrait { border: 0; padding: 0; width: 100%; cursor: pointer; }
.patron-upload { position: absolute; bottom: 14px; left: 14px; display: inline-flex;
  align-items: center; gap: 7px; min-height: 36px; padding: 0 14px;
  border-radius: var(--r-pill); font-family: var(--sans); font-size: 12px;
  font-weight: 600; color: var(--ink);
  background: color-mix(in srgb, var(--off-white) 82%, transparent);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%); }
.patron-upload .icon { width: 14px; height: 14px; }

/* Season figures carry what they are counting. A 6 on its own could be shows,
   seats or months; "2 of 6" and "Orchestra A" cannot be misread. */
.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; margin: 0; }
.stat-row > div { padding: 14px 0; border-bottom: 1px solid var(--line); min-width: 0; }
.stat-row dt { font-family: var(--sans); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--quiet); }
.stat-row dd { margin: 6px 0 0; font: 26px/1 var(--display); font-weight: 400;
  letter-spacing: -.02em; display: flex; align-items: baseline; gap: 6px; }
.stat-row dd span { font-family: var(--sans); font-size: 11px; font-weight: 500;
  letter-spacing: 0; color: var(--muted); }

/* ---- Announcements as a panel ------------------------------------------
   Not a card on a scrim: it slides in and owns the screen, the way a
   notifications page does. No backdrop and no blur, so nothing behind it
   competes and the list is simply the thing you are looking at. */
.stage-over[data-overlay-panel="announcements"] { background: none;
  backdrop-filter: none; -webkit-backdrop-filter: none; animation: none;
  display: block; padding: 0; }
.modal-card { position: absolute; inset: 0; display: flex; flex-direction: column;
  background: var(--off-white); border-radius: 0; box-shadow: none;
  animation: panel-in 420ms cubic-bezier(.32, .72, 0, 1) both; }
/* In from the right, the way a pushed view arrives. The curve is the long
   decelerating one, so it settles rather than snapping. */
@keyframes panel-in { from { transform: translateX(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { .modal-card { animation: none; } }
.modal-head { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: calc(var(--safe-top) + 4px) 18px 14px;
  border-bottom: 1px solid var(--line); }
.modal-head h2 { margin: 0; font: 22px/1.15 var(--display); font-weight: 400;
  letter-spacing: -.02em; }
.modal-close { width: 36px; height: 36px; border: 0; border-radius: var(--r-pill);
  background: var(--gray); color: var(--ink); display: grid; place-items: center;
  cursor: pointer; flex: none; }
.modal-close .icon { width: 15px; height: 15px; }
.modal-body { flex: 1; overflow: auto; padding: 4px 18px calc(104px + var(--safe-bottom));
  scrollbar-width: none; }
.modal-body::-webkit-scrollbar { display: none; }
.modal-body .section-block { margin-top: 18px; }
.modal-body .notice-list, .modal-body .notice-item { margin-left: 0; margin-right: 0;
  width: auto; }

/* The promotional drawer is a quarter of the screen. It is an offer, not a task,
   so it announces itself and leaves. At this height the contents have to be
   compact rather than merely scrollable, or the cap just hides them. */
.sheet-panel { max-height: 25%; overflow: auto; padding: 8px 18px 16px;
  scrollbar-width: none; }
.sheet-panel::-webkit-scrollbar { display: none; }
.sheet-grip { margin: 0 auto 10px; }
.sheet-eyebrow { margin: 0 0 4px; }
.sheet-panel h2 { font-size: 18px; line-height: 1.15; margin-bottom: 4px; }
.sheet-body { margin-bottom: 10px; font-size: 12px; line-height: 1.4; }
.sheet-panel .field { margin-bottom: 0; }
.sheet-panel .field label { display: none; }
.sheet-panel .field input { min-height: 40px; }
.sheet-actions { grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; margin-top: 10px; }
.sheet-actions .btn { min-height: 42px; }
.sheet-foot { display: none; }

/* Choosing how you get in is a decision, not a destination: it rises over the
   screen you were on and leaves you there. Taller than the promo drawer,
   because it holds two real options rather than an offer. */
.access-sheet { max-height: 62%; }
.access-sheet h2 { font: 26px/1.08 var(--display); font-weight: 400;
  letter-spacing: -.025em; margin: 0 0 14px; }
.access-sheet .choice { border-top: 1px solid var(--line); }
.access-sheet .choice:last-of-type { border-bottom: 1px solid var(--line); }
.access-sheet .access-note { margin: 14px 0 0; }

/* A membership is issued by an institution, not by the platform it is held in,
   so the stamp names the institution. No marks exist for these, and drawing an
   approximation of a real logo is worse than setting the name. */
.member-head { align-items: baseline; gap: 8px; }
.member-issuer { font-family: var(--sans); font-size: 11px; font-weight: 650;
  letter-spacing: .06em; text-transform: uppercase; color: var(--brand); }


/* ---- Included shows ------------------------------------------------------
   The same row the Events screen uses: date block on the left, the show and
   where it is on the right. One row format across the app, so a show reads the
   same wherever it is listed. Two of the six have no date yet, and the slot
   says so rather than being left blank. */
.incl-shows { display: grid; }
.incl-shows .event-card { padding: 15px 0; margin: 0;
  border-bottom: 1px solid var(--line); background: none;
  grid-template-columns: 38px 1fr; gap: 14px; }
.incl-shows .event-card:last-child { border-bottom: 0; }
.incl-shows .event-title { font-size: 16px; margin-top: 4px; }
.event-when.is-unset { padding-top: 5px; }
.event-when.is-unset span { margin-top: 0; }
.event-open { margin: 6px 0 0; font-family: var(--sans); font-size: 11px;
  letter-spacing: .06em; text-transform: uppercase; color: var(--quiet); }

/* The account row was a full-bleed band pinned above the navigation, which read
   as fixed chrome rather than the last item on the page. It comes off the bleed
   rule entirely so it sits on the same gutter as everything above it. */
.settings-list--single { border-top: 0; margin: 10px 0 0;
  margin-left: 0; margin-right: 0; width: 100%; }
/* Filled, like every other card on the screen. An outline was the one card
   treatment that appeared once. */
.settings-list--single button { border: 0; background: var(--gray);
  border-radius: 14px; padding: 0 16px; min-height: 58px;
  margin-left: 0; margin-right: 0; width: 100%; }


/* ---- The pass as the evening, not a card on a page -----------------------
   After the reference: the poster is the surface. The image runs to the device
   edges and dissolves into a dark ground that carries the billing, and the one
   white object on the screen is the thing you actually hold at the door. The
   date takes the lemon because on this ground burgundy is unreadable, and
   lemon is already in the palette. */
/* The chrome scrolls away with the poster. Sticky leaves the pill on top of the
   billing; absolute pins it to the phone, which is worse — it then rides over
   the ticket card. Relative keeps it in flow, and the stage's negative top
   margin still pulls the photograph up underneath it. */
.pass-chrome { position: relative; z-index: 20; }
.pass-stage { position: relative; margin: 0 -18px;
  background: var(--off-black); color: var(--off-white);
  padding: 0 18px 26px;
  margin-top: calc(-1 * (var(--safe-top) + 52px)); }
.pass-poster { position: absolute; top: 0; left: 0; right: 0;
  height: calc(min(874px, 100vh - 78px) * 0.46); }
.pass-poster img { display: block; width: 100%; height: 100%; object-fit: cover;
  object-position: center 42%; }
.pass-poster-veil { position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    rgb(16 16 16 / .55) 0, rgb(16 16 16 / .18) 34%,
    rgb(16 16 16 / .82) 82%, var(--off-black) 100%); }
.pass-billing { position: relative;
  padding-top: calc(min(874px, 100vh - 78px) * 0.46); }
.pass-billing h1 { margin: 18px 0 0; font: 26px/1.1 var(--display); font-weight: 400;
  letter-spacing: -.025em; color: var(--off-white); }
.pass-billing-when { margin: 8px 0 0; font-family: var(--sans); font-size: 13px;
  font-weight: 600; color: var(--accent); }
.pass-billing-where { margin: 4px 0 0; font-family: var(--sans); font-size: 13px;
  color: rgb(255 255 255 / .58); }

.pass-ticket { position: relative; margin-top: 20px; background: var(--off-white);
  color: var(--ink); border-radius: 16px; padding: 16px; display: grid; gap: 14px; }
.pass-ticket-row { display: grid; grid-template-columns: 38px 1fr; gap: 12px;
  align-items: center; }
.pass-ticket-ico { width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: 10px; background: var(--pressed); color: var(--ink); }
.pass-ticket-row small { display: block; font-family: var(--sans); font-size: 11px;
  letter-spacing: .06em; text-transform: uppercase; color: var(--quiet); }
.pass-ticket-row strong { display: block; margin-top: 3px; font: 19px/1.1 var(--display);
  font-weight: 400; letter-spacing: -.02em; }
.pass-ticket-seats { display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  padding-top: 14px; border-top: 1px solid var(--line); }
.pass-ticket-seats dt { font-family: var(--sans); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--quiet); }
.pass-ticket-seats dd { margin: 4px 0 0; font-family: var(--sans); font-size: 13px;
  font-weight: 600; line-height: 1.3; }
.pass-ticket .pass-code-zone { background: var(--gray); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line); border-radius: 12px; min-height: 96px; }
.pass-ticket .pass-code-zone small { opacity: 1; color: var(--muted); }
.pass-ticket .btn { width: 100%; }


/* ---- Events, grouped by membership ---------------------------------------
   A month heading answers "when", which every row already says. Grouping by
   the membership answers the question the app is actually for: what does each
   relationship get me, and what falls outside all of them. */
.event-group + .event-group { margin-top: 26px; }
.event-group-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; margin: 0 0 6px; }
.event-group-name { margin: 0; font: 17px/1.15 var(--display); font-weight: 400;
  letter-spacing: -.02em; color: var(--ink); }
.event-group-kind { font-family: var(--sans); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--quiet); white-space: nowrap; }


/* The three relationships stated as classifiers rather than as two lines of
   prose that repeated the membership cards. Name on the left, what kind of
   thing it is on the right, the same pairing the Events groups use. */
.patron-classifiers { list-style: none; margin: 14px 0 0; padding: 0;
  display: grid; gap: 0; }
.patron-classifiers li { display: flex; justify-content: space-between;
  align-items: baseline; gap: 12px; padding: 10px 0;
  border-bottom: 1px solid var(--line); }
.patron-classifiers li:last-child { border-bottom: 0; }
.patron-classifiers span { font-size: 14px; color: var(--ink); }
.patron-classifiers em { font-style: normal; font-family: var(--sans);
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--quiet); white-space: nowrap; }

/* The account's sub-blocks now carry the same head as the Events groups, so
   the two screens describe a membership the same way. */
.sub-module .event-group-head { margin-bottom: 8px; }


/* ---- No lateral drift ----------------------------------------------------
   `overflow: auto` scrolls both axes, so any child a pixel over its box let the
   screen slide sideways under a thumb and rubber-band into the page behind it.
   Each screen scrolls on one axis and keeps its overscroll to itself. */
.screen { overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
html, body { overflow-x: hidden; overscroll-behavior: none; }


/* Every horizontal scroller keeps its overscroll to itself, so a sideways flick
   inside a rail never chains out to the screen or the page behind it. */
.filter-row, .announce-rail, .member-rail, .segments {
  overscroll-behavior-x: contain; overflow-y: hidden; }
.filter-row, .announce-rail { scroll-snap-type: x proximity; }


/* Plan status is an object, not a strip of loose text, so it sits in the same
   card the memberships use. The renewal date is the one thing to act on, so it
   takes the brand hue and everything else stays neutral. */
.plan-strip { display: flex; justify-content: space-between; align-items: center;
  gap: 14px; background: var(--gray); border-radius: 14px;
  padding: 14px 16px; margin: 0; }
.plan-strip b { display: block; font: 17px/1.15 var(--display); font-weight: 400;
  letter-spacing: -.02em; }
.plan-strip small { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); }
.plan-flag { flex: none; font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--brand);
  text-align: right; }

.renew-block p { font-size: 13px; line-height: 1.5; }
.renew-actions { margin-top: 12px; align-items: center; }
.renew-actions .btn--tertiary { padding: 0 4px; }

/* Controls is one object with rows inside it, not six separate cards. Filled
   once, parted by hairlines, corners only on the ends. */
.settings-list:not(.settings-list--single) { background: var(--gray);
  border-radius: 14px; border-top: 0; overflow: hidden;
  margin-left: 0; margin-right: 0; width: 100%; }
.settings-list:not(.settings-list--single) button { padding: 0 16px;
  border-bottom: 1px solid rgb(0 0 0 / .06); margin-left: 0; margin-right: 0;
  width: 100%; }
.settings-list:not(.settings-list--single) button:last-child { border-bottom: 0; }

/* The dark stage runs to the device edges, so the foot fade that lifts content
   off the tab bar has nothing to fade into. The pass screen carries its own
   clearance instead: the ticket card ends above the bar rather than under it. */
.screen[data-screen="pass"] .pass-stage { margin-bottom: 18px; }
.screen[data-screen="pass"] { padding-bottom: 118px; }
