/*
 * Exiva XP — base layer: brand fonts, semantic tokens and the product app
 * shell (sidebar → icon rail → bottom tab bar). Token values come from the
 * Instagram Design System (nesleykent/instagram-design-system): light-first,
 * near-monochrome surfaces, one loud gradient, a single fixed action blue.
 */

/* ---------- brand typefaces ---------- */

@font-face {
  font-family: "Optimistic";
  src: url("../fonts/optimistic-vf.ttf") format("truetype");
  font-weight: 200 800;
  font-display: swap;
}
@font-face {
  font-family: "Instagram Sans Headline";
  src: url("../fonts/instagram-sans-headline.otf") format("opentype");
  font-weight: 400 800;
  font-display: swap;
}
@font-face {
  font-family: "Instagram Sans";
  src: url("../fonts/instagram-sans-regular.ttf") format("truetype");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Instagram Sans";
  src: url("../fonts/instagram-sans-bold.ttf") format("truetype");
  font-weight: 700;
  font-display: swap;
}

/* ---------- tokens ---------- */

:root {
  /* brand gradient — the only saturated element */
  --grad-yellow: #FFD600;
  --grad-orange: #FF7A00;
  --grad-rose: #FF0169;
  --grad-magenta: #D300C5;
  --grad-purple: #7638FA;
  --grad-hero: linear-gradient(72.44deg, var(--grad-rose) 4.69%, var(--grad-magenta) 48.96%, var(--grad-purple) 92.19%);
  --grad-spectrum: linear-gradient(125deg, var(--grad-yellow) 15%, var(--grad-orange) 30%, var(--grad-rose), var(--grad-magenta) 70%, var(--grad-purple) 85%);
  --grad-story: conic-gradient(from 210deg, var(--grad-yellow), var(--grad-orange), var(--grad-rose), var(--grad-magenta), var(--grad-purple), var(--grad-yellow));

  /* RGB-triple twins of the --grad-* hex stops, for contexts that need
     rgb(var(--x)) composability (donut segments, chart legend swatches) —
     the categorical brand-hue set (AGENTS.md §5),
     for non-elemental categorical groupings (grounds, sources), never
     elemental data (--c-* stays reserved for that). */
  --brand-rose: 255, 1, 105;
  --brand-magenta: 211, 0, 197;
  --brand-purple: 118, 56, 250;
  --brand-orange: 255, 122, 0;
  --brand-yellow: 255, 214, 0;

  /* semantic surfaces — light default */
  --surface: 255, 255, 255;
  --surface-2: 243, 245, 247;
  --surface-raised: 255, 255, 255;
  --ink: 0, 0, 0;
  --ink-2: 115, 115, 115;
  --icon: 38, 38, 38;
  --wash: 239, 239, 239;
  /* the source's --ig-secondary-button (button fill) is a shade lighter than
     --ig-highlight-background/--wash in light mode (250 vs 239) though they
     collapse to the same value in dark (38); kept distinct so real buttons
     (.btn-secondary) don't borrow the badge/skeleton tone. */
  --surface-btn2: 250, 250, 250;
  --surface-btn2-hover: 219, 219, 219;
  --line: 219, 219, 219;
  /* the source's --ig-separator (list/section dividers, --line above) and
     --ig-stroke (visible borders on cards/buttons/inputs) share one value
     in light mode (219) but diverge sharply in dark (38 vs 85) — collapsing
     them into a single token made every control's border nearly invisible
     in dark mode. --stroke is --ig-stroke; keep --line for actual dividers. */
  --stroke: 219, 219, 219;
  --line-strong: 168, 168, 168;
  --overlay: 0, 0, 0;
  --overlay-a: .05;
  /* --ig-tertiary-button-border/-hover — distinct from --stroke/--wash: the
     source's tertiary button border is darker than its general --ig-stroke
     in dark mode (54 vs 85), and its hover wash doesn't match --wash exactly
     in light mode (245 vs 239) */
  --tertiary-border: 219, 219, 219;
  --tertiary-hover: 245, 245, 245;
  /* app extensions for surfaces that sit ON a raised panel: dark mode's
     --surface-2/--wash/--surface-raised are within 5 levels of each other,
     so a pressed segment (darker than its track) and empty meter/heatmap
     tracks vanished there. Light values equal the tones they replace. */
  --surface-selected: 255, 255, 255;
  --track: 239, 239, 239;
  /* status/link tones used as TEXT: the fixed --blue, --red, --green and
     warning orange fall below WCAG AA (4.5:1) as text on light surfaces and
     their own washes, so light mode darkens them; dark mode lifts blue and
     red for the raised surfaces. Fills, washes and icons keep the fixed tones. */
  --ink-info: 0, 100, 183;
  --ink-error: 196, 30, 48;
  --ink-success: 0, 122, 18;
  --ink-warning: 160, 80, 0;

  /* fixed semantics — never theme-swap, matching the source's --web-always-* */
  --blue: 0, 149, 246;
  --blue-press: 24, 119, 242;
  --red: 237, 73, 86;
  --green: 88, 195, 34;
  --white-fixed: 255, 255, 255;
  /* the source Badge's neutral fill: the primary-icon value, fixed in both appearances */
  --neutral-fixed: 38, 38, 38;

  /* element accents (data colour only) — aliased to the fixed/brand tokens
     above wherever the design system already reused that hue (fire=orange,
     energy=purple, ice=blue, holy=yellow, death=magenta, earth=green); only
     physical has no other home. Aliasing instead of repeating the literal
     stops the two systems from silently drifting apart if either is retuned. */
  --c-physical: 142, 142, 142;
  --c-earth: var(--green);
  --c-fire: var(--brand-orange);
  --c-energy: var(--brand-purple);
  --c-ice: var(--blue);
  --c-holy: var(--brand-yellow);
  --c-death: var(--brand-magenta);

  /* type */
  --f-ui: "Optimistic", Montserrat, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, "Noto Sans", sans-serif;
  --f-wordmark: "Instagram Sans Headline", "Instagram Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --f-display: "Instagram Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --f-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* spacing / shape / elevation */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s8: 32px; --s10: 40px; --s12: 48px; --s16: 64px;
  --r-xs: 2px; --r-sm: 4px; --r-input: 8px; --r-md: 8px; --r-card: 10px; --r-lg: 12px; --r-xl: 16px;
  --r-pill: 1000px; --r-circle: 50%;
  /* the source defines one shadow value for all three contexts
     (--ig-shadow-card/-dropdown/-float all equal "0 0 10px rgba(0,0,0,.2)") —
     kept as three named tokens for call-site clarity, but identical */
  --shadow-card: 0 0 10px rgba(0,0,0,.2);
  --shadow-float: 0 0 10px rgba(0,0,0,.2);
  --shadow-dropdown: 0 0 10px rgba(0,0,0,.2);

  /* type scale — named after the source's paired system-N tokens */
  --fs-10: 10px; --fs-11: 11px; --fs-12: 12px; --fs-13: 13px; --fs-14: 14px;
  --fs-15: 15px; --fs-16: 16px; --fs-17: 17px; --fs-18: 18px; --fs-20: 20px;
  --fs-22: 22px; --fs-24: 24px; --fs-26: 26px; --fs-28: 28px; --fs-32: 32px;

  /* motion — curves match the source's --ig-ease-standard/--ig-ease-confident/--ig-ease-settle exactly */
  --e-standard: cubic-bezier(.33, 0, .67, 1);
  --e-confident: cubic-bezier(.7, 0, .3, 1);
  --e-settle: cubic-bezier(0, .61, .28, .92);
  --t-hover: 150ms;
  --t-reveal: 666ms; /* matches the source's --ig-duration-reveal */

  /* shell geometry */
  --rail: 245px;
  --rail-slim: 76px;
  --feed: 660px;
  --canvas: 1060px;
  --tap: 44px;

  color-scheme: light;
}

