/* ==========================================================================
   Acevelt design system · desys.css
   Extracted from the A–J prototypes. Plain CSS, no build step, no JS.

   Contents
     01  Tokens
     02  Base
     03  Layout helpers        .stack .row .split .rule
     04  Chrome & navigation   .app .appbar .estate-mark .iconbtn .topbanner .page
                               .back .pagehead .section .tabbar .stepper .footbar
                               .brand
     05  Actions               .btn .textbtn .tile .dial
     06  Inputs                .field .input .textarea .select .chip .seg .choice
                               .switch .upload .daypick .slot .amount
     07  Data display          .list .badge .count .dot .stats .figure .card
                               .attachment .kv .receipt .meter .stages .bullets
                               .preview .bigcode .numbered .table .photo .avatar
     08  Feedback & states     .banner .alert .empty .hint .toast
     09  Overlays              .scrim .sheet .popcard
     10  Gate (guard) & Pass   .gate .gate-status .gate-nav .gate-btn .gate-input
                               .codeboxes .keypad .gate-strip .gate-card
                               .gate-alert .result .pass .gate-dest .gate-steps
                               .gate-row .gate-timer .gate-checks .passpage
     11  Documentation chrome  .doc-*  (specimen pages only, the app never loads it)

   Conventions
     block, block__element, block--variant. A variant is never used alone:
     class="btn btn--primary". State is .is-* (.is-active .is-on .is-selected
     .is-invalid .is-live .is-done .is-disabled) and those are the only classes
     a script toggles.
   ========================================================================== */

/* Instrument Sans 400/600. Imported here so a page needs one <link> only.
   Self-host in production and replace this line. */
@import url('https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;600&display=swap');


/* ==========================================================================
   01  Tokens
   ========================================================================== */
:root {
  /* Brand. Keyline Blue is the only accent: primary actions, links, focus,
     selected. It never carries status. */
  --blue: #3448C5;
  --blue-hover: #2B3CA8;
  --blue-tint: #ECEEFA;      /* selected chip/option fill, accent badge */
  --blue-wash: #F7F8FD;      /* selected row in a choice list, paler than tint
                                so the radio dot stays the strongest signal */
  --ink: #15171C;            /* text, and the "solid" fill for filter chips,
                                gate buttons, toasts, estate mark */

  /* Surfaces */
  --paper: #FCFCFD;          /* app background, sheets, sticky bars */
  --white: #FFFFFF;          /* list groups, inputs, cards */
  --slate-25: #F7F8F9;       /* row hover, attachment button */
  --slate-50: #F3F4F6;       /* grouped surface, neutral callout, secondary hover */
  --slate-75: #EFF0F2;       /* inactive badge fill, avatar */
  --stripe: #E9EBEE;         /* second band of the photo placeholder */
  --rule: #E3E5E9;           /* hairlines */
  --rule-strong: #C9CDD3;    /* unchecked marks, switch track, dashed upload,
                                gate inputs: anything that must read as "empty" */

  /* Text */
  --text: var(--ink);
  --text-2: #5F6570;         /* Graphite: secondary text, icons, metadata */
  --text-3: #8A9099;         /* placeholder only. Fails AA as body text. */
  --text-on: #FFFFFF;

  /* Semantic pairs. Always used together: the strong colour on text/icon/
     strip/dot, the tint as fill. Never the strong colour as a badge fill.
     (Gate results and the danger button are the only solid fills.) */
  --valid: #1E7A4C;     --valid-tint: #E8F2EC;
  --pending: #A86A12;   --pending-tint: #F7EFE3;
  --danger: #C8322B;    --danger-tint: #FAEAE9;
  --inactive: #8A9099;  --inactive-tint: #EFF0F2;
  /* Inactive TEXT is Graphite, not #8A9099: #8A9099 on its own tint is ~2.9:1.
     The grey hex is kept for strips and dots only. */
  --inactive-text: var(--text-2);
  --neutral: var(--text-2); --neutral-tint: var(--slate-50);
  --accent: var(--blue);    --accent-tint: var(--blue-tint);

  /* Radius ladder: 6 small things, 10 controls & groups, 16 floating surfaces.
     8 is for things that sit inside a 10 or are under 40px tall. */
  --r-hair: 2px;    /* bars, grabber */
  --r-xs: 4px;      /* Acevelt brand mark */
  --r-sm: 6px;      /* badges, chips, gate status pill */
  --r-md: 8px;      /* estate mark, sm buttons, segmented thumb */
  --r-lg: 10px;     /* inputs, buttons, list groups, cards, callouts, toast */
  --r-xl: 16px;     /* sheets, pop cards, the Pass */
  --r-pill: 999px;  /* counts, avatars, dots, switch */

  /* Shadows. Depth is borders and tone; these are the only three. */
  --shadow-sheet: 0 -8px 32px rgba(21, 23, 28, 0.14);
  --shadow-float: 0 8px 24px rgba(21, 23, 28, 0.18);   /* toast */
  --shadow-pop: 0 12px 32px rgba(21, 23, 28, 0.18);    /* top pop cards */
  --shadow-pass: 0 1px 0 #E3E5E9, 0 10px 28px rgba(21, 23, 28, 0.08);
  --ring: 0 0 0 1px var(--blue);                       /* input focus, adds to border */
  --stamp-bg: rgba(255, 255, 255, 0.85);              /* VOID stamp backing */
  --valid-stripe: #DCEBE2;                             /* 2nd band of a taken photo */
  --scrim: rgba(21, 23, 28, 0.32);
  --scrim-interrupt: rgba(21, 23, 28, 0.40);           /* walk-in request only */
  --on-fill: rgba(255, 255, 255, 0.18);                /* viewfinder stripes on a gate result */
  --on-fill-2: rgba(255, 255, 255, 0.08);

  /* Spacing. 4px grid; 2/6/10/14 are the tight pair gaps (icon–text,
     label–control, title–sub) and are deliberate. */
  --s2: 2px; --s4: 4px; --s6: 6px; --s8: 8px; --s10: 10px; --s12: 12px;
  --s14: 14px; --s16: 16px; --s18: 18px; --s20: 20px; --s24: 24px; --s32: 32px; --s40: 40px;
  --gutter: var(--s20);      /* page side padding */
  --column: 480px;           /* resident, guard and admin all ship in this column */

  /* Control heights */
  --h-xs: 28px;   /* prototype bar only */
  --h-sm: 36px;   /* filter chip, header buttons, banner action */
  --h-chip: 40px; /* option chip */
  --h-touch: 44px;/* minimum hit target */
  --h-btn: 48px;  /* default button & input */
  --h-row: 56px;  /* list row, tab, large button */
  --h-g-sm: 56px; --h-g-md: 64px; --h-g-lg: 72px;   /* gate */

  /* Borders */
  --bw: 1px; --bw-mark: 1.5px; --bw-g: 2px;

  /* Type. One family, two weights. Tabular figures everywhere. */
  --font: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: ui-monospace, Menlo, monospace;  /* placeholder captions only */
  --w-regular: 400; --w-strong: 600;
  --type-micro: 600 11px/18px var(--font);
  --type-small: 400 12px/16px var(--font);
  --type-small-strong: 600 12px/16px var(--font);
  --type-badge: 600 12px/18px var(--font);
  --type-meta: 400 13px/18px var(--font);
  --type-meta-strong: 600 13px/18px var(--font);
  --type-secondary: 400 14px/20px var(--font);
  --type-control: 600 14px/20px var(--font);
  --type-body: 400 15px/22px var(--font);
  --type-body-strong: 600 15px/22px var(--font);
  --type-input: 400 16px/22px var(--font);   /* 16px stops iOS zoom on focus */
  --type-reading: 400 16px/24px var(--font);
  --type-section: 600 17px/24px var(--font);
  --type-lead: 600 19px/26px var(--font);
  --type-card-title: 600 20px/26px var(--font);
  --type-title: 600 22px/28px var(--font);
  --type-stat: 600 24px/30px var(--font);
  --type-total: 600 32px/38px var(--font);
  --type-figure: 600 36px/42px var(--font);
  --type-code: 600 44px/48px var(--font);
  /* Gate: nothing under 17px */
  --type-g-body: 400 17px/24px var(--font);
  --type-g-strong: 600 17px/24px var(--font);
  --type-g-input: 400 19px/26px var(--font);
  --type-g-lead: 600 20px/26px var(--font);
  --type-g-title: 600 22px/28px var(--font);
  --type-g-btn: 600 22px/28px var(--font);    /* 72px gate button label */
  --type-g-heading: 600 26px/32px var(--font);
  --type-g-name: 600 28px/34px var(--font);
  --type-g-alert: 600 32px/36px var(--font);
  --type-g-digit: 600 34px/40px var(--font);
  --type-g-word: 600 48px/52px var(--font);
  --track-code: 0.06em;     /* numeric codes in two groups: 482 913 */
  --track-pairing: 0.12em;  /* letter+digit pairing codes need more air */
  --track-otp: 0.3em;       /* 6-digit email code typed into one input */

  /* Motion */
  --t-fast: 150ms; --t-base: 200ms; --t-reveal: 250ms;
  --ease: ease-out;
  --pulse: 2.4s ease-in-out infinite;
}


