/* ═══════════════════════════════════════════════════════════════════════════
   Platinumlist design system — primitives and semantic tokens.

   There is no shared design-system.css package on this machine; the other
   ported dashboards inline their tokens, which is how ~30 rgba() literals of
   an old palette ended up hardcoded in one of them. This file exists so both
   pages of this project share ONE definition.

   Themes use CSS light-dark(). It resolves against `color-scheme`, so the
   theme switch is a color-scheme switch:

     :root                    color-scheme: light   (default — light loads first)
     [data-theme="light"]     color-scheme: light
     [data-theme="dark"]      color-scheme: dark

   Every semantic token below is therefore written once, not twice.
   ═══════════════════════════════════════════════════════════════════════════ */

@font-face {
  /* Bold is the ONLY licensed weight — never request another.
     Regular is not on this machine and preloading it 404s. */
  font-family: 'MD Nichrome';
  src: url('/fonts/MDNichrome-Bold.otf') format('opentype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light;

  /* ── Primitives ─────────────────────────────────────────────────────────
     Aussie is the neutral ramp, Poodle the brand purple. Named, not guessed:
     these are the values the rest of the estate already uses. */
  --aussie-100: #0F0F15;
  --aussie-95:  #18181E;
  --aussie-90:  #24242E;
  --aussie-80:  #2C2C38;
  --aussie-60:  #6E6E81;
  --aussie-40:  #9999AC;
  --aussie-30:  #B3B3C3;
  --aussie-20:  #ECEDF2;
  --aussie-10:  #F5F5F8;
  --aussie-0:   #FFFFFF;

  --poodle-primary: #7E05E8;
  --poodle-60:      #9737EC;
  --poodle-40:      #B169F1;
  --poodle-20:      #D8B4F8;

  --accent-success:  #30BB47;
  --accent-alert:    #F93643;
  --accent-warning:  #FF8300;   /* orange. NOT caution-yellow: it fails on white */
  --accent-caution:  #F9C54B;
  --accent-discount: #E8467C;
  --link-base:       #1A56FF;

  /* ── Semantic surfaces ──────────────────────────────────────────────────
     The page is grey and cards are white, never both white — a card has to be
     visible against the ground it sits on. */
  --bg-default:      light-dark(#FFFFFF, #0F0F15);
  --bg-secondary:    light-dark(#ECEDF2, #24242E);
  --bg-hover:        light-dark(#F5F5F8, #2C2C38);
  --bg-wash:         light-dark(#F5F5F8, #18181E);
  --bg-border:       light-dark(#DFE0E8, #34343F);
  --bg-border-hover: light-dark(#C4C5D2, #45454F);
  --bg-fade:         light-dark(rgba(15,15,21,0.45), rgba(0,0,0,0.62));

  /* ── Semantic content ───────────────────────────────────────────────────
     Three levels, and they are not interchangeable:
       primary    text you read
       secondary  labels, metadata, column headers — still read
       highlight  placeholder and disabled ONLY — decorative
     Mapping column headers to `highlight` renders them at #B3B3C3 and they
     effectively vanish. */
  --content-primary:   light-dark(#0F0F15, #FFFFFF);
  --content-secondary: light-dark(#6E6E81, #A6A6B8);
  --content-tertiary:  light-dark(#9999AC, #86869A);
  --content-highlight: light-dark(#B3B3C3, #6E6E81);
  --content-inverse:   light-dark(#FFFFFF, #0F0F15);

  /* ── Type ───────────────────────────────────────────────────────────────
     MD Nichrome is a DISPLAY face: headings and metric values only. Body,
     tables and labels stay on the UI face. */
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-accent: 'MD Nichrome', 'Inter', sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --font-family-default: var(--font);

  /* ── The system's own furniture ─────────────────────────────────────────
     Every page sets `viewport-fit=cover` and iOS is told the status bar is
     `black-translucent`, which together mean the page is drawn UNDERNEATH the
     notch and the home indicator. Nothing consumed the TOP inset until now, so
     an installed iPhone drew the clock and the battery straight over the
     Platinumlist logo. Read through variables rather than calling env()
     inline, so a test can override them — a browser at 375x812 reports 0 for
     both and would show the bug as fixed whether it is or not. */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);

  /* ── Shape, depth, motion ───────────────────────────────────────────────*/
  --radius-xs:   8px;
  --radius-sm:   10px;
  --radius-base: 14px;
  --radius-lg:   20px;

  /* The light-dark() goes around the COLOUR, not around the whole shadow.
     `light-dark()` is a colour function, so `light-dark(0 1px 2px rgba(...),
     ...)` does not parse — and a custom property is only validated where it is
     used, so nothing complained: the tokens looked fine and every
     `box-shadow: var(--shadow-*)` in the estate silently computed to `none`.
     The floating nav pill on a phone asks for --shadow-lg and had no shadow at
     all; so did the login card, the round menu and the alert drawer. Verified
     with CSS.supports: `color` accepts light-dark(), `box-shadow` refuses it.
     (2026-09-07.) */
  --shadow-sm: 0 1px 2px light-dark(rgba(15,15,21,0.06), rgba(0,0,0,0.4));
  --shadow-md: 0 4px 16px light-dark(rgba(15,15,21,0.10), rgba(0,0,0,0.5));
  --shadow-lg: 0 12px 32px light-dark(rgba(15,15,21,0.14), rgba(0,0,0,0.6));

  --duration-base: 160ms;
}

/* An explicit choice beats the OS. Both directions are declared so the toggle
   wins whichever way the system is set. */
[data-theme="light"] { color-scheme: light; }
[data-theme="dark"]  { color-scheme: dark; }

/* ═══════════════════════════════════════════════════════════════════════════
   Tab bar — icon over label in a pill, the active one filled.

   Defined here rather than in each page so the three pages cannot drift apart;
   a nav that looks slightly different on every screen reads as three separate
   tools rather than one. Mark the current page with aria-current="page" — the
   styling hangs off that attribute, so the highlight and the accessible state
   can never disagree.
   ═══════════════════════════════════════════════════════════════════════════ */
.pl-tabs{
  display:inline-flex; align-items:center; gap:2px; padding:5px;
  background: var(--bg-default);
  border:1px solid var(--bg-border);
  border-radius:999px;
  box-shadow: var(--shadow-sm);
}
.pl-tabs a{
  display:flex; flex-direction:column; align-items:center; gap:3px;
  min-width:66px; padding:6px 14px 5px;
  border-radius:999px; text-decoration:none;
  font-family: var(--font); font-size:11px; font-weight:500; line-height:1;
  color: var(--content-secondary);
  transition: color var(--duration-base);
  -webkit-tap-highlight-color: transparent;
}
.pl-tabs a .ic{
  display:flex; align-items:center; justify-content:center;
  width:32px; height:24px; border-radius:9px;
  transition: background var(--duration-base);
}
.pl-tabs a svg{
  width:17px; height:17px; display:block;
  fill:none; stroke:currentColor; stroke-width:1.7;
  stroke-linecap:round; stroke-linejoin:round;
}
.pl-tabs a:hover{ color: var(--content-primary); }
.pl-tabs a:focus-visible{ outline:2px solid var(--poodle-primary); outline-offset:2px; }

/* `.pl-tabs a{display:flex}` above is an author rule and therefore BEATS the
   browser's `[hidden]{display:none}`. Without this line the role-gated items
   (People, Roles, Activity) stay laid out for everybody even though nav.js has
   marked them hidden: on a phone that made the bottom pill five items wide —
   clamped to `calc(100vw - 16px)`, sprawling across the card underneath — for a
   person who may open only two of them, and `sizeToFit()` never applied
   `is-wide` because it correctly counted two. It also showed tabs that answer
   403. Same trap as `.sub-tabs[hidden]` and `.approx-tag[hidden]`: if a rule
   here sets `display`, it must opt back out of `[hidden]`. */
.pl-tabs a[hidden]{ display:none; }

/* Held by nav.js for the one fetch on a browser with no cached tab list, so the
   pill fades in already at its final width rather than painting narrow and
   growing. nav.js reveals it on the answer, on a failure, or after a timeout,
   so this class is never a state the pill can get stuck in. */
.pl-tabs{ transition: opacity var(--duration-base) ease; }
.pl-tabs.is-pending{ opacity:0; visibility:hidden; }
@media (prefers-reduced-motion: reduce){
  .pl-tabs{ transition:none; }
}

.pl-tabs a[aria-current="page"]{ color: var(--poodle-primary); font-weight:600; }
.pl-tabs a[aria-current="page"] .ic{ background: rgb(126 5 232 / 0.12); }

/* Poodle at full strength is too heavy on a dark ground — same paired value
   the rest of the app uses. */
[data-theme="dark"] .pl-tabs a[aria-current="page"]{ color: var(--poodle-40); }
[data-theme="dark"] .pl-tabs a[aria-current="page"] .ic{ background: rgb(177 105 241 / 0.18); }

@media (max-width: 560px){
  .pl-tabs a{ min-width:56px; padding:6px 9px 5px; font-size:10px; }
}

/* ── Mobile: the tabs move to the bottom ────────────────────────────────────
   Thumbs reach the bottom of a phone; they do not comfortably reach a nav
   stacked under a header. Fixed to the bottom as a floating pill, centred,
   clear of the home indicator.

   z-index 40 is deliberate and sits BELOW the modal (50) and the alert drawer
   (90/91) — a nav floating on top of a dialog is a nav you cannot dismiss. */
@media (max-width: 600px){
  .pl-tabs{
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(10px + var(--safe-bottom));
    z-index: 40;
    box-shadow: var(--shadow-lg);
    padding: 6px;
  }
  .pl-tabs a{ min-width: 72px; padding: 7px 12px 6px; font-size: 11px; }
  .pl-tabs a .ic{ height: 26px; }

  /* Four or five items do not fit a 375px phone at 72px each — 5 x 72 plus
     padding is wider than the screen, and the pill would sit edge to edge or
     clip. `is-wide` is set by nav.js from the count of items this person can
     actually see, so a Viewer with two keeps the roomier pill and only a role
     that really has five pays for them. Never let it exceed the viewport. */
  .pl-tabs{ max-width: calc(100vw - 16px); }
  .pl-tabs.is-wide a{ min-width: 56px; padding: 7px 6px 6px; font-size: 10px; }
  .pl-tabs.is-wide a .ic{ width: 28px; height: 24px; }

  /* The bar floats over the page, so the page has to end above it or the last
     element is unreachable — the footnote was sitting 41px behind it.
     `html body` and not `body`: every page sets `html, body{ padding:0 }` in
     its own <style>, which loads after this file and would otherwise win on
     equal specificity. */
  html body{ padding-bottom: calc(96px + var(--safe-bottom)); }

  /* iOS pins a position:fixed element to the VISUAL viewport, so when the
     keyboard opens the pill rides up with it and comes to rest on top of the
     form underneath — in the role editor it sat squarely over Save / Cancel /
     Delete role. Nobody needs the section nav while they are typing, so it
     stands down until the field is blurred (class set by nav.js). */
  .pl-tabs.is-typing{ display: none; }
}

/* ── "Made with Platinumlist AI" ────────────────────────────────────────────
   The attribution badge. Two <img> and not one recolourable SVG for the same
   reason `.brand` carries two: both logo files are outlined paths with a baked
   `fill`, so the theme is chosen by which file is SHOWN, never by
   `currentColor`.

   The `AI` is live text in the display face rather than part of the artwork.
   That keeps the badge on the two logo files every page already caches, and it
   scales with the text instead of pixelating.

   The badge is named ONCE, on the pill, as `role="img"` with an aria-label,
   and every piece inside it is aria-hidden. Built the obvious way instead —
   alt="Platinumlist" on the logo plus a live "AI" span — the accessibility tree
   came out as "Made with Platinumlist" with the AI dropped entirely, and BOTH
   images listed, because the alt of a display:none image is not reliably
   pruned. One name on the container has neither problem.

   Note there is deliberately NO `.mw-lockup img{ display:block }`. That is the
   trap this project has now paid for three times (.approx-tag, .sub-tabs,
   .pl-tabs a): a bare `display` on the element beats the `display:none` that
   hides the wrong theme's logo, and BOTH would render. Sizing is set on its own
   rule that touches no display, and every display lives on a `.light-only` /
   `.dark-only` selector at matching specificity. */
.pl-madewith{
  display: flex;
  justify-content: center;
  padding: 30px 16px 6px;
}
.pl-madewith .mw-pill{
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 15px;
  border-radius: 999px;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-border);
  font-family: var(--font);
  font-size: 13px;
  line-height: 1;
  color: var(--content-secondary);
  /* Not a link and not a control. On a phone it sits just above the floating
     nav pill, and a stray selection drag there should not highlight it. */
  user-select: none;
  -webkit-user-select: none;
}
.pl-madewith .mw-lockup{ display: inline-flex; align-items: flex-start; gap: 1.5px; }
.pl-madewith .mw-lockup img{ height: 15px; width: auto; }

.pl-madewith .mw-lockup img.light-only{ display: block; }
.pl-madewith .mw-lockup img.dark-only{ display: none; }
[data-theme="dark"] .pl-madewith .mw-lockup img.light-only{ display: none; }
[data-theme="dark"] .pl-madewith .mw-lockup img.dark-only{ display: block; }

.pl-madewith .mw-ai{
  font-family: var(--font-accent);
  font-size: 8px;
  line-height: 1;
  margin-top: 1px;
  letter-spacing: 0.02em;
  /* Follows the wordmark it hangs off: purple on light, white on dark. The
     brand purple on the dark surface would be all but unreadable at 8px. */
  color: light-dark(var(--poodle-primary), var(--aussie-0));
}

/* ── The locked header ──────────────────────────────────────────────────────
   Both pages put `.topbar` inside `.app`, so one rule locks both, and it locks
   at EVERY width — on desktop the section nav lives inside the bar, so making
   the bar sticky is what pins Overview / Settings / People / Roles / Activity
   too. Nothing is hidden or shrunk at any size: the whole header stays.

   `padding-top: var(--safe-top)` sits on the bar itself and not on `.app`,
   because the bar is the thing that overlaps the status bar. Put the inset on
   the container and it scrolls away with the content, and the collision comes
   straight back the moment you move. It applies at all widths because
   `viewport-fit=cover` does — an iPad in standalone has a status bar too.

   What it costs, measured, and accepted: the full bar plus the nav pill take
   29-32% of a phone once the notch and home indicator are counted, so a 375px
   screen shows three match cards where it used to show four. Arif's call,
   2026-09-07, after seeing the numbers — a header you can always reach beats
   a fourth card you have to scroll to.

   The background is a pseudo-element bleeding to either side rather than a
   background on the bar, because `.app` pads itself by 14px on a phone and
   24px above that, and a background on the bar alone would leave two unpainted
   gutters for the cards to show through as they pass underneath.

   That bleed needs the page to clip horizontally, and the page already did —
   with `overflow-x: hidden`, which is exactly what stopped the bar sticking.
   `hidden` on one axis forces the other to `auto`, so html and body both
   became scroll containers and a sticky descendant has nothing left to stick
   to; measured, the bar scrolled 682px off the top. `clip` clips identically
   WITHOUT creating a scroll container. It is behind @supports because Safari
   only took it in 16: an older iPhone keeps `hidden`, so the bleed is still
   clipped and the header simply does not lock — which is what it did before
   today, not a regression. */
@supports (overflow-x: clip){
  /* `:root` and `html body` rather than `html, body`: every page re-declares
     these in its own <style>, which loads after this file and would otherwise
     win on equal specificity. The same trick the 16px zoom floor needs. */
  :root, html body{ overflow-x: clip; }
}

.topbar{
  position: sticky;
  top: 0;
  /* Below the nav pill (40), the modal (50), the round menu (60) and the
     alert drawer (90/91). A header over an open dialog is a bug. */
  z-index: 30;
  padding-top: var(--safe-top);
}
.topbar::before{
  content: '';
  position: absolute;
  inset: 0 -100vw;
  z-index: -1;
  background: var(--bg-default);
}

/* The only thing that changes on scroll. The bar and the cards share a
   background, so with nothing between them a locked header reads as a chunk of
   the page that has stopped moving rather than a layer above it. `.is-stuck`
   is set by nav.js the moment the page leaves the top. */
.topbar.is-stuck{ box-shadow: var(--shadow-sm); }

/* ── Touch targets ──────────────────────────────────────────────────────────
   Keyed on pointer:coarse rather than width, because the thing that makes a
   control hard to hit is a finger, not a narrow window — an iPad at 768px gets
   no mobile breakpoint but has exactly the same problem. A Playwright audit
   found the round picker at 16px and the crest remove button at 19px. */
@media (pointer: coarse){
  .round-btn{ min-height: 40px; padding: 9px 2px; }
  .theme-toggle{ width: 42px; height: 42px; }
  .crest .clear, .card .clear{ min-width: 34px; min-height: 34px; padding: 8px; }
  .idbox input, .idbox .idgo{ min-height: 40px; }
  .btn, .btn.sm{ min-height: 40px; }
  /* Typed inputs, not a bare `input`: checkboxes and radios size themselves and
     a min-height would stretch their hit box oddly. `date` was the gap — the
     Activity range pickers sat at 35px because only text/number were listed. */
  select,
  input[type="text"], input[type="number"], input[type="date"],
  input[type="time"], input[type="search"], input[type="email"],
  input[type="password"], input[type="tel"]{ min-height: 40px; }

  /* iOS Safari zooms the whole page in when a control it focuses has a
     font-size under 16px, and does not zoom back out afterwards — that is the
     "it zooms in and out when I tap something". 16px here is a FLOOR on the
     control's own text, not a redesign of the form.

     The other well-known fix, `maximum-scale=1` on the viewport meta, also
     takes pinch-zoom away from everybody permanently, so it is not on the
     table. Checkboxes, radios and ranges are excluded: they carry no text and
     a font-size only distorts them.

     Prefixed `html` for the same reason `html body` is used below: each page's
     inline <style> loads AFTER this file, so a bare `select{font-size:13px}`
     there beats a bare `select` here on equal specificity. #eventPick stayed at
     13px — and so kept zooming — until this rule outranked it. */
  html input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  html select, html textarea{ font-size: 16px; }

  /* Overlay and secondary controls that a Playwright/audit pass found below the
     40px floor on tablets (which get no max-width breakpoint) and in the modal
     and alert drawer. The visible glyph stays small; only the hit area grows. */
  .icon-btn{ min-width: 40px; min-height: 40px; }
  .alert-ack{ min-width: 40px; min-height: 40px; }
  .drawer-clear{ min-height: 40px; }
  .round-option{ min-height: 40px; }
  .sub-tabs button{ min-height: 40px; }
  .ac-exp{ min-width: 40px; min-height: 40px; }
  .ac-rb{ min-height: 40px; }
  .zone{ min-height: 44px; }
}