[data-appearance="dark"] {
  --surface: 12, 16, 20; /* the source's --ig-primary-background — not pure black */
  --surface-2: 37, 41, 46;
  --surface-raised: 33, 35, 40;
  --ink: 245, 245, 245;
  --ink-2: 168, 168, 168;
  --icon: 245, 245, 245;
  --wash: 38, 38, 38;
  --surface-btn2: 38, 38, 38;
  --surface-btn2-hover: 38, 38, 38;
  --line: 38, 38, 38;
  --stroke: 85, 85, 85;
  --line-strong: 85, 85, 85;
  --overlay: 255, 255, 255;
  --overlay-a: .1;
  --tertiary-border: 54, 54, 54;
  --tertiary-hover: 38, 38, 38;
  --surface-selected: 54, 54, 54;
  --track: 54, 54, 54;
  --ink-info: 64, 172, 255;
  --ink-error: 255, 105, 115;
  --ink-success: var(--green);
  --ink-warning: var(--brand-orange);
  color-scheme: dark;
}

@media (prefers-reduced-motion: reduce) {
  /* one iteration too: an infinite .01ms animation (the skeleton shimmer) cycled
     thousands of times a second and flickered instead of resting */
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
}

/* ---------- resets ---------- */

* { box-sizing: border-box; }