/* ==========================================================================
   02  Base
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
html, body {
  margin: 0; background: var(--paper); color: var(--text);
  font: var(--type-body); -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}
h1, h2, h3, p { margin: 0; }
/* Script-toggled visibility: [hidden] must beat any display class. */
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* Lists used for layout (rows, tiles, cards) carry role="list" and no bullets. */
ul[role="list"], ol[role="list"] { margin: 0; padding: 0; list-style: none; }
button, input, select, textarea { font-family: inherit; color: inherit; }
button { cursor: pointer; }
a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--blue-hover); text-decoration: underline; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
::placeholder { color: var(--text-3); }
svg.icon { flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

@keyframes avSheet { from { transform: translate(-50%, 24px); opacity: 0; } to { transform: translate(-50%, 0); opacity: 1; } }
@keyframes avFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes avPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(200, 50, 43, 0.28); } 50% { box-shadow: 0 0 0 8px rgba(200, 50, 43, 0); } }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }


/* ==========================================================================
   03  Layout helpers. That's all of them. Anything else is a component.
   ========================================================================== */
.stack { display: flex; flex-direction: column; gap: var(--s8); }
.stack--tight { gap: var(--s4); }
.stack--loose { gap: var(--s16); }
.stack--page { gap: var(--s24); }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s6); }
.split { display: flex; justify-content: space-between; align-items: center; gap: var(--s12); }
.split--baseline { align-items: baseline; }
.split--center { justify-content: center; }   /* one centred object: the hold dial */
.rule { border: 0; border-top: var(--bw) solid var(--rule); margin: 0; }


/* ==========================================================================
   04  Chrome & navigation
   ========================================================================== */
/* The column. Hairlines left and right mark its edge on desktop. */
.app {
  min-height: 100vh; min-height: 100dvh; width: 100%; max-width: var(--column); margin: 0 auto;
  background: var(--paper); border-left: var(--bw) solid var(--rule); border-right: var(--bw) solid var(--rule);
  display: flex; flex-direction: column; position: relative;
}

/* Estate header. 61px min so the 48px switcher + 6px padding never jumps
   when the role line appears. */
.appbar {
  position: sticky; top: 0; z-index: 5; min-height: 61px;
  display: flex; align-items: center; gap: var(--s6);
  padding: var(--s6) var(--s12); background: var(--paper); border-bottom: var(--bw) solid var(--rule);
}
.appbar__estate {
  flex: 1; min-width: 0; min-height: var(--h-btn); display: flex; align-items: center; gap: var(--s10);
  padding: var(--s4) var(--s8); border: 0; border-radius: var(--r-lg); background: none; text-align: left;
}
button.appbar__estate:hover { background: var(--slate-50); }
.appbar__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.appbar__name { font: var(--type-section); line-height: 22px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.appbar__role { font: var(--type-small); color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.appbar__chev { color: var(--text-2); }
/* Something is waiting in another estate. */
.appbar__dot { flex: none; width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--blue); }
.appbar__actions { flex: none; display: flex; align-items: center; gap: var(--s6); }

/* The estate's initial. Square-ish (8px), never round, so it can't be read
   as a person's avatar. */
.estate-mark {
  flex: none; width: 30px; height: 30px; border-radius: var(--r-md);
  background: var(--ink); color: var(--text-on); font: var(--type-control);
  display: flex; align-items: center; justify-content: center;
}
.estate-mark--lg { width: 36px; height: 36px; }
/* Invitation and welcome: the estate you're joining. Unified from 48/56. */
.estate-mark--xl { width: 48px; height: 48px; border-radius: var(--r-lg); font: var(--type-g-lead); }

.iconbtn {
  position: relative; flex: none; width: var(--h-touch); height: var(--h-touch);
  border: 0; border-radius: var(--r-lg); background: none; color: var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.iconbtn:hover { background: var(--slate-50); }
/* Bordered: remove a block in the layout editor. */
.iconbtn--outline { border: var(--bw) solid var(--rule); background: var(--white); color: var(--text-2); }
.iconbtn > .count { position: absolute; top: 6px; right: 5px; min-width: 16px; height: 16px; padding: 0 var(--s4); font-size: 10px; line-height: 16px; }

/* Full-width strip under the header for app-wide states (notifications off,
   update ready). Its action is Ink-outlined, not blue: the strip is already
   tinted and a blue button would compete with the page's primary action. */
.topbanner {
  display: flex; align-items: center; gap: var(--s10); padding: var(--s10) var(--gutter);
  background: var(--slate-50); border-bottom: var(--bw) solid var(--rule); font: var(--type-secondary);
}
.topbanner--warn { background: var(--pending-tint); }
.topbanner--accent { background: var(--blue-tint); }
.topbanner__text { flex: 1; text-wrap: pretty; }
.topbanner__action {
  flex: none; min-height: var(--h-sm); padding: 0 var(--s12); border: var(--bw) solid var(--ink);
  border-radius: var(--r-md); background: var(--white); font: var(--type-meta-strong);
}

.page { flex: 1; padding: var(--s12) var(--gutter) var(--s32); display: flex; flex-direction: column; gap: var(--s24); }
/* Signed-out pages (sign in, create account, accept invite): no estate header,
   so the page starts lower. .page__foot pins a ruled footer to the bottom. */
.page--auth { padding-top: var(--s32); }
/* The one action of a full-height step (welcome), pushed to the bottom. */
.page__end { margin-top: auto; }
.page__foot { margin-top: auto; padding-top: var(--s20); border-top: var(--bw) solid var(--rule); display: flex; flex-direction: column; gap: var(--s8); }

/* Back sits 16px tighter to the title than page rhythm allows. */
.back {
  align-self: flex-start; min-height: var(--h-touch); margin-bottom: calc(-1 * var(--s16));
  display: flex; align-items: center; gap: var(--s4); padding: 0; border: 0; background: none;
  font: var(--type-body-strong); color: var(--blue);
}

.pagehead { display: flex; flex-direction: column; gap: var(--s4); padding-top: var(--s8); }
.pagehead__title { font: var(--type-title); text-wrap: pretty; }
.pagehead__sub { color: var(--text-2); text-wrap: pretty; }

.section { display: flex; flex-direction: column; gap: var(--s10); }
.section__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s12); }
.section__title { font: var(--type-section); }
.section__aside { font: var(--type-meta); color: var(--text-2); }

/* Bottom tabs. Unified from two drifts: A–E used icon+label with colour only,
   F–J used text with a 2px top line and a count. Both now take the line;
   the icon is optional (.tabbar__icon). Max five tabs. */
.tabbar {
  position: sticky; bottom: 0; margin-top: auto; z-index: 5;
  display: grid; grid-auto-columns: 1fr; grid-auto-flow: column;
  background: var(--paper); border-top: var(--bw) solid var(--rule);
}
.tabbar__tab {
  position: relative; min-height: var(--h-row); padding: 0 var(--s2);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s2);
  border: 0; border-top: var(--bw-g) solid transparent; background: none;
  color: var(--text-2); font: var(--type-control);
}
.tabbar__tab.is-active { color: var(--blue); border-top-color: var(--blue); }
/* Tabs are links in production (each is a URL). Keep base link hover off them. */
a.tabbar__tab:hover { color: var(--text-2); text-decoration: none; }
a.tabbar__tab.is-active:hover { color: var(--blue); }
.tabbar__label { display: flex; align-items: center; gap: var(--s6); }
/* Dot = something live needs attention (an ongoing alert). Count = unread. */
.tabbar__dot { position: absolute; top: var(--s8); left: calc(50% + 14px); width: 6px; height: 6px; border-radius: var(--r-pill); background: var(--danger); }

/* Setup wizard header: "Step 2 of 4 · Layout" + bars. Bars unified to 4px
   with .stages. */
.stepper { display: flex; flex-direction: column; gap: var(--s10); padding: var(--s16) var(--gutter) 0; }
.stepper__meta { font: var(--type-meta); color: var(--text-2); }
.stepper__bars { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s4); }
.stepper__bar { height: 4px; border-radius: var(--r-hair); background: var(--rule); }
.stepper__bar.is-done { background: var(--blue); }

/* Sticky bottom action area for multi-step forms. Replaces the tab bar. */
.footbar {
  position: sticky; bottom: 0; margin-top: auto; z-index: 5;
  display: flex; flex-direction: column; gap: var(--s8);
  padding: var(--s12) var(--gutter) var(--s16); background: var(--paper); border-top: var(--bw) solid var(--rule);
}
.footbar__summary { font: var(--type-control); }

/* Acevelt wordmark, for surfaces that aren't inside an estate yet: the public
   pass page and gate device setup. A plain Ink square, so it can't be read as
   an estate mark. */
.brand { display: flex; align-items: center; gap: var(--s8); font: var(--type-control); color: var(--ink); }
.brand--center { justify-content: center; }
.brand--lg { font: var(--type-g-strong); }
.brand__mark { flex: none; width: 16px; height: 16px; border-radius: var(--r-xs); background: var(--ink); }
.brand--lg .brand__mark { width: 20px; height: 20px; }