/* The UA's own `[hidden] { display: none }` loses to any class that sets a
   display — `.filter-bar` is `display: flex`, so hiding it by attribute did
   nothing until this rule. Anything marked hidden is hidden, full stop. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: rgb(var(--surface));
  color: rgb(var(--ink));
  font-family: var(--f-ui);
  font-size: var(--fs-16);
  line-height: normal;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
/* links inside running text must read as links (WCAG 1.4.1): the reset above
   suits chrome, but a bare <a> in a sentence was indistinguishable from it */
:where(p, li) a:not([class]) { color: rgb(var(--ink-info)); text-decoration: underline; text-decoration-color: rgba(var(--ink-info), .4); text-underline-offset: 2px; }
:where(p, li) a:not([class]):hover { text-decoration-color: currentColor; }
button { font-family: inherit; }
h1, h2, h3, h4 { margin: 0; font-weight: 700; }
/* the UA's `bolder` turned a <b> inside 600-weight text (pills, the level
   progress row) into 900; bold is one weight */
b, strong { font-weight: 700; }
h1 { font-family: var(--f-wordmark); font-size: var(--fs-26); line-height: 28px; letter-spacing: -.26px; }

.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.skip-link { position: fixed; left: -9999px; min-height: var(--tap); z-index: 200; background: rgb(var(--surface-raised)); color: rgb(var(--ink)); padding: var(--s3) var(--s4); border: 0; border-radius: var(--r-md); box-shadow: var(--shadow-float); font: inherit; cursor: pointer; }
.skip-link:focus { left: var(--s3); top: var(--s3); }
:focus-visible { outline: 2px solid rgb(var(--blue)); outline-offset: 2px; }

/* ---------- app shell ---------- */

.shell { display: flex; min-height: 100vh; }

.rail {
  position: sticky; top: 0; align-self: flex-start;
  width: var(--rail); height: 100vh; flex-shrink: 0;
  display: flex; flex-direction: column;
  gap: var(--s1);
  padding: var(--s2) var(--s3) var(--s5);
  border-right: 1px solid rgb(var(--line));
  background: rgb(var(--surface));
  z-index: 50;
}

.wordmark {
  display: flex; align-items: center; gap: 10px;
  padding: var(--s6) var(--s3) var(--s5);
  font-family: var(--f-wordmark);
  font-weight: 700; font-size: var(--fs-26); line-height: normal; letter-spacing: -.5px;
  white-space: nowrap;
}
.wordmark svg { width: 30px; height: 30px; flex-shrink: 0; display: none; }

.rail-nav { display: flex; flex-direction: column; gap: var(--s1); flex: 1; }

.rail-item {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3); border: 0; border-radius: var(--r-md);
  background: transparent; color: rgb(var(--ink));
  font-size: var(--fs-16); line-height: normal; font-weight: 400;
  cursor: pointer; white-space: nowrap;
  transition: background var(--t-hover) var(--e-standard);
}
.rail-item:hover { background: rgba(var(--overlay), var(--overlay-a)); }
.rail-item[aria-current="page"] { font-weight: 700; }
/* the label that carries the bold is hidden on the icon rail and tab bar, so
   the current destination also gets the heavier icon stroke (the source tab
   bar marks its active tab by icon weight) */
.rail-item[aria-current="page"] svg { stroke-width: 2.4; }
.rail-item svg { width: 26px; height: 26px; flex-shrink: 0; }

.rail-foot { display: flex; flex-direction: column; gap: var(--s1); }