/* ==========================================================================
   05  Actions
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s6);
  height: var(--h-btn); padding: 0 var(--s16); width: 100%;
  border: var(--bw) solid var(--rule); border-radius: var(--r-lg);
  background: var(--white); color: var(--ink); font: var(--type-body-strong); text-decoration: none;
}
.btn:hover { background: var(--slate-50); text-decoration: none; color: var(--ink); }
.btn--primary { background: var(--blue); border-color: var(--blue); color: var(--text-on); }
.btn--primary:hover { background: var(--blue-hover); border-color: var(--blue-hover); color: var(--text-on); }
.btn--secondary { /* = base. Exists so markup states intent. */ }
/* Solid red is for confirming a destructive or emergency step inside a sheet
   (Delete, Send to everyone, Acknowledge). Never on a page's resting state. */
.btn--danger { background: var(--danger); border-color: var(--danger); color: var(--text-on); }
.btn--danger:hover { background: var(--danger); color: var(--text-on); }
/* Red outline opens a danger flow without being the confirm (Emergency,
   Warn the whole estate). 1.5px so it holds its weight at 36px. */
.btn--danger-outline { border: var(--bw-mark) solid var(--danger); color: var(--danger); }
.btn--danger-outline:hover { background: var(--danger-tint); color: var(--danger); }
/* "On" = a toggle button that is currently applied. Same tint as .chip.is-selected. */
.btn--on { background: var(--blue-tint); border-color: var(--blue); color: var(--blue); }
.btn--inline { width: auto; flex: none; }
.btn--sm { height: var(--h-sm); padding: 0 var(--s10); border-radius: var(--r-md); font: var(--type-meta-strong); width: auto; flex: none; }
.btn--lg { height: var(--h-row); font-size: 16px; }
/* Emergency phone links: solid, tall, white text. The only place a link is a fill. */
.btn--call { height: var(--h-g-sm); border: 0; background: var(--danger); color: var(--text-on); font: var(--type-section); }
.btn--call:hover { background: var(--danger); color: var(--text-on); }
.btn--call-alt { background: var(--ink); }
.btn--call-alt:hover { background: var(--ink); }
.btn.is-disabled, .btn:disabled { opacity: 0.5; pointer-events: none; }
/* Add another of something (block, gate, household member) at the end of a list. */
.btn--dashed { border-style: dashed; border-color: var(--rule-strong); }

.textbtn {
  align-self: flex-start; min-height: var(--h-touch); display: inline-flex; align-items: center; gap: var(--s4);
  padding: 0; border: 0; background: none; color: var(--blue); font: var(--type-body-strong);
}
.textbtn:hover { color: var(--blue-hover); }
.textbtn:disabled { color: var(--text-2); cursor: default; }
.textbtn--sm { min-height: var(--h-chip); font: var(--type-control); }
/* Inside .gate: 48px target, 17px label ("‹ Change unit", "Record a manual entry"). */
.textbtn--gate { min-height: var(--h-btn); font: var(--type-g-strong); }
.textbtn--center { align-self: center; }

/* Option tiles in a grid: a choice that is bigger than a chip (meal plan,
   facility type, rating). Selection uses the chip tint. */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: var(--s8); }
.tiles--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tiles--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tile {
  min-height: var(--h-btn); padding: var(--s6) var(--s10); border: var(--bw) solid var(--rule);
  border-radius: var(--r-lg); background: var(--white); color: var(--ink); font: var(--type-body-strong);
}
.tile.is-selected { background: var(--blue-tint); border-color: var(--blue); color: var(--blue); }
.tile--tall { min-height: var(--h-row); }
/* Emergency type picker. 2px red, 72px: this is chosen under stress. */
.tile--alarm { min-height: var(--h-g-lg); border: var(--bw-g) solid var(--danger); color: var(--danger); font: var(--type-section); }

/* Press-and-hold to raise an alert. --p is the 0–1 hold progress (set inline
   by the hold timer: the one inline value the system allows). */
.dial {
  --p: 0; width: 232px; height: 232px; border-radius: var(--r-pill);
  background: conic-gradient(var(--danger) calc(var(--p) * 360deg), var(--danger-tint) 0);
  display: flex; align-items: center; justify-content: center;
  touch-action: none; user-select: none; -webkit-user-select: none; cursor: pointer;
}
.dial__face {
  width: 200px; height: 200px; border-radius: var(--r-pill); background: var(--white); color: var(--danger);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s4);
  text-align: center; transition: background var(--t-fast) var(--ease);
}
.dial.is-active .dial__face { background: var(--danger); color: var(--text-on); }
.dial__big { font: 600 40px/44px var(--font); }
.dial__small { font: var(--type-body-strong); line-height: 20px; }


/* ==========================================================================
   06  Inputs
   ========================================================================== */
.field { display: flex; flex-direction: column; gap: var(--s6); }
.field__label { font: var(--type-control); }
.field__help { font: var(--type-meta); color: var(--text-2); }
/* Validation is amber, not red. Red is kept for danger; a missing amount is
   not danger. Don't "fix" this to --danger. */
.field__error { font: var(--type-meta); color: var(--pending); }
.field__ok { font: var(--type-meta); color: var(--valid); }     /* "We'll use +234 803 123 4567" */
.field__optional { font-weight: var(--w-regular); color: var(--text-2); }
/* Fixed prefix before a phone number. */
.field__prefix {
  flex: none; height: var(--h-btn); padding: 0 var(--s12); display: flex; align-items: center;
  border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--slate-50); color: var(--text-2); font: var(--type-input);
}
/* Password with Show / Hide inside the input. */
.field__control { position: relative; }
.field__reveal {
  position: absolute; right: var(--s4); top: var(--s4); height: 40px; padding: 0 var(--s10);
  border: 0; background: none; color: var(--blue); font: var(--type-control);
}
.input--reveal { padding-right: 64px; }
/* One-time email code: one input, wide tracking. */
.input--code { height: 60px; font: 600 28px/34px var(--font); letter-spacing: var(--track-otp); }
.input[readonly] { background: var(--slate-50); color: var(--text-2); }
.field__row { display: flex; gap: var(--s8); }
.field__row > .input { flex: 1; min-width: 0; }

.input, .textarea, .select {
  width: 100%; height: var(--h-btn); padding: 0 var(--s14);
  border: var(--bw) solid var(--rule); border-radius: var(--r-lg);
  background: var(--white); color: var(--ink); font: var(--type-input); outline: none;
}
.textarea { height: auto; padding: var(--s12) var(--s14); resize: vertical; }
.select { padding: 0 var(--s12); }
.input:focus, .textarea:focus, .select:focus { border-color: var(--blue); box-shadow: var(--ring); }
.input.is-invalid, .textarea.is-invalid, .select.is-invalid { border-color: var(--pending); }
.input--sm, .select--sm { height: var(--h-touch); }  /* inline fixes inside a list row */
/* Search: magnifier inside the input. */
.search { position: relative; }
.search__icon { position: absolute; left: var(--s14); top: 15px; color: var(--text-2); pointer-events: none; }
.search .input { padding-left: 40px; }

/* Two chips, deliberately different. Don't merge them.
   .chip         = an answer in a form. Selected = blue tint (it's a value).
   .chip--filter = narrows a list.     Selected = solid Ink (it's a view).
   Filter is 36px because it sits above a list; option is 40px because it's a
   field you're filling in. */
.chips { display: flex; flex-wrap: wrap; gap: var(--s6); }
.chip {
  min-height: var(--h-chip); padding: 0 var(--s14); border: var(--bw) solid var(--rule);
  border-radius: var(--r-sm); background: var(--white); color: var(--ink); font: var(--type-control); text-align: left;
}
.chip.is-selected { background: var(--blue-tint); border-color: var(--blue); color: var(--blue); }
.chip--filter { min-height: var(--h-sm); padding: 0 var(--s12); }
.chip--filter.is-selected { background: var(--ink); border-color: var(--ink); color: var(--text-on); }

/* Segmented control: switches between views of the same thing. The thumb is
   white on slate (raised), unlike chips (filled). */
.seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s2); padding: 3px; background: var(--slate-50); border-radius: var(--r-lg); }
.seg__opt { height: 38px; border: var(--bw) solid transparent; border-radius: var(--r-md); background: transparent; color: var(--text-2); font: var(--type-control); }
.seg__opt.is-active { background: var(--white); border-color: var(--rule); color: var(--ink); }
/* As tab links (each view is a URL). */
a.seg__opt { display: flex; align-items: center; justify-content: center; text-decoration: none; }
a.seg__opt:hover { color: var(--ink); text-decoration: none; }

/* Radio / checkbox rows. Lives inside a .list. Radio is round 20px, check is
   6px-radius 22px: the 2px size difference is so a mixed sheet reads right. */