/* icon rail on narrow desktop */
@media (max-width: 1100px) {
  .rail { width: var(--rail-slim); align-items: center; padding: var(--s2) var(--s2) var(--s5); }
  /* labels leave the layout but stay the links' accessible names — with
     display:none the wordmark link had no name at all on the icon rail */
  .wordmark span, .rail-item span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .wordmark svg { display: block; }
  .wordmark { padding: var(--s6) 0 var(--s5); justify-content: center; }
  .rail-item { justify-content: center; width: 48px; }
}

/* bottom tab bar on mobile */
@media (max-width: 700px) {
  .shell { flex-direction: column; }
  .rail {
    position: fixed; top: auto; bottom: 0; left: 0; right: 0;
    width: 100%; height: calc(52px + env(safe-area-inset-bottom));
    flex-direction: row; align-items: center;
    justify-content: space-around; padding: 0 var(--s2) env(safe-area-inset-bottom);
    border-right: 0; border-top: 1px solid rgb(var(--line));
  }
  .wordmark { display: none; }
  .rail-nav { flex-direction: row; flex: 1; justify-content: space-around; gap: 0; }
  .rail-item { padding: var(--s2) 6px; width: auto; }
  .rail-nav .rail-item, .rail-foot .rail-item { min-width: var(--tap); min-height: var(--tap); }
  /* source IOSTabBar spec: icons render at 27px when the bar carries no labels */
  .rail-nav .rail-item svg { width: 27px; height: 27px; }
  /* GitHub + appearance are utilities, not destinations — a floating
     corner cluster keeps the bottom tab bar to the real nav targets. On
     pages whose first element is the full-bleed hero card (character.html),
     main.stage's own s8 (32px) top padding put the cluster's fixed 8px `top`
     above the card's top edge entirely — the pill visibly straddled the
     card boundary, half sat on plain background, half overlapping the card.
     Match the stage's own top padding instead of a flat s2 so the cluster
     always starts safely inside whatever's at the top of the page. */
  .rail-foot {
    position: fixed; top: calc(var(--s8) + var(--s2) + env(safe-area-inset-top)); right: var(--s2); z-index: 60;
    flex-direction: row; gap: 2px; padding: 2px;
    background: rgba(var(--surface), .92);
    backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgb(var(--stroke)); border-radius: var(--r-pill);
  }
  .rail-foot .rail-item { padding: var(--s2); }
  /* the tab bar carries only the five daily-loop destinations; the rest
     stay in the desktop sidebar and on the home workspace grid */
  .rail-nav .rail-desktop-only { display: none; }
}

main.stage {
  flex: 1; min-width: 0;
  padding: var(--s8) var(--s8) var(--s16);
}
/* phone gutters are HIG's 16pt layout margin — the desktop s8 left ~263px of
   content inside a padded panel at 375. The top stays s8: the floating
   utility cluster's `top` is measured from it. */
@media (max-width: 700px) {
  main.stage { padding: var(--s8) var(--s4) calc(72px + env(safe-area-inset-bottom)); }
}
.stage-inner { max-width: var(--canvas); margin: 0 auto; animation: page-settle var(--t-reveal) var(--e-settle) both; }
.stage-inner.narrow { max-width: var(--feed); }

@keyframes page-settle { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: scale(1); } }

/* ---------- primitives ----------
 * Button — three-tier action control (primary/secondary/tertiary), plus an
 * app-specific "destructive" tier using the system's own error colour.
 * Radius is 6px at every size — sizes vary padding/font only, never shape.
 */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 28px; padding: 5px var(--s3);
  border: 1px solid transparent; border-radius: var(--r-input);
  font-size: var(--fs-12); line-height: 16px; font-weight: 600; letter-spacing: normal; cursor: pointer;
  transition: background var(--t-hover) var(--e-standard);
}
.btn-primary { background: rgb(var(--blue)); color: rgb(var(--white-fixed)); }
.btn-primary:not([disabled]):hover { background: rgb(var(--blue-press)); }
.btn-secondary { background: rgb(var(--surface-btn2)); color: rgb(var(--ink)); }
.btn-secondary:not([disabled]):hover { background: rgb(var(--surface-btn2-hover)); }
/* solid elevated fill (--ig-tertiary-button-background), not transparent —
   on the page background (not inside a panel) transparent let the raw page
   colour show through instead of reading as a raised button; --icon matches
   --ig-tertiary-button-text exactly in both appearances (38/light, 245/dark) */