.choice {
  width: 100%; display: flex; gap: var(--s12); align-items: flex-start; padding: var(--s12) var(--s16);
  border: 0; border-top: var(--bw) solid var(--rule); background: var(--white); text-align: left;
}
.choice.is-selected { background: var(--blue-wash); }
.choice__mark {
  flex: none; margin-top: var(--s2); width: 20px; height: 20px; border-radius: var(--r-pill);
  border: var(--bw-mark) solid var(--rule-strong); display: flex; align-items: center; justify-content: center;
}
.choice.is-selected .choice__mark { border-color: var(--blue); }
.choice.is-selected .choice__mark::after { content: ""; width: 10px; height: 10px; border-radius: var(--r-pill); background: var(--blue); }
.choice__mark--check { width: 22px; height: 22px; margin-top: 1px; border-radius: var(--r-sm); }
.choice.is-selected .choice__mark--check { background: var(--blue); }
.choice.is-selected .choice__mark--check::after {
  width: 10px; height: 6px; border-radius: 0; background: none;
  border-left: 2.4px solid var(--white); border-bottom: 2.4px solid var(--white); transform: translateY(-1px) rotate(-45deg);
}
.choice__body { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.choice__title { font: var(--type-body-strong); }
.choice__desc { font: var(--type-meta); color: var(--text-2); }

/* Switch: used only for a setting that takes effect immediately. If it needs
   a confirm, use a .choice or a button instead. */
.switch {
  flex: none; position: relative; width: 44px; height: 26px; border-radius: var(--r-pill);
  background: var(--rule-strong); transition: background var(--t-fast) var(--ease);
}
.switch::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 22px; height: 22px; border-radius: var(--r-pill);
  background: var(--white); transition: left var(--t-fast) var(--ease);
}
.switch.is-on { background: var(--blue); }
.switch.is-on::after { left: 20px; }

/* Upload tile. Dashed until something is attached, then valid-green. The
   mono caption is a placeholder convention, not product type. */
.upload {
  width: 100%; height: var(--h-g-lg); border: var(--bw) dashed var(--rule-strong); border-radius: var(--r-lg);
  background: var(--white); color: var(--text-2); font: 400 13px/18px var(--mono);
}
.upload.is-done { border-color: var(--valid); background: var(--valid-tint); color: var(--valid); }
.upload--tall { height: 88px; }
.upload--square { aspect-ratio: 1; height: auto; font: var(--type-meta-strong); color: var(--ink); }

/* Number stepper: − / value / +. Unit counts in the layout editor. */
.counter { flex: none; display: flex; align-items: center; border: var(--bw) solid var(--rule); border-radius: var(--r-lg); overflow: hidden; background: var(--white); }
.counter__btn { width: var(--h-touch); height: var(--h-touch); border: 0; background: var(--white); font: 400 20px/1 var(--font); }
.counter__btn:hover { background: var(--slate-50); }
.counter__input { width: 64px; height: var(--h-touch); border: 0; border-left: var(--bw) solid var(--rule); border-right: var(--bw) solid var(--rule); text-align: center; font: var(--type-section); outline: none; }

/* Removable tag: a value you've added that can be renamed or removed (unit
   names, owned units). Not a chip: it isn't a choice. --accent when it's a
   selected value in a form. */
.tags { display: flex; flex-wrap: wrap; gap: var(--s6); }
.tag { display: inline-flex; align-items: center; height: 34px; border: var(--bw) solid var(--rule); border-radius: var(--r-sm); background: var(--white); overflow: hidden; }
.tag__label { height: 100%; padding: 0 var(--s4) 0 var(--s10); border: 0; background: none; font: var(--type-secondary); color: var(--ink); }
.tag__remove { width: 28px; height: 100%; border: 0; background: none; color: var(--text-2); display: flex; align-items: center; justify-content: center; }
.tag--accent { border-color: var(--blue); background: var(--blue-tint); }
.tag--accent .tag__label { font-weight: var(--w-strong); color: var(--blue); }
.tag--accent .tag__remove { color: var(--blue); }
.tag.is-selected { border-color: var(--blue); background: var(--blue-wash); }

/* Day picker. Behaves as a filter (changes the view below), so selected is Ink
   like .chip--filter, but it's 10px radius and two-line. */
.daypick { display: flex; gap: var(--s6); overflow-x: auto; padding-bottom: var(--s4); }
.daypick__day {
  flex: none; min-width: var(--h-row); min-height: var(--h-touch); padding: var(--s4) var(--s10);
  border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); color: var(--ink);
  font: var(--type-meta-strong); line-height: 16px; display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.daypick__date { font: var(--type-small); }
.daypick__day.is-selected { background: var(--ink); border-color: var(--ink); color: var(--text-on); }

/* Time slots. Four states by meaning: open, chosen (blue fill: the one
   solid-blue selection, because it becomes a booking), yours (valid tint),
   taken (slate, no border). */
.slots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s8); }
.slots--1 { grid-template-columns: minmax(0, 1fr); }   /* half-day slots: Morning / Afternoon / All day */
.slot {
  min-height: var(--h-row); padding: var(--s6) var(--s8); border: var(--bw) solid var(--rule-strong);
  border-radius: var(--r-lg); background: var(--white); color: var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; text-align: center;
}
.slot__time { font: var(--type-control); }
.slot__sub { font: var(--type-small); }
.slot.is-selected { background: var(--blue); border-color: var(--blue); color: var(--text-on); }
.slot--mine { background: var(--valid-tint); border-color: var(--valid-tint); color: var(--valid); }
.slot.is-disabled { background: var(--slate-50); border-color: var(--slate-50); color: var(--text-2); cursor: default; }


/* Amount: one big number typed in, with an optional prefix (₦) or suffix
   (kWh). Electricity vending: buy by amount or by units. Not .input: the
   value is the page's main figure, so it uses stat type and a 64px box. */
.amount { display: flex; align-items: center; gap: var(--s8); height: var(--h-g-md); padding: 0 var(--s16); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); }
.amount:focus-within { border-color: var(--blue); box-shadow: var(--ring); }
.amount__affix { flex: none; font: var(--type-stat); color: var(--text-2); }
.amount__input { flex: 1; min-width: 0; height: 100%; border: 0; outline: none; background: transparent; font: var(--type-total); color: var(--text); font-variant-numeric: tabular-nums; }
.amount__input:focus-visible { outline: none; }


/* ==========================================================================
   07  Data display
   ========================================================================== */
/* The list group: the default container for almost everything. Rows are
   separated by top hairlines; the inner wrapper pulls the first one up under
   the group border so there's never a double line. */
.list { border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); overflow: hidden; }
.list__inner { margin-top: -1px; }
/* As a <ul>: one <li> per row. */
ul.list__inner { margin: -1px 0 0; padding: 0; list-style: none; }
.list__row {
  width: 100%; min-height: var(--h-row); display: flex; align-items: center; gap: var(--s12);
  padding: var(--s10) var(--s16); border: 0; border-top: var(--bw) solid var(--rule);
  background: var(--white); color: var(--ink); text-align: left; text-decoration: none; cursor: default;
}
.list__row--link { cursor: pointer; }
.list__row--link:hover { background: var(--slate-25); color: var(--ink); text-decoration: none; }
.list__row--top { align-items: flex-start; padding-top: var(--s12); padding-bottom: var(--s12); }
.list__row--stacked { flex-direction: column; align-items: stretch; gap: var(--s8); padding-top: var(--s12); padding-bottom: var(--s12); }
.list__row.is-unread { background: var(--blue-wash); }
.list__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.list__title { font: var(--type-body-strong); text-wrap: pretty; }
.list__title--plain { font-weight: var(--w-regular); }   /* explanatory rows that aren't items */
.list__title--danger { color: var(--danger); }           /* Cancel pass, Remove gate: opens a danger sheet */
.list__sub { font: var(--type-meta); color: var(--text-2); text-wrap: pretty; }
/* Message text inside a row (forum post, board comment, announcement snippet). */
.list__text { font: var(--type-secondary); color: var(--ink); text-wrap: pretty; }
.list__text--hidden { color: var(--text-2); font-style: italic; }   /* "This post was hidden by a moderator." */
.list__body > .meter { margin-top: var(--s6); }
.list__meta { font: var(--type-meta); color: var(--pending); }  /* time-sensitive line: "Due 30 Oct" */
.list__value { flex: none; max-width: 52%; font: var(--type-control); text-align: right; }
.list__value--action { color: var(--blue); }
.list__amount { flex: none; font: var(--type-body-strong); text-align: right; }
.list__amount--in { color: var(--valid); }      /* money received */
.list__amount--due { color: var(--pending); }   /* overdue */
.list__amount--void { color: var(--text-2); text-decoration: line-through; }
.list__chev { color: var(--text-2); }
.list__unread { flex: none; width: 8px; height: 8px; margin-top: 7px; border-radius: var(--r-pill); background: var(--blue); }
/* Current choice in a picker list (estate switcher). */
.list__check { flex: none; color: var(--blue); stroke-width: 1.75; }
/* Expanded detail under a row (join request): sits inside the same <li>. */
.list__panel { display: flex; flex-direction: column; gap: var(--s12); padding: 0 var(--s16) var(--s16); background: var(--white); }

/* Setup checklist mark: empty ring, then valid-filled tick. */
.check-circle { flex: none; width: 24px; height: 24px; border-radius: var(--r-pill); border: var(--bw-mark) solid var(--rule-strong); display: flex; align-items: center; justify-content: center; color: var(--valid); }
.check-circle svg { display: none; width: 14px; height: 14px; stroke-width: 2.2; }
.check-circle.is-done { border-color: var(--valid); background: var(--valid-tint); }
.check-circle.is-done svg { display: block; }
.list__title.is-done { color: var(--text-2); }

/* Done mark: the tick at the top of a "finished" page or confirmation card. */
.done-mark { flex: none; width: 48px; height: 48px; border-radius: var(--r-pill); background: var(--valid-tint); color: var(--valid); display: flex; align-items: center; justify-content: center; }
.done-mark svg { width: 24px; height: 24px; stroke-width: 1.75; }
.done-mark--sm { width: 32px; height: 32px; }
.done-mark--sm svg { width: 18px; height: 18px; }

.avatar {
  flex: none; width: 36px; height: 36px; border-radius: var(--r-pill); background: var(--slate-75); color: var(--text-2);
  font: var(--type-meta-strong); display: flex; align-items: center; justify-content: center;
}
.avatar--lg { width: 56px; height: 56px; font: var(--type-card-title); }   /* profile */
/* You, not someone else: your own resident code row. */
.avatar--accent { background: var(--blue-tint); color: var(--blue); }

/* Badge: status word + icon on a tint. The icon is mandatory for the four
   status variants (never colour alone); --accent and --neutral are labels,
   not statuses, and carry no icon. */
.badge {
  flex: none; display: inline-flex; align-items: center; gap: var(--s4); padding: var(--s2) var(--s8);
  border-radius: var(--r-sm); font: var(--type-badge); white-space: nowrap;
  background: var(--neutral-tint); color: var(--neutral);
}
.badge svg { width: 12px; height: 12px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.badge--valid { background: var(--valid-tint); color: var(--valid); }
.badge--pending { background: var(--pending-tint); color: var(--pending); }
.badge--danger { background: var(--danger-tint); color: var(--danger); }
.badge--inactive { background: var(--inactive-tint); color: var(--inactive-text); }
.badge--accent { background: var(--accent-tint); color: var(--accent); }
.badge--neutral { /* = base */ }

.count {
  display: inline-block; min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--r-pill);
  background: var(--blue); color: var(--text-on); font: var(--type-micro); text-align: center;
}

.dot { flex: none; width: 10px; height: 10px; border-radius: var(--r-pill); background: var(--text-2); margin-top: 5px; }
.dot--valid { background: var(--valid); }
.dot--pending { background: var(--pending); }
.dot--danger { background: var(--danger); }

.stats { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); overflow: hidden; }
.stats__cell { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s12) var(--s16); border-right: var(--bw) solid var(--rule); margin-right: -1px; text-align: left; }
.stats__n { font: var(--type-stat); }
.stats__l { font: var(--type-meta); color: var(--text-2); }
.stats__n--valid { color: var(--valid); }
.stats__n--pending { color: var(--pending); }
/* Stat cells that filter the list below (Maintenance) */
button.stats__cell { border: 0; border-right: var(--bw) solid var(--rule); background: var(--white); }
button.stats__cell.is-active { background: var(--blue-wash); }
/* Four stats: two by two. */
.stats--grid { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stats--grid .stats__cell { border-bottom: var(--bw) solid var(--rule); margin-bottom: -1px; }

/* Hero figure: the one number a money screen is about. */
.figure { display: flex; flex-direction: column; gap: var(--s4); padding: var(--s18); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); }
.figure__label { font: var(--type-meta-strong); color: var(--text-2); }
.figure__n { font: var(--type-figure); }
.figure__n--pending { color: var(--pending); }
.figure__n--valid { color: var(--valid); }
.figure__sub { font: var(--type-secondary); text-wrap: pretty; }

/* Card. The exception to "lists over cards": one self-contained document
   (a notice, a receipt, a job). If there are several, they're rows. */
.card { border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); padding: var(--s20); display: flex; flex-direction: column; gap: var(--s12); }
.card--compact { padding: var(--s16); }
.card__title { font: var(--type-card-title); text-wrap: pretty; }
.card__body { font: var(--type-reading); white-space: pre-wrap; text-wrap: pretty; }
.card__foot { display: flex; flex-direction: column; gap: var(--s2); padding-top: var(--s12); border-top: var(--bw) solid var(--rule); }
.card__from { font: var(--type-control); }
.card__audience { font: var(--type-meta); color: var(--text-2); text-wrap: pretty; }
/* Notice header: source label + date. Label is a .badge. */
.card__head { display: flex; justify-content: space-between; align-items: center; gap: var(--s10); }
.card__date { font: var(--type-meta); color: var(--text-2); }

.attachment {
  display: flex; align-items: center; gap: var(--s10); min-height: var(--h-btn); padding: var(--s8) var(--s12);
  border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--slate-25); font: var(--type-control); text-align: left;
}
.attachment svg { color: var(--text-2); }

.kv { display: flex; flex-direction: column; gap: var(--s6); }
.kv__line { display: flex; justify-content: space-between; gap: var(--s12); font: var(--type-secondary); }
.kv__v { font-weight: var(--w-strong); text-align: right; }

/* Receipt. A card with a ruled total and an optional VOID stamp. The stamp is
   the only rotated element in the product; keep it that way. */
.receipt { position: relative; overflow: hidden; gap: var(--s14); }
.receipt__total { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s14) 0; border-top: var(--bw) solid var(--rule); border-bottom: var(--bw) solid var(--rule); }
.receipt__total-l { font: var(--type-meta); color: var(--text-2); }
.receipt__total-n { font: var(--type-total); }
.receipt__stamp {
  position: absolute; top: 42%; left: 50%; transform: translate(-50%, -50%) rotate(-12deg);
  padding: var(--s6) var(--s18); border: 3px solid var(--danger); border-radius: var(--r-md);
  color: var(--danger); font: 600 30px/36px var(--font); background: var(--stamp-bg);
}

.meter { display: block; width: 100%; height: 6px; border-radius: 3px; background: var(--slate-50); overflow: hidden; }
.meter__fill { display: block; width: var(--value, 0%); height: 100%; background: var(--blue); }   /* style="--value:40%" */
.meter__fill--valid { background: var(--valid); }
.meter__fill--pending { background: var(--pending); }
.meter__fill--muted { background: var(--rule-strong); }
.meter__fill--ink { background: var(--ink); }   /* leading result in a closed poll */

/* Stage tracker: where a complaint/job is. Bars up to and including the
   current step are blue; only the current label is Ink and strong. */
.stages { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s4); }
.stages__step { display: flex; flex-direction: column; gap: var(--s6); }
.stages__bar { height: 4px; border-radius: var(--r-hair); background: var(--rule); }
.stages__label { font: var(--type-small); color: var(--text-2); }
.stages__step.is-done .stages__bar, .stages__step.is-current .stages__bar { background: var(--blue); }
.stages__step.is-current .stages__label { font-weight: var(--w-strong); color: var(--ink); }

/* "What happens" box in a confirm sheet. */
.bullets { display: flex; flex-direction: column; gap: var(--s8); padding: var(--s14) var(--s16); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); }
.bullets__head { font: var(--type-meta-strong); color: var(--text-2); }
.bullets__item { display: flex; gap: var(--s10); align-items: flex-start; font: var(--type-secondary); text-wrap: pretty; }
.bullets__item::before { content: ""; flex: none; width: 5px; height: 5px; border-radius: var(--r-pill); background: var(--text-2); margin-top: var(--s8); }

/* Preview of exactly what recipients will receive. Tinted with the meaning
   of the message (danger for an alert, valid for an all-clear). */
.preview { display: flex; flex-direction: column; gap: var(--s4); padding: var(--s12) var(--s14); border-radius: var(--r-lg); background: var(--slate-50); }
.preview--danger { background: var(--danger-tint); }
.preview--valid { background: var(--valid-tint); }
.preview__label { font: var(--type-small-strong); color: var(--text-2); }
.preview__text { font: var(--type-secondary); text-wrap: pretty; }
.preview__text--strong { font: var(--type-body-strong); }

/* A code shown for someone to read out or type (pairing code, resident code).
   Not the Pass: no strip, no ticket shape. */
.bigcode { display: flex; flex-direction: column; align-items: center; gap: var(--s6); padding: var(--s24) var(--s16); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); text-align: center; }
.bigcode__label { font: var(--type-meta-strong); color: var(--text-2); }
.bigcode__code { font: var(--type-code); letter-spacing: var(--track-code); }
.bigcode__code--pairing { letter-spacing: var(--track-pairing); }
/* Electricity token: 20 digits in five groups of four. Smaller than a gate
   code so it fits the column on two lines at most; balance keeps the groups whole. */
.bigcode__code--token { font: var(--type-title); letter-spacing: var(--track-code); text-wrap: balance; }
.bigcode__sub { font: var(--type-meta); color: var(--text-2); }

.numbered { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--s12); }
.numbered__item { display: flex; gap: var(--s12); align-items: flex-start; }
.numbered__n { flex: none; width: 24px; height: 24px; border-radius: var(--r-pill); background: var(--blue-tint); color: var(--blue); font: var(--type-meta-strong); display: flex; align-items: center; justify-content: center; }

/* Upload preview table (bulk invite). The only table: admins checking a CSV. */
.table-wrap { border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); overflow: auto; }
.table { width: 100%; border-collapse: collapse; font: var(--type-meta); white-space: nowrap; }
.table th { text-align: left; font-weight: var(--w-strong); padding: var(--s8) var(--s12); background: var(--slate-50); }
.table td { padding: var(--s8) var(--s12); border-top: var(--bw) solid var(--rule); color: var(--text-2); }