.btn-tertiary { background: rgb(var(--surface-raised)); color: rgb(var(--icon)); border: 1px solid rgb(var(--tertiary-border)); }
.btn-tertiary:not([disabled]):hover { background: rgb(var(--tertiary-hover)); }
.btn-destructive { background: rgb(var(--red)); color: rgb(var(--white-fixed)); }
.btn-destructive:not([disabled]):hover { filter: brightness(.92); }
/* one pressed state for every tier; hover never recolours a disabled button */
.btn:not([disabled]):active { opacity: .7; }
.btn[disabled] { opacity: .3; cursor: default; }
/* size tiers vary height, padding and type only — never shape */
.btn-sm { min-height: 24px; padding: var(--s1) var(--s2); font-size: var(--fs-11); line-height: 13px; }
.btn-lg { min-height: var(--tap); padding: var(--s3) var(--s5); font-size: var(--fs-14); line-height: 18px; }

/* Utility card — the standalone design uses the quiet separator tone around
   filled surfaces, while the stronger stroke is reserved for controls. Keep
   the contract's 6px utility radius and hover-only elevation at list density. */
.panel {
  background: rgb(var(--surface-raised));
  border: 1px solid rgb(var(--line));
  border-radius: var(--r-card);
}
.panel-pad { padding: var(--s6); }
@media (max-width: 700px) { .panel-pad { padding: var(--s4); } }

.eyebrow {
  font-size: var(--fs-11); line-height: 13px; font-weight: 600;
  letter-spacing: 1px; text-transform: uppercase;
  color: rgb(var(--ink-2));
}

.dim { color: rgb(var(--ink-2)); }
.fine { font-size: var(--fs-12); line-height: 16px; }
.num { font-variant-numeric: tabular-nums; }

/* the sort-menu button shares the field chrome so a menu reads as a field */
input[type="text"], input[type="search"], input[type="number"], select, textarea, .sort-menu-btn {
  font-family: var(--f-ui); font-size: var(--fs-14);
  color: rgb(var(--ink));
  background: rgb(var(--surface-2));
  border: 1px solid rgb(var(--stroke)); border-radius: var(--r-input);
  padding: 10px var(--s3); min-height: 36px; /* 10px: established cozy half-step, matches .fact/table cells/toast */
  line-height: 20px; /* one box height for inputs, selects and the sort button (they measured 41/42/40px) */
}
/* selects draw the same thin chevron as the sort menu instead of the OS
   arrow; two gradient strokes keep it on the --ink-2 token in both themes */
select {
  appearance: none; -webkit-appearance: none;
  padding-right: 36px;
  background-image:
    linear-gradient(45deg, transparent calc(50% - 1px), rgb(var(--ink-2)) calc(50% - 1px), rgb(var(--ink-2)) calc(50% + 1px), transparent calc(50% + 1px)),
    linear-gradient(-45deg, transparent calc(50% - 1px), rgb(var(--ink-2)) calc(50% - 1px), rgb(var(--ink-2)) calc(50% + 1px), transparent calc(50% + 1px));
  background-size: 6px 6px;
  background-position: right 19px center, right 14px center;
  background-repeat: no-repeat;
  cursor: pointer;
}
/* a field the controller marked invalid */
:is(input, select, textarea)[aria-invalid="true"] { border-color: rgb(var(--red)); }
textarea { width: 100%; font-family: var(--f-mono); font-size: var(--fs-12); line-height: 1.5; resize: vertical; }

label.lbl { display: grid; gap: var(--s1); }
/* every filter field shares one width so a row of controls lines up evenly;
   lbl-wide/lbl-narrow are the only sanctioned exceptions (free text search,
   short numeric inputs) — never set width via inline style on a .lbl field. */
label.lbl > input, label.lbl > select { min-width: 160px; width: 100%; box-sizing: border-box; }
label.lbl.lbl-wide > input { min-width: 220px; }
label.lbl.lbl-narrow > input { min-width: 96px; width: 96px; }
label.lbl > .sort-menu { min-width: 160px; width: 100%; box-sizing: border-box; }