/* Photo placeholder: what a real user photo is replaced by in prototypes. */
.photo {
  border-radius: var(--r-lg); aspect-ratio: 4 / 3; display: flex; align-items: flex-end; padding: var(--s8);
  background: repeating-linear-gradient(135deg, var(--slate-50) 0 8px, var(--stripe) 8px 16px);
  font: 400 11px/14px var(--mono); color: var(--text-2);
}
.photo--square { aspect-ratio: 1; }
.photo--thumb { flex: none; width: 44px; height: 44px; aspect-ratio: auto; padding: 0; align-items: center; justify-content: center; font: var(--type-small-strong); }
.photo--taken { background: repeating-linear-gradient(135deg, var(--valid-tint) 0 10px, var(--valid-stripe) 10px 20px); color: var(--valid); align-items: center; justify-content: center; }
/* Camera viewfinder (live) and a full-width captured photo. */
.photo--viewfinder { aspect-ratio: auto; height: 220px; align-items: center; justify-content: center; font: 400 14px/18px var(--mono); }
.photo--wide { aspect-ratio: auto; height: 140px; align-items: center; justify-content: center; font: 400 14px/18px var(--mono); }
/* On a coloured gate result: translucent white stripes. */
.photo--inverse { background: repeating-linear-gradient(135deg, var(--on-fill) 0 10px, var(--on-fill-2) 10px 20px); color: var(--text-on); }
/* A person's face: walk-in request, walk-in waiting. */
.photo--portrait { flex: none; width: 88px; height: 88px; aspect-ratio: auto; padding: var(--s6); font-size: 10px; }
.photo-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s8); }


/* ==========================================================================
   08  Feedback & states
   ========================================================================== */
/* Banner: an inline, tinted callout at the top of a page or in a sheet. Text
   stays Ink; only the icon takes the status colour. */
.banner { display: flex; gap: var(--s10); align-items: flex-start; padding: var(--s12) var(--s14); border-radius: var(--r-lg); background: var(--slate-50); font: var(--type-secondary); text-wrap: pretty; }
.banner > svg { margin-top: 1px; color: var(--text-2); }
.banner--warn { background: var(--pending-tint); }
.banner--warn > svg { color: var(--pending); }
.banner--ok { background: var(--valid-tint); }
.banner--ok > svg { color: var(--valid); }
.banner--danger { background: var(--danger-tint); }
.banner--danger > svg { color: var(--danger); }
.banner--strong { font: var(--type-body-strong); padding: var(--s14) var(--s16); }
/* Inside .gate: 17px strong, no icon. Combine with --warn. */
.banner--gate { font: var(--type-g-strong); padding: var(--s14) var(--s16); }

/* Alert card: an emergency that concerns you. Bigger type than a banner,
   and the only thing that pulses. .is-live adds the pulse; drop it once
   someone has acknowledged. */
.alert { display: flex; flex-direction: column; gap: var(--s8); padding: var(--s16); border-radius: var(--r-lg); background: var(--danger-tint); }
.alert.is-live { animation: avPulse var(--pulse); }
.alert__kicker { font: var(--type-small-strong); color: var(--danger); }
.alert__text { font: var(--type-lead); text-wrap: pretty; }
.alert__sub { font: var(--type-secondary); text-wrap: pretty; }
.alert__action { align-self: flex-start; height: var(--h-touch); padding: 0 var(--s14); border: var(--bw) solid var(--danger); border-radius: var(--r-lg); background: var(--white); font: var(--type-control); }
.alert--clear { background: var(--valid-tint); }
.alert--clear .alert__kicker { color: var(--valid); }
.alert--clear .alert__action { border-color: var(--valid); }

.empty { padding: var(--s16); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); color: var(--text-2); text-wrap: pretty; }

.hint { font: var(--type-meta); color: var(--text-2); text-wrap: pretty; }
.hint--small { font: var(--type-small); }

/* Toast: past-tense confirmation of what just happened. Ink, bottom, above
   the tab bar. Never carries an action or an error. */
.toast {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 76px; z-index: 40;
  width: calc(100% - 32px); max-width: calc(var(--column) - 32px);
  display: flex; gap: var(--s10); align-items: flex-start; padding: var(--s12) var(--s14);
  border-radius: var(--r-lg); background: var(--ink); color: var(--text-on); font: var(--type-body);
  box-shadow: var(--shadow-float); animation: avFade var(--t-fast) var(--ease);
}
.toast > svg { margin-top: 2px; }


/* ==========================================================================
   09  Overlays
   ========================================================================== */
.scrim { position: fixed; inset: 0; z-index: 20; background: var(--scrim); animation: avFade var(--t-fast) var(--ease); }
.scrim--interrupt { z-index: 30; background: var(--scrim-interrupt); }

/* Bottom sheet: every confirm, form and choice that isn't a whole page.
   Grid with minmax(0,1fr) so long unbroken text can't widen it. */
.sheet {
  position: fixed; left: 50%; bottom: 0; transform: translate(-50%, 0); z-index: 21;
  width: 100%; max-width: var(--column); max-height: 90vh; overflow: auto;
  display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--s16);
  padding: var(--s8) var(--gutter) var(--s24); background: var(--paper);
  border-radius: var(--r-xl) var(--r-xl) 0 0; box-shadow: var(--shadow-sheet); animation: avSheet var(--t-base) var(--ease);
}
.sheet__grabber { justify-self: center; width: 36px; height: 4px; border-radius: var(--r-hair); background: var(--rule); }
.sheet__title { font: var(--type-section); text-wrap: pretty; }
.sheet__body { color: var(--text-2); text-wrap: pretty; }
.sheet__actions { display: flex; flex-direction: column; gap: var(--s8); }

/* Pop card: drops from the top for something that needs a response now
   (walk-in request) or to preview a push notification. Distinct from the
   sheet: it comes from the system, not from something you tapped. */
.popcard {
  position: fixed; left: 50%; top: var(--s12); transform: translateX(-50%); z-index: 31;
  width: calc(100% - 24px); max-width: calc(var(--column) - 24px);
  display: flex; flex-direction: column; gap: var(--s14); padding: var(--s16);
  background: var(--white); border: var(--bw) solid var(--rule); border-radius: var(--r-xl); box-shadow: var(--shadow-pop);
}
.popcard__source { font: var(--type-meta-strong); color: var(--text-2); }
.popcard__text { font: var(--type-card-title); text-wrap: pretty; }
.popcard__note { font: var(--type-small); color: var(--text-2); }
.popcard__who { display: flex; gap: var(--s12); align-items: center; }
.popcard__name { font: var(--type-section); }
.popcard__meta { font: var(--type-meta); color: var(--text-2); }
.popcard__actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s8); }
.popcard--status { flex-direction: row; align-items: center; gap: var(--s12); }
.popcard--status .popcard__text { flex: 1; font: var(--type-body-strong); }


/* ==========================================================================
   10  Gate (guard) & Pass
   The guard is the same product, louder: 2px borders, 56–72px targets,
   nothing under 17px, Ink as the primary fill (blue on white loses contrast
   in direct sun; Ink doesn't). Don't "align" these with the app values.
   ========================================================================== */
.gate { flex: 1; display: flex; flex-direction: column; background: var(--white); font: var(--type-g-body); }
.gate__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s12); padding: var(--s12) var(--s16); border-bottom: var(--bw) solid var(--rule); }
.gate__name { font: var(--type-g-title); }
.gate__id { min-width: 0; display: flex; flex-direction: column; }
.gate__estate { font: var(--type-body); line-height: 20px; color: var(--text-2); }
.gate__body { display: flex; flex-direction: column; gap: var(--s14); padding: var(--s4) var(--s16) var(--s20); }
.gate__title { font: var(--type-g-title); }
.gate__heading { font: var(--type-g-heading); }
.gate__sub { color: var(--text-2); text-wrap: pretty; }
/* Whole-screen message (device unpaired) and the device setup page. */
.gate__empty { flex: 1; display: flex; flex-direction: column; gap: var(--s16); padding: var(--s40) var(--gutter); }
.gate--setup { padding: var(--s32) var(--gutter); gap: var(--s24); }
.gate-label { font: var(--type-g-strong); }
.gate-note { font: var(--type-body); color: var(--text-2); text-wrap: pretty; }

/* Walk-in destination estate: name + relation, 72px. */
.gate-dest {
  width: 100%; min-height: var(--h-g-lg); padding: var(--s12) var(--s16); display: flex; flex-direction: column; gap: var(--s2);
  border: var(--bw-g) solid var(--rule); border-radius: var(--r-lg); background: var(--white); color: var(--ink); text-align: left;
}
.gate-dest__name { font: var(--type-g-lead); }
.gate-dest__desc { font: var(--type-body); color: var(--text-2); }

/* Offline manual process. Numbered, 2px, Ink numbers (not the app's blue .numbered). */
.gate-steps { margin: 0; padding: 0; list-style: none; border: var(--bw-g) solid var(--rule); border-radius: var(--r-lg); overflow: hidden; }
.gate-steps__item { display: flex; gap: var(--s12); padding: var(--s14) var(--s16); border-top: var(--bw-g) solid var(--rule); }
.gate-steps__item:first-child { border-top: 0; }
.gate-steps__n { flex: none; width: 32px; height: 32px; border-radius: var(--r-pill); background: var(--ink); color: var(--text-on); font-weight: var(--w-strong); display: flex; align-items: center; justify-content: center; }

/* Horizontal scroller of gate options (block picker). */
.gate-row { display: flex; gap: var(--s8); overflow-x: auto; }
.gate-row > .gate-option { flex: none; min-height: var(--h-btn); padding: 0 var(--s16); }

/* Walk-in waiting: elapsed time and what the system has done so far. */
.gate-timer { display: flex; align-items: baseline; gap: var(--s10); }
.gate-timer__n { font: var(--type-code); }
.gate-timer__l { color: var(--text-2); }
.gate-checks { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--s8); }
.gate-checks__item { display: flex; gap: var(--s10); align-items: flex-start; }
.gate-checks__item > svg { margin-top: var(--s2); color: var(--valid); stroke-width: 2; }

.gate-status { flex: none; display: inline-flex; align-items: center; gap: var(--s6); height: var(--h-sm); padding: 0 var(--s12); border-radius: var(--r-sm); font: var(--type-body-strong); background: var(--valid-tint); color: var(--valid); }
.gate-status::before { content: ""; width: 8px; height: 8px; border-radius: var(--r-pill); background: currentColor; }
.gate-status--offline { background: var(--pending-tint); color: var(--pending); }

.gate-nav { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s8); padding: var(--s12) var(--s16); }
.gate-nav__tab { height: var(--h-g-sm); border: var(--bw-g) solid var(--rule); border-radius: var(--r-lg); background: var(--white); font: 600 16px/22px var(--font); }
.gate-nav__tab.is-active { background: var(--ink); border-color: var(--ink); color: var(--text-on); }
/* In-panel switcher (Walk-in / Manual entry; Log states). Same tabs as
   .gate-nav, but sized to its tabs and flush with the panel: equal columns,
   no own padding. Five tabs fit a 360px phone with short labels. */
.gate-nav--sub { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 1fr; padding: 0; }
.gate-nav--sub .gate-nav__tab { padding-left: var(--s4); padding-right: var(--s4); }
/* Gate device bottom nav: the shared .tabbar, sized for guards. Four tabs
   (Check · Walk-in · Log · Alerts). Walk-in holds walk-ins and manual
   entry, Log holds Inside now and today's log; their sub-views use
   .gate-nav at the top. Ink active (not blue), 64px tabs, 24px icons. */
.tabbar--gate .tabbar__tab { min-height: var(--h-g-md); font: var(--type-g-strong); color: var(--text-2); }
.tabbar--gate .tabbar__tab.is-active { color: var(--ink); border-top-color: var(--ink); }
.tabbar--gate .icon { width: 24px; height: 24px; }

.gate-btn { width: 100%; height: var(--h-g-lg); border: 0; border-radius: var(--r-lg); background: var(--ink); color: var(--text-on); font: var(--type-g-btn); }
.gate-btn--md { height: var(--h-g-md); font: var(--type-lead); }
.gate-btn--sm { height: var(--h-g-sm); font: var(--type-g-strong); }
.gate-btn--outline { background: var(--white); color: var(--ink); border: var(--bw-g) solid var(--ink); }
.gate-btn--quiet { background: var(--white); color: var(--ink); border: var(--bw-g) solid var(--rule); }
/* Blue only where the guard hands a decision to residents ("Ask the residents"). */
.gate-btn--ask { background: var(--blue); }
/* On a full-bleed result: inverse white, or a 2px white outline. */
.gate-btn--inverse { background: var(--white); color: var(--ink); }
.gate-btn--ghost { background: transparent; color: var(--text-on); border: var(--bw-g) solid var(--white); }
.gate-option { min-height: var(--h-g-sm); padding: var(--s6); border: var(--bw-g) solid var(--ink); border-radius: var(--r-lg); background: var(--white); color: var(--ink); font: var(--type-g-strong); }
.gate-option.is-selected { background: var(--ink); color: var(--text-on); }
/* On a coloured result (refuse reasons). */
/* Units in the walk-in grid: quiet rule border; Ink border is for picks that
   stay selected (block, direction, reason). */
.gate-option--quiet { border-color: var(--rule); }
.gate-option--inverse { border-color: var(--white); background: transparent; color: var(--text-on); }
.gate-option--inverse.is-selected { background: var(--white); color: var(--ink); }
.gate-btn--refuse { background: var(--white); color: var(--danger); }
.gate-btn--valid { background: var(--valid); }
.gate-btn--inline { width: auto; flex: none; padding: 0 var(--s16); }

/* Gate input focus is an Ink border, not the blue ring. */
.gate-input { width: 100%; height: var(--h-g-sm); padding: 0 var(--s16); border: var(--bw-g) solid var(--rule-strong); border-radius: var(--r-lg); background: var(--white); font: var(--type-g-input); outline: none; }
.gate-input:focus { border-color: var(--ink); }
.gate-input--area { height: auto; padding: var(--s12) var(--s16); font: var(--type-g-body); resize: vertical; }
/* Plate number on a result: borderless white on colour. */
.gate-input--plate { border: 0; font: var(--type-g-lead); text-transform: uppercase; }
.gate-input--pairing { height: var(--h-g-lg); border-color: var(--ink); font: 600 34px/40px var(--font); letter-spacing: var(--track-pairing); text-transform: uppercase; }
.gate-error { font: var(--type-g-strong); color: var(--pending); }

/* As a link (milestone-1 empty states). Same box as the button. */
a.gate-btn { display: flex; align-items: center; justify-content: center; text-decoration: none; }

.codeboxes { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--s6); }
.codebox { height: var(--h-g-md); border: var(--bw-g) solid var(--rule); border-radius: var(--r-lg); display: flex; align-items: center; justify-content: center; font: var(--type-g-digit); }
.codebox.is-active { border-color: var(--ink); }
.codebox.is-invalid { border-color: var(--pending); }

.keypad { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s8); }
.keypad__key { height: var(--h-g-md); border: var(--bw-g) solid var(--rule); border-radius: var(--r-lg); background: var(--white); font: var(--type-g-digit); }
.keypad__key:active { background: var(--rule); }
.keypad__key--fn { background: var(--slate-50); font: var(--type-g-strong); }

/* Full-width solid strips. The only places a status colour is a fill outside
   a result: the guard has no time to read a tint. */
.gate-strip { padding: var(--s14) var(--s16); background: var(--pending); color: var(--text-on); font: var(--type-g-strong); }
.gate-strip--back { display: flex; align-items: center; justify-content: space-between; gap: var(--s12); background: var(--valid-tint); color: var(--ink); padding: var(--s12) var(--s16); }

.gate-card { display: flex; flex-direction: column; gap: var(--s10); padding: var(--s14) var(--s16); border: var(--bw-g) solid var(--rule); border-radius: var(--r-lg); }
.gate-card__name { font: var(--type-lead); }
.gate-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--s10); }
.gate-card__body { min-width: 0; display: flex; flex-direction: column; }
.gate-card__meta { font: var(--type-body); color: var(--text-2); }
.gate-card__who { display: flex; gap: var(--s12); align-items: center; }
.gate-card .photo--portrait { width: 64px; height: 64px; }
.gate-badge { flex: none; display: inline-flex; padding: var(--s4) var(--s10); border-radius: var(--r-sm); background: var(--pending); color: var(--text-on); font: var(--type-control); }

/* Guard alert takeover. Unified to 10px radius (Safety had 12). */
.gate-alert { display: flex; flex-direction: column; gap: var(--s12); margin: var(--s12) var(--s16) 0; padding: var(--s18); border-radius: var(--r-lg); background: var(--danger); color: var(--text-on); }
.gate-alert.is-live { animation: avPulse var(--pulse); }
.gate-alert__kicker { font: var(--type-control); line-height: 18px; opacity: 0.92; }
.gate-alert__word { font: var(--type-g-alert); }
.gate-alert__where { font: var(--type-g-title); }
.gate-alert__line { font: var(--type-g-strong); text-wrap: pretty; }
/* Shared alert from a connected estate: white card, red 3px frame, so it's
   clearly "not your estate" at arm's length. */
.gate-alert--shared { background: var(--white); color: var(--ink); border: 3px solid var(--danger); }
.gate-alert--over { background: var(--slate-50); color: var(--ink); border: var(--bw-g) solid var(--rule); }

/* Gate result: the whole screen is the answer. Inactive uses Graphite, not
   #8A9099, because white 48px type on #8A9099 is below 3:1. */
.result {
  display: flex; flex-direction: column; gap: var(--s18); padding: var(--s32) var(--gutter) var(--s24);
  min-height: 100%; background: var(--valid); color: var(--text-on);
  clip-path: circle(150% at 50% 82%); transition: clip-path var(--t-reveal) var(--ease);
}
.result--pending { background: var(--pending); }
.result--danger { background: var(--danger); }
.result--inactive { background: var(--text-2); }
.result--resident { background: var(--blue); }
/* In the app the result covers the gate screen. Script adds .is-entering on
   open and removes it a frame later so the circle spreads from the button. */