/* Sort control — Apple HIG's menu pattern for a small mutually-exclusive
   option set: a button that opens an anchored list with a checkmark on the
   current choice, dismissed on pick or outside click. Replaces a native
   <select>, which read as an OS-chrome control next to the rest of this
   custom-styled system. */
.sort-menu { position: relative; }
.sort-menu-btn {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  width: 100%; text-align: left; cursor: pointer;
}
.sort-menu-btn:hover { background: rgba(var(--overlay), calc(var(--overlay-a) * 2)); }
.sort-menu-btn[aria-expanded="true"] { border-color: rgb(var(--line-strong)); }
.sort-menu-btn .value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sort-menu-btn .chevron { width: 16px; height: 16px; flex-shrink: 0; color: rgb(var(--ink-2)); transition: transform var(--t-hover) var(--e-standard); }
.sort-menu-btn[aria-expanded="true"] .chevron { transform: rotate(180deg); }
.sort-menu-list {
  position: absolute; top: calc(100% + var(--s1)); left: 0; right: 0; z-index: 20;
  padding: 6px; margin: 0;
  background: rgb(var(--surface-raised)); border: 1px solid rgb(var(--stroke));
  /* --shadow-dropdown: the anchored-menu role (same value as the other shadow tokens) */
  border-radius: var(--r-lg); box-shadow: var(--shadow-dropdown);
}
.sort-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px; border: 0; border-radius: var(--r-input);
  background: transparent; color: rgb(var(--ink)); font-family: var(--f-ui); font-size: var(--fs-14); text-align: left; cursor: pointer;
}
.sort-menu-item:hover, .sort-menu-item:focus-visible { background: rgba(var(--overlay), var(--overlay-a)); }
/* keyboard focus keeps the ring (drawn inside, the list clips nothing) so it
   stays distinct from pointer hover */
.sort-menu-item:focus-visible { outline-offset: -2px; }
.sort-menu-item .check { width: 16px; height: 16px; flex-shrink: 0; color: rgb(var(--blue)); visibility: hidden; }
.sort-menu-item[aria-checked="true"] .check { visibility: visible; }

/* Compact desktop controls expand to the iOS minimum target on touch-sized
   layouts. This changes hit area, not hierarchy: labels, padding and type
   remain the same, while every shared form/action primitive becomes reliably
   reachable on the 375pt iPhone 8 baseline. */
@media (max-width: 700px) {
  .btn { min-width: var(--tap); }
  .btn,
  input[type="text"], input[type="search"], input[type="number"], select, textarea,
  .sort-menu-btn, .sort-menu-item { min-height: var(--tap); }
  /* iOS Safari zooms the page into any field under 16px on focus and leaves
     it zoomed; the Log a hunt paste box (12px) zoomed to ~133% */
  input[type="text"], input[type="search"], input[type="number"], select, textarea,
  .sort-menu-btn { font-size: var(--fs-16); }
}

/* gradient text — whole-word treatment only */
.grad-text {
  background: var(--grad-spectrum);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}

/* Initials avatar ring for grounds, creatures and compact hunt links. */
.ring {
  display: inline-grid; place-items: center;
  width: 64px; height: 64px; border-radius: var(--r-circle);
  background: var(--grad-story);
  flex-shrink: 0; box-sizing: border-box;
}
.ring > .ring-core {
  display: grid; place-items: center;
  width: 56px; height: 56px; border-radius: var(--r-circle);
  background: rgb(var(--surface));
  font-weight: 700; font-size: var(--fs-18);
}
.ring.ring-quiet { background: rgb(var(--line)); }

.toast {
  position: fixed; left: 50%; bottom: var(--s6); transform: translateX(-50%);
  background: rgb(var(--ink)); color: rgb(var(--surface));
  padding: 10px var(--s5); border-radius: var(--r-md);
  font-size: var(--fs-14); z-index: 120; box-shadow: var(--shadow-float);
  animation: toast-rise .35s var(--e-settle);
}
@keyframes toast-rise { from { opacity: 0; transform: translate(-50%, 10px); } }

/* @media print rules do not reliably fire in the pipeline behind iOS
   Safari's Share ▸ Print export. Keep essential state visible in the normal
   render instead of relying on print-only substitutions. */