.result--screen { position: fixed; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); width: 100%; max-width: var(--column); z-index: 25; overflow: auto; }
.result.is-entering { clip-path: circle(0% at 50% 82%); }
.result__main { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s12); text-align: center; }
.result__icon { width: 96px; height: 96px; border-radius: var(--r-pill); border: 4px solid var(--white); display: flex; align-items: center; justify-content: center; }
.result__icon svg { width: 52px; height: 52px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.result__word { font: var(--type-g-word); }
.result__name { font: var(--type-g-name); }
.result__dest { font: var(--type-lead); }
.result__note { font: 600 18px/26px var(--font); max-width: 380px; text-wrap: pretty; }
.result__actions { display: flex; flex-direction: column; gap: var(--s10); }
.result__refuse { min-height: var(--h-btn); border: 0; background: none; color: var(--text-on); font: var(--type-g-strong); text-decoration: underline; }

/* The Pass. The only bold object in the product; nothing else may borrow its
   strip or ticket shadow. Strip colour = status; blue = resident code. */
.pass { width: 100%; max-width: 360px; margin: 0 auto; background: var(--white); border: var(--bw) solid var(--rule); border-radius: var(--r-xl); overflow: hidden; box-shadow: var(--shadow-pass); }
.pass__strip { height: 10px; background: var(--valid); }
.pass--pending .pass__strip { background: var(--pending); }
.pass--danger .pass__strip { background: var(--danger); }
.pass--inactive .pass__strip { background: var(--inactive); }
.pass--resident .pass__strip { background: var(--blue); }
.pass__body { display: flex; flex-direction: column; align-items: center; gap: var(--s14); padding: var(--s16) var(--gutter) var(--gutter); text-align: center; }
.pass__head { width: 100%; display: flex; justify-content: space-between; align-items: center; gap: var(--s8); }
.pass__kind { font: var(--type-small-strong); color: var(--text-2); }
.pass__status { font: var(--type-meta-strong); color: var(--valid); }
.pass--pending .pass__status { color: var(--pending); }
.pass--danger .pass__status { color: var(--danger); }
.pass--inactive .pass__status { color: var(--inactive-text); }
.pass--resident .pass__status { color: var(--blue); }
.pass__who { display: flex; flex-direction: column; gap: var(--s2); }
.pass__place { font: var(--type-meta); color: var(--text-2); }
.pass__person { font: var(--type-section); }
.pass__code { display: flex; gap: var(--s18); font: var(--type-code); letter-spacing: var(--track-code); }
.pass__qr { padding: var(--s12); background: var(--white); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); }
.pass__qr-img { display: block; width: 164px; height: 164px; }
.pass--inactive .pass__qr-img { opacity: 0.25; }
.pass__validity { font: var(--type-body-strong); }

/* Public pass page: what a visitor opens from the shared link. Slate behind
   so the white Pass lifts off it. No estate chrome; Acevelt .brand on top. */
.passpage { flex: 1; display: flex; flex-direction: column; gap: var(--s18); padding: var(--s20) var(--s16) var(--s32); background: var(--slate-50); }
.passpage__head { display: flex; flex-direction: column; gap: var(--s4); text-align: center; }
.passpage__title { font: var(--type-title); text-wrap: pretty; }
.passpage__sub { color: var(--text-2); text-wrap: pretty; }
.passpage__msg { padding: var(--s14) var(--s16); border-radius: var(--r-lg); background: var(--white); text-align: center; text-wrap: pretty; }
.passpage__msg--warn { background: var(--pending-tint); }
.passpage__route { font: var(--type-secondary); color: var(--text-2); text-align: center; text-wrap: pretty; }
.passpage__privacy { display: flex; flex-direction: column; gap: var(--s4); padding: var(--s14) var(--s16); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); background: var(--white); font: var(--type-meta); color: var(--text-2); text-wrap: pretty; }
.passpage__privacy-title { font: var(--type-meta-strong); color: var(--ink); }


/* ==========================================================================
   11  Documentation chrome — specimen pages only. The app does not need any
   of this; strip it from production builds.
   ========================================================================== */
.doc { background: var(--slate-50); min-height: 100vh; }
.doc-header { position: sticky; top: 0; z-index: 50; background: var(--paper); border-bottom: var(--bw) solid var(--rule); }
.doc-header__inner { max-width: 1120px; margin: 0 auto; padding: var(--s12) var(--s24); display: flex; flex-wrap: wrap; align-items: center; gap: var(--s12) var(--s24); }
.doc-brand { display: flex; align-items: center; gap: var(--s8); font: var(--type-section); color: var(--ink); }
.doc-brand:hover { color: var(--ink); text-decoration: none; }
.doc-nav { display: flex; flex-wrap: wrap; gap: var(--s4); }
.doc-nav__link { padding: var(--s6) var(--s10); border-radius: var(--r-sm); font: var(--type-control); color: var(--text-2); }
.doc-nav__link:hover { background: var(--slate-50); color: var(--ink); text-decoration: none; }
.doc-nav__link.is-active { background: var(--ink); color: var(--text-on); }
.doc-main { max-width: 1120px; margin: 0 auto; padding: var(--s32) var(--s24) 64px; display: flex; flex-direction: column; gap: var(--s24); }
.doc-intro { max-width: 70ch; display: flex; flex-direction: column; gap: var(--s8); }
.doc-intro h1 { font: 600 32px/38px var(--font); }
.doc-intro p { color: var(--text-2); text-wrap: pretty; }
.doc-card { background: var(--white); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); overflow: hidden; }
.doc-card__head { padding: var(--s20) var(--s24) var(--s16); display: flex; flex-direction: column; gap: var(--s6); }
.doc-eyebrow { font: var(--type-small-strong); color: var(--blue); }
.doc-card__name { font: var(--type-title); }
.doc-card__desc { max-width: 72ch; color: var(--text-2); text-wrap: pretty; }
.doc-sig { margin-top: var(--s4); padding: var(--s8) var(--s12); border-radius: var(--r-sm); background: var(--slate-50); font: 400 13px/18px var(--mono); color: var(--ink); overflow-x: auto; white-space: nowrap; }
.doc-states { display: flex; flex-wrap: wrap; gap: var(--s24); align-items: flex-start; padding: var(--s24); background: var(--paper); border-top: var(--bw) solid var(--rule); border-bottom: var(--bw) solid var(--rule); }
.doc-state { display: flex; flex-direction: column; gap: var(--s10); min-width: 0; flex: 1 1 260px; max-width: 420px; }
.doc-state--wide { flex-basis: 100%; max-width: none; }
.doc-state--auto { flex: 0 0 auto; }
.doc-state__label { font: var(--type-meta-strong); color: var(--text-2); }
.doc-card__foot { padding: var(--s12) var(--s24); font: var(--type-meta); color: var(--text-2); }
.doc-card__foot strong { color: var(--ink); font-weight: var(--w-strong); }
.doc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s12); width: 100%; }
.doc-swatch { display: flex; flex-direction: column; gap: var(--s6); font: var(--type-meta); }
.doc-swatch__chip { height: 56px; border-radius: var(--r-lg); border: var(--bw) solid var(--rule); }
.doc-swatch__name { font: var(--type-meta-strong); }
.doc-swatch__hex { font: 400 12px/16px var(--mono); color: var(--text-2); }
.doc-table { width: 100%; border-collapse: collapse; font: var(--type-secondary); }
.doc-table th { text-align: left; font: var(--type-meta-strong); color: var(--text-2); padding: var(--s8) var(--s12) var(--s8) 0; border-bottom: var(--bw) solid var(--rule); }
.doc-table td { padding: var(--s10) var(--s12) var(--s10) 0; border-bottom: var(--bw) solid var(--rule); vertical-align: middle; }
.doc-table code, .doc-code { font: 400 12px/16px var(--mono); }
.doc-bar { height: 12px; background: var(--blue); border-radius: var(--r-hair); }
/* A phone-width frame so fixed-position components render in place. */
.doc-frame { position: relative; width: 100%; max-width: var(--column); border: var(--bw) solid var(--rule); border-radius: var(--r-lg); overflow: hidden; background: var(--paper); }
.doc-frame--tall { min-height: 640px; display: flex; flex-direction: column; }
.doc-frame--dim { background: var(--scrim); padding-top: 120px; }
.doc-frame .app { min-height: 0; border: 0; flex: 1; }
.doc-frame .appbar, .doc-frame .tabbar, .doc-frame .footbar { position: relative; }
.doc-inplace { position: relative !important; left: auto !important; top: auto !important; bottom: auto !important; transform: none !important; width: 100% !important; max-width: none !important; animation: none !important; }
.doc-spacer { width: var(--s16); flex: none; background: var(--blue-tint); }

/* Prototype bar ("View as" / "Try"). Lives above every prototype; not product UI. */
.proto-bar { background: var(--slate-50); border-bottom: var(--bw) solid var(--rule); padding: var(--s8) var(--s12); display: flex; flex-direction: column; gap: var(--s6); }
.proto-bar__label { font: var(--type-small-strong); color: var(--text-2); margin-right: var(--s4); }
.proto-bar__btn { height: var(--h-xs); padding: 0 var(--s10); border: var(--bw) solid var(--rule); border-radius: var(--r-sm); background: var(--white); font: var(--type-small-strong); }
.proto-bar__btn.is-active { background: var(--ink); color: var(--text-on); }
/* As a link (imp-templates/milestone-1: "Demo hub"). */
a.proto-bar__btn { display: inline-flex; align-items: center; color: var(--ink); text-decoration: none; }
