/* Name: Yip Kai Jun Jayden */
/* Student Admin No: 241266U */
/* Tutorial Group: BF2501 */

/* ArcaneVault theme — "Sapphire & Amber" astral vault.
   Layered on top of Bootstrap: we re-theme Bootstrap's component CSS
   variables and a handful of components rather than replacing markup,
   so every existing page keeps working while taking on the new identity. */

:root {
  --av-void: #060B1A;
  --av-panel: #0E1630;
  --av-panel-2: #131F40;
  --av-sapphire: #4D7CFF;
  --av-sky: #6FB0FF;
  --av-amber: #F0A93C;
  --av-amber-soft: #F6C572;
  --av-text: #E7EDFB;
  --av-text-2: #B6C2E0;
  --av-mut: #8A97B8;
  --av-line: rgba(77, 124, 255, 0.16);
  --av-line-2: rgba(77, 124, 255, 0.30);
  --av-amber-line: rgba(240, 169, 60, 0.32);
  --av-ink: #0A1124;

  /* Semantic state colours. These four were previously hardcoded hexes copied
     into each page's own <style> block, where they drifted — Accepted rendered
     #4caf82 on Trades/History but #4ade80 on Trades/Moderate, so the same
     status was a different green depending on which page you were looking at.
     Naming them here is what stops that recurring. */
  --av-ok: #4ADE80;
  --av-bad: #F87171;
  --av-warn: #FB923C;
  /* The validation coral pair and the alerts' softer success green, named for
     the same reason as the four above — they were repeated as raw hex across
     both stylesheets and had nowhere to drift back to. --av-ok-soft is kept
     distinct from --av-ok pending a conscious merge of the two greens. */
  --av-coral: #ff7a6b;
  --av-coral-soft: #ff9b8a;
  --av-ok-soft: #4fd99b;

  /* Elevation. Everything used to sit flat on one panel colour, which is why
     the app read as shallow — nothing announced which surface was on top of
     which. Three steps: resting panel, raised card, lifted/hovered. */
  --av-elev-1: 0 1px 2px rgba(3, 7, 18, 0.40);
  --av-elev-2: 0 6px 18px rgba(3, 7, 18, 0.45), 0 1px 2px rgba(3, 7, 18, 0.35);
  --av-elev-3: 0 16px 40px rgba(3, 7, 18, 0.55), 0 2px 6px rgba(3, 7, 18, 0.40);

  --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --font-rune: 'Cinzel', Georgia, serif;
  --font-ui: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  /* Motion layer — the "living vault" elevation. One easing, a few durations,
     and a stagger step so every page opens with the same choreographed rhythm.
     All of it is switched off by the prefers-reduced-motion block at the foot
     of this file. */
  --av-ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --av-t-fast: 0.18s;
  --av-t-rise: 0.6s;
  --av-t-seal: 1.05s;
  --av-stagger: 80ms;
}

html {
  font-size: 15px;
  position: relative;
  min-height: 100%;
}
@media (min-width: 768px) {
  html { font-size: 16px; }
}

body {
  font-family: var(--font-ui);
  color: var(--av-text-2);
  background-color: var(--av-void);
  background-image:
    radial-gradient(900px 620px at 86% -8%, rgba(77, 124, 255, 0.18), transparent 60%),
    radial-gradient(720px 520px at -12% 112%, rgba(240, 169, 60, 0.10), transparent 60%);
  background-attachment: fixed;
  min-height: 100vh;
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

/* A faint, fixed star-field so the vault has depth behind the content. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(1.4px 1.4px at 18% 28%, rgba(231, 237, 251, 0.55), transparent 60%),
    radial-gradient(1.2px 1.2px at 72% 18%, rgba(231, 237, 251, 0.40), transparent 60%),
    radial-gradient(1.5px 1.5px at 42% 72%, rgba(231, 237, 251, 0.35), transparent 60%),
    radial-gradient(1.1px 1.1px at 85% 62%, rgba(231, 237, 251, 0.45), transparent 60%),
    radial-gradient(1.3px 1.3px at 56% 44%, rgba(231, 237, 251, 0.30), transparent 60%),
    radial-gradient(1.2px 1.2px at 12% 82%, rgba(231, 237, 251, 0.35), transparent 60%),
    radial-gradient(1.2px 1.2px at 92% 88%, rgba(231, 237, 251, 0.30), transparent 60%);
  opacity: 0.5;
}

/* header sits ABOVE main/footer, not merely alongside them: all three were
   equal z-index, which only lifted them off the fixed star-field — but equal
   z-index siblings paint in DOM order, so main (later in the document) drew
   over the navbar's own open dropdowns no matter what z-index Bootstrap gave
   the menu itself. A dropdown can only escape its ancestor's stacking rank,
   never its ancestor's sibling's. Bumping header's rank fixes every nav
   dropdown at once (Trading/More/Admin) without touching the menus themselves. */
header { position: relative; z-index: 2; }
main, footer { position: relative; z-index: 1; }

/* Ambient life: the star-field drifts slowly and a faint sapphire aura breathes
   behind the content, so the vault feels alive even when nothing is happening.
   Both sit behind everything (z-index 0) and ignore pointer events. */
body::before { animation: av-drift 18s ease-in-out infinite alternate; }
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(820px 560px at 84% 6%, rgba(77, 124, 255, 0.10), transparent 60%);
  animation: av-aura 9s ease-in-out infinite;
}

/* ---------- Typography ---------- */
h1, h2, h3, h4, .av-display {
  color: var(--av-text);
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.005em;
}
a { color: var(--av-sky); text-decoration: none; transition: color 0.2s ease; }
a:hover { color: var(--av-amber); }

:focus-visible { outline: 2px solid var(--av-amber); outline-offset: 2px; border-radius: 4px; }

/* Selection wears the vault's amber, not the browser's default blue — the
   last UA-coloured surface after the focus ring above and the checkbox
   accent-color. Alpha stays ≤0.35 so --av-text keeps ≥4.5:1 over the wash
   on the lightest panel. */
::selection { background: rgba(240, 169, 60, 0.30); color: var(--av-text); }

/* Scrollable card surfaces (assistant thread, listing tables) get a thin,
   hairline-toned scrollbar instead of the stock chunky one — the thumb is
   --av-mut at the same alpha family as the hairlines. Standards-based;
   older engines simply keep their default bar. */
.av-chat-thread,
.av-table-wrap {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--av-mut) 35%, transparent) transparent;
}

/* ---------- Navbar (redesigned 2026-08-03) ----------
   One bar, one voice, every role. The link treatment is declared ONCE
   (Inter 500 · 0.92rem · +0.02em tracking, muted ink that brightens);
   dropdown toggles are that exact treatment plus the shared .av-caret
   chevron; the menus are retyped in the app's panel language — hairline
   border, elev shadow, folio corner ticks. Staff walk the same six doors
   as a Collector and gain one Admin menu behind a hairline at the end of
   the row. The laptop bands and the collapsed sheet follow in the
   responsiveness section directly after this one. */
.av-navbar {
  background: rgba(8, 14, 32, 0.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  /* A full 1px hairline plus the resting shadow: the bar is the surface
     every page hangs from, so its bottom edge gets one step more
     separation than the app's usual 0.5px rule. */
  border-bottom: 1px solid var(--av-line);
  box-shadow: var(--av-elev-1);
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

/* Brand lockup — sigil and wordmark on one optical axis, close enough to
   read as a single mark. Sizes step down with the bands below. */
.av-brand { display: flex; align-items: center; gap: 0.6rem; padding-block: 0.25rem; }
.av-sigil-sm { width: 32px; height: 32px; flex: none; }
.av-word {
  font-family: var(--font-rune);
  font-weight: 600;
  font-size: 1.08rem;
  letter-spacing: 0.15em;
  color: var(--av-text);
  line-height: 1;
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .av-brand:hover .av-word {
    background: linear-gradient(100deg, var(--av-text) 0%, var(--av-amber-soft) 50%, var(--av-text) 100%);
    background-size: 220% auto;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: av-shimmer 1.4s ease;
  }
}

/* Hairline-bordered toggler in place of Bootstrap's focus-ring default. */
.av-navbar .navbar-toggler {
  border: 0.5px solid var(--av-line-2);
  border-radius: 10px;
  padding: 0.4rem 0.65rem;
}
.av-navbar .navbar-toggler:focus {
  box-shadow: none;
  outline: 2px solid var(--av-amber);
  outline-offset: 2px;
}

/* THE link treatment. --av-nav-pad feeds both the padding and the
   underline inset so the two can never drift apart across the bands;
   the flex row + gap seats the count pill and the chevron on the text's
   own axis instead of relying on vertical-align nudges. */
.av-navbar .nav-link {
  --av-nav-pad: 0.7rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  position: relative;
  padding: 0.55rem var(--av-nav-pad);
  color: var(--av-text-2);
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1.2;
  transition: color 0.2s ease;
}
.av-navbar .nav-link:hover,
.av-navbar .nav-link:focus { color: var(--av-text); }
/* Hover/active mark: a precise 1px amber rule inset to the text edge —
   folio hairline voice, no glow. Hover grows it from the left;
   .active / .av-nav-active (av-interactions.js) keep it lit, including
   on a toggle whose CHILD is the current page. */
.av-navbar .nav-link::after {
  content: "";
  position: absolute;
  left: var(--av-nav-pad); right: var(--av-nav-pad); bottom: 0.3rem;
  height: 1px;
  background: var(--av-amber);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s var(--av-ease);
}
.av-navbar .nav-link:hover::after { transform: scaleX(1); }

/* Dropdown toggles: Bootstrap draws its caret as ::after BORDERS — the
   same pseudo-element this bar uses for the underline, which is why the
   old toggles wore a smeared wedge instead of either mark. Retire the
   borders; the inline .av-caret SVG (identical on all three toggles) is
   the one chevron, and it flips while its menu is open. */
.av-navbar .dropdown-toggle::after { border: 0; margin-left: 0; }
.av-caret {
  width: 0.6rem;
  height: 0.6rem;
  flex: none;
  opacity: 0.55;
  transition: transform 0.2s var(--av-ease), opacity 0.2s ease;
}
.av-navbar .nav-link:hover .av-caret,
.av-navbar .nav-link:focus .av-caret { opacity: 0.9; }
.av-navbar .dropdown-toggle[aria-expanded="true"] .av-caret {
  transform: rotate(180deg);
  opacity: 0.9;
}

/* Identity chip. Same type size as the links; the name is the ONE flex
   item in the right rail allowed to shrink, so a long display name
   ellipsizes instead of pushing the bar into a wrap (.av-nav-rail
   below). */
.av-hello { color: var(--av-text-2); font-size: 0.92rem; padding: 0.4rem 0.5rem 0.4rem 0.35rem; }
/* The greeting doubles as the link to /Profile: same quiet look as the old
   static text, with an amber name on hover/focus as the only "clickable" cue
   (no new nav item — keeps the navbar uncluttered). */
a.av-hello-link { text-decoration: none; }
a.av-hello-link:hover .av-cap,
a.av-hello-link:focus .av-cap { color: var(--av-amber); text-decoration: underline; }
/* Menus, retyped as the app's panel. background-COLOR longhand on
   purpose: the markup composes .av-ticked, whose corner registration
   marks are background-IMAGES that a `background` shorthand here would
   erase (the same per-property cascade note as .av-desk-band). Tick ink
   is dialled below card strength — this is a transient surface. */
.av-navbar .av-dropdown {
  --av-tick: rgba(240, 169, 60, 0.38);
  background-color: var(--av-panel);
  border: 0.5px solid var(--av-line-2);
  border-radius: 12px;
  box-shadow: var(--av-elev-3);
  padding: 0.6rem;
  min-width: 13.5rem;
  margin-top: 0.4rem;
}
/* Opening is one short settle; the global reduced-motion block collapses
   the duration for anyone who asked for stillness. */
@keyframes av-menu-in {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}
.av-navbar .av-dropdown.show { animation: av-menu-in 0.16s var(--av-ease); }
.av-navbar .av-dropdown .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;                 /* honest touch rows — px on purpose,
                                       the phone root is 15px so rem lies */
  padding: 0.45rem 0.85rem;
  border-radius: 8px;
  color: var(--av-text-2);
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: 0.02em;           /* the top-level voice, restated */
  transition: color 0.15s ease, background-color 0.15s ease;
}
/* Hover/press: a quiet amber text shift over a near-invisible wash — the
   loud filled row is the one Bootstrap default this menu must not keep. */
.av-navbar .av-dropdown .dropdown-item:hover,
.av-navbar .av-dropdown .dropdown-item:focus,
.av-navbar .av-dropdown .dropdown-item:active {
  color: var(--av-amber-soft);
  background-color: rgba(240, 169, 60, 0.06);
}
/* Current page inside a menu: amber ink behind a 2px rule at the left
   edge — the same "precise rule" language as the top-level underline
   (aria-current="page" carries the state for screen readers). */
.av-navbar .av-dropdown .dropdown-item.active {
  color: var(--av-amber);
  background-color: rgba(240, 169, 60, 0.08);
  box-shadow: inset 2px 0 0 var(--av-amber);
}
/* Group hairline — Trading's ledger line above Wallet/My Shop, Admin's
   line under Insights — inset to the items' text edge. */
.av-navbar .av-dropdown .dropdown-divider {
  border-top: 0.5px solid var(--av-line-2);
  opacity: 1;
  margin: 0.4rem 0.85rem;
}
.av-rolebadge {
  display: inline-block;
  flex: none;                       /* never the piece that shrinks */
  white-space: nowrap;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--av-amber-soft);
  background: rgba(240, 169, 60, 0.12);
  border: 0.5px solid var(--av-amber-line);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  vertical-align: middle;
}
/* Pending-trades count pill inside the "My Trades" nav link. Solid amber on
   ink (not the translucent rolebadge treatment) so a waiting request stands
   out at a glance; the number itself carries the meaning, never colour alone.
   It sits in flex rows (toggle + menu item) whose gap IS the spacing, so it
   carries no offset margins of its own. */
.av-nav-count {
  display: inline-block;
  min-width: 1.5em;
  text-align: center;
  font-size: 0.72rem; /* matched to .av-bell-count — the two amber pills stay one recipe */
  font-weight: 600;
  color: var(--av-ink);
  background: var(--av-amber);
  border-radius: 999px;
  padding: 0.12rem 0.42rem;
  font-variant-numeric: tabular-nums;
}

/* Right-rail rhythm: one 8px gap between neighbours (the pieces carry no
   side margins any more — the bell's own margin from its component style
   is zeroed below for the same reason), everything centred on the bar's
   axis by the markup's align-items-lg-center. */
.av-nav-rail { column-gap: 0.5rem; }
.av-navbar .av-bell { margin-inline: 0; }
@media (min-width: 992px) {
  /* The identity chip is the rail's designated shrinker. Flex refuses to
     shrink an item below its content unless min-width: 0 opens the gate
     at EVERY level of the chain (collapse → ul → li → link → span), so
     the whole chain is declared here; everything that is not the name is
     pinned with flex: none. Net effect: a long display name ellipsizes,
     links / chip / bell / Log out never budge, the bar never wraps. */
  .av-navbar .navbar-collapse { min-width: 0; }
  .av-nav-primary { flex: none; }
  .av-nav-rail { min-width: 0; }
  .av-nav-rail > li { flex: none; }
  .av-nav-rail > .av-nav-id { flex: 0 1 auto; min-width: 0; }
  .av-nav-rail .av-nav-id .av-hello-link { min-width: 0; }
  .av-nav-rail .av-nav-id .av-hello-link .av-avatar { flex: none; }
  .av-nav-rail .av-nav-id .av-hello-link > span {
    white-space: nowrap;
    overflow: hidden;         /* also zeroes the flex min-size, which is
                                 what actually lets the name give way */
    text-overflow: ellipsis;
    min-width: 3rem;          /* the greeting may shorten, never vanish */
  }
  /* The staff wing: Admin sits past a hairline at the end of the shared
     set, so the collector geography reads identically for every role. */
  .av-nav-primary .av-nav-admin {
    margin-left: 0.4rem;
    padding-left: 0.5rem;
    border-left: 0.5px solid var(--av-line-2);
  }
}

/* ---------- Navbar responsiveness ----------
   (2026-07-29 audit, retuned for the 2026-08-03 redesign.)
   The bar must hold SEVEN top-level items for Staff — the full Collector
   set plus Admin — from 992px up. Escape hatch chosen: no More-menu
   spill. Folding the whole staff surface into the one Admin toggle plus
   two compact bands (type/pad step-downs, the brand stepping with them,
   the coin chip's "VC" unit bowing out in its own end-of-file block)
   buys the width, and the identity chip's ellipsis absorbs long display
   names. overflow-x: clip stays as the last line of defence so no
   regression can ever scroll a page sideways. */
html, body { overflow-x: clip; }

/* Wide-laptop band — one small step down. */
@media (min-width: 1200px) and (max-width: 1399.98px) {
  .av-navbar .nav-link { --av-nav-pad: 0.55rem; font-size: 0.9rem; }
  .av-word { font-size: 1.02rem; letter-spacing: 0.13em; }
  .av-sigil-sm { width: 30px; height: 30px; }
}

/* Compact band — the tightest desktop (960px container) the full Staff
   row must survive. Measured in a static harness with the real fonts:
   the Staff row needs ~74px back from the wide treatment and the
   Collector row ~69px, so every dial below is a size/space change — no
   link is hidden or renamed, and the harness confirms both rows sit
   inside the container at 992px with these values. */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .av-navbar .nav-link { --av-nav-pad: 0.3rem; gap: 0.3rem; font-size: 0.83rem; }
  .av-word { font-size: 0.88rem; letter-spacing: 0.06em; }
  .av-sigil-sm { width: 26px; height: 26px; }
  .av-brand { gap: 0.45rem; margin-right: 0.4rem; }
  .av-hello { font-size: 0.83rem; padding-inline: 0.2rem; }
  /* The avatar is the one ornament this band trades away — the pinned
     rail content (name, badge, coins, bell, Log out) all stays, and the
     avatar returns at 1200px. */
  .av-nav-id .av-avatar { display: none; }
  .av-rolebadge { font-size: 0.62rem; letter-spacing: 0.08em; padding: 0.1rem 0.35rem; }
  .av-nav-rail { column-gap: 0.25rem; }
  .av-nav-rail .btn { --bs-btn-padding-x: 0.55rem; }
  .av-nav-primary .av-nav-admin { margin-left: 0.25rem; padding-left: 0.3rem; }
}

/* Collapsed sheet (below lg — with expand-lg this serves tablets too).
   Not a raw list: three ruled groups — the pages, each open menu
   indented off a hairline spine, then the identity rail behind its own
   hairline — with full-width 44px rows throughout. */
@media (max-width: 991.98px) {
  .av-navbar .navbar-collapse {
    margin-top: 0.6rem;
    border-top: 0.5px solid var(--av-line);
    padding: 0.85rem 0.15rem 0.65rem;
  }
  /* Full-width rows with an explicit 44px floor (px on purpose: html is
     15px below 768px, so a rem padding can't promise the hit target). */
  .av-navbar .nav-link { display: flex; min-height: 44px; padding: 0.55rem 0.65rem; }
  /* The underline reads wrong on a full-width row, so the "you are
     here" mark rotates to the menus' 2px left rule down here. */
  .av-navbar .nav-link::after { display: none; }
  .av-navbar .nav-link.active,
  .av-navbar .nav-link.av-nav-active {
    color: var(--av-text);
    box-shadow: inset 2px 0 0 var(--av-amber);
  }
  /* Open menus hang off a hairline spine — panel, ticks and shadow all
     stand down (background-image: none cancels .av-ticked's marks). */
  .av-navbar .av-dropdown {
    background-color: transparent;
    background-image: none;
    border: 0;
    border-left: 0.5px solid var(--av-line-2);
    border-radius: 0;
    box-shadow: none;
    min-width: 0;
    margin: 0.1rem 0 0.6rem 0.95rem;
    padding: 0 0 0 0.5rem;
  }
  .av-navbar .av-dropdown.show { animation: none; }
  /* The right rail becomes the sheet's final group: identity, coins,
     bell, Log out — stacked behind their own hairline. */
  .av-nav-rail {
    margin-top: 0.5rem;
    border-top: 0.5px solid var(--av-line);
    padding-top: 0.7rem;
    row-gap: 0.2rem;
  }
  .av-hello { padding-left: 0.65rem; }
  /* Log out / Create vault buttons pick up the rows' left rhythm. */
  .av-nav-rail .btn { margin: 0.4rem 0 0.2rem 0.65rem; }
}

/* ---------- Buttons ---------- */
.btn {
  font-family: var(--font-ui);
  font-weight: 500;
  border-radius: 10px;
  letter-spacing: 0.01em;
  transition: transform 0.15s ease, box-shadow 0.25s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.btn-primary {
  --bs-btn-bg: var(--av-amber);
  --bs-btn-border-color: var(--av-amber);
  --bs-btn-color: var(--av-ink);
  --bs-btn-hover-bg: var(--av-amber-soft);
  --bs-btn-hover-border-color: var(--av-amber-soft);
  --bs-btn-hover-color: var(--av-ink);
  --bs-btn-active-bg: var(--av-amber-soft);
  --bs-btn-active-border-color: var(--av-amber-soft);
  --bs-btn-active-color: var(--av-ink);
  --bs-btn-disabled-bg: var(--av-amber);
  --bs-btn-disabled-border-color: var(--av-amber);
  --bs-btn-disabled-color: var(--av-ink);
  --bs-btn-focus-shadow-rgb: 240, 169, 60;
  font-weight: 600;
}
.btn-primary:hover { box-shadow: 0 8px 24px rgba(240, 169, 60, 0.26); transform: translateY(-2px); }
.btn-primary:active { transform: translateY(0); }

.av-btn-ghost {
  background: transparent;
  color: var(--av-text);
  border: 0.5px solid var(--av-line-2);
}
.av-btn-ghost:hover {
  color: var(--av-text);
  border-color: var(--av-sapphire);
  background: rgba(77, 124, 255, 0.08);
  box-shadow: 0 0 0 3px rgba(77, 124, 255, 0.12);
}

/* ---------- Cards / surfaces ---------- */
/* Bootstrap's own .card. NOTHING in Pages/ currently uses it — every card in
   this app is a bespoke av-*-card — so this whole block, elevation included,
   is defensive re-theming that renders nowhere today. The surfaces that
   actually needed elevation are .av-cap-card, .av-item-card, .av-tile,
   .av-table-wrap and Insights' .av-chart-card; don't mistake this rule for
   the one doing that work. */
.card {
  --bs-card-bg: rgba(14, 22, 48, 0.72);
  --bs-card-border-color: var(--av-line);
  --bs-card-color: var(--av-text-2);
  border-radius: 16px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--av-elev-2);
}

/* ---------- Forms ---------- */
.form-label {
  color: var(--av-mut);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 500;
  margin-bottom: 0.4rem;
}
.form-control, .form-select {
  background-color: rgba(6, 11, 26, 0.6);
  border: 0.5px solid var(--av-line-2);
  color: var(--av-text);
  border-radius: 10px;
  padding: 0.7rem 0.85rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}
.form-control::placeholder { color: var(--av-mut); }
.form-control:focus, .form-select:focus {
  background-color: rgba(6, 11, 26, 0.85);
  border-color: var(--av-amber);
  color: var(--av-text);
  box-shadow: 0 0 0 3px rgba(240, 169, 60, 0.16);
}
.text-danger { color: var(--av-coral-soft); font-size: 0.83rem; }
.validation-summary-errors { color: var(--av-coral-soft); }

/* ---------- Hero ---------- */
.av-hero { padding: clamp(2.6rem, 7vw, 5.5rem) 0 1.5rem; }
.av-hero-grid {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 2rem;
  align-items: center;
}
.av-eyebrow {
  font-family: var(--font-ui);
  font-size: 0.74rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--av-sky);
  margin-bottom: 1rem;
}
.av-display {
  font-size: clamp(2.5rem, 6vw, 4.3rem);
  line-height: 1.04;
  margin: 0 0 1.1rem;
}
.av-display em { font-style: italic; color: var(--av-amber); }
.av-lead {
  font-size: 1.05rem;
  line-height: 1.7;
  color: var(--av-text-2);
  max-width: 34rem;
  margin: 0 0 2rem;
}
.av-cta-row { display: flex; gap: 0.85rem; flex-wrap: wrap; align-items: center; }

.av-sigil { position: relative; display: flex; justify-content: center; align-items: center; }
.av-sigil::before {
  content: "";
  position: absolute;
  width: 72%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(77, 124, 255, 0.28), transparent 65%);
  animation: av-breathe 6s ease-in-out infinite;
}
.av-sigil-svg { width: clamp(210px, 30vw, 330px); height: auto; position: relative; }
.av-spin { transform-box: fill-box; transform-origin: center; animation: av-spin 30s linear infinite; }
.av-spin-rev { transform-box: fill-box; transform-origin: center; animation: av-spin 44s linear infinite reverse; }
.av-core { transform-box: fill-box; transform-origin: center; animation: av-pulse 3.6s ease-in-out infinite; }

@media (max-width: 800px) {
  .av-hero-grid { grid-template-columns: 1fr; gap: 1rem; }
  .av-sigil { margin-top: 1.5rem; }
  .av-sigil-svg { width: clamp(180px, 60vw, 260px); }
}

/* ---------- Feature tiles ---------- */
.av-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.1rem;
  margin-top: 3.5rem;
}
.av-tile {
  background: rgba(14, 22, 48, 0.55);
  border: 0.5px solid var(--av-line);
  border-radius: 16px;
  padding: 1.6rem 1.45rem;
  box-shadow: var(--av-elev-1);
  transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}
/* Rest at elev-1, lift to elev-3 — the hover shadow was already hand-rolled at
   almost exactly elev-3, so this is the same motion expressed as the token. */
.av-tile:hover {
  transform: translateY(-5px);
  border-color: var(--av-amber-line);
  box-shadow: var(--av-elev-3);
}
.av-tile-ico {
  width: 48px; height: 48px;
  border-radius: 13px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(240, 169, 60, 0.12);
  color: var(--av-amber);
  border: 0.5px solid var(--av-amber-line);
  margin-bottom: 1.1rem;
}
.av-tile-ico svg { width: 24px; height: 24px; }
.av-tile h3 { font-size: 1.4rem; margin: 0 0 0.4rem; }
.av-tile p { font-size: 0.92rem; color: var(--av-mut); margin: 0; line-height: 1.6; }

/* ---------- Home dashboard (signed-in) ---------- */
.av-dash { margin-top: 3.5rem; }
.av-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1.1rem;
  margin-top: 1rem;
}
.av-stat { display: flex; flex-direction: column; gap: 0.4rem; }
.av-stat-num {
  font-family: var(--font-display);
  font-size: 2.7rem;
  font-weight: 600;
  line-height: 1;
  color: var(--av-amber);
}
.av-stat-label {
  font-family: var(--font-ui);
  font-size: 0.74rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--av-mut);
}
.av-quick { display: flex; gap: 0.85rem; flex-wrap: wrap; margin-top: 1.7rem; }
.av-dash-empty { text-align: center; padding: 2.6rem 1.6rem; }
.av-dash-empty h3, .av-dash-empty h2 { font-size: 1.5rem; margin: 0 0 0.5rem; }
.av-dash-empty p { color: var(--av-mut); max-width: 30rem; margin: 0 auto 1.4rem; }

/* ---------- Vault auth card ---------- */
.av-vault { max-width: 30rem; margin: clamp(1.5rem, 5vw, 3.5rem) auto; }
.av-vault-card {
  background: rgba(12, 19, 42, 0.80);
  border: 0.5px solid var(--av-line-2);
  border-radius: 20px;
  padding: 2.4rem 2.2rem;
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 26px 72px rgba(0, 0, 0, 0.45);
  position: relative;
  overflow: hidden;
}
.av-vault-card::before {
  content: "";
  position: absolute;
  top: -42%; left: 50%;
  transform: translateX(-50%);
  width: 130%;
  aspect-ratio: 1;
  background: radial-gradient(circle, rgba(77, 124, 255, 0.16), transparent 60%);
  pointer-events: none;
}
.av-vault-head { text-align: center; margin-bottom: 1.6rem; position: relative; }
.av-vault-sigil { width: 46px; height: 46px; }
.av-vault-title { font-size: 1.95rem; margin: 0.8rem 0 0.25rem; }
.av-vault-sub { color: var(--av-mut); font-size: 0.9rem; margin: 0; }
.av-vault form { position: relative; }
.av-altlink { text-align: center; margin: 1.4rem 0 0; font-size: 0.9rem; color: var(--av-mut); }

/* The auth sigil feels alive: a gentle lift + glow when the head is hovered. */
.av-vault-sigil { transition: transform 0.4s var(--av-ease), filter 0.4s ease; }
.av-vault-head:hover .av-vault-sigil {
  transform: scale(1.06) rotate(8deg);
  filter: drop-shadow(0 0 10px rgba(240, 169, 60, 0.45));
}

/* ---------- Unseal-on-submit flourish (av-auth.js) ----------
   On a real sign-in / create, the seal opens and the card settles back as the
   request flies out. Reduced motion neutralises the keyframes (global block),
   so it simply submits. */
.av-vault.av-unsealing .av-vault-card {
  animation: av-vault-open 0.7s var(--av-ease) forwards;
}
.av-vault.av-unsealing .av-vault-sigil {
  animation: av-sigil-unseal 0.7s var(--av-ease) forwards;
}
.av-btn-busy { position: relative; opacity: 0.92; cursor: progress; }
.av-btn-busy::after {
  content: "";
  position: absolute;
  top: 50%; right: 1rem;
  width: 14px; height: 14px;
  margin-top: -7px;
  border: 2px solid rgba(10, 17, 36, 0.35);
  border-top-color: var(--av-ink);
  border-radius: 50%;
  animation: av-spin 0.7s linear infinite;
}
/* The default spinner ink is tuned for the amber primary button; on ghost
   submits (Users / Moderate action forms) dark-on-dark is invisible, so
   swap to the light text tones. */
.av-btn-ghost.av-btn-busy::after {
  border-color: rgba(231, 237, 251, 0.25);
  border-top-color: var(--av-text);
}
@keyframes av-vault-open {
  40% { transform: translateY(-2px); box-shadow: 0 30px 80px rgba(240, 169, 60, 0.20); }
  100% { transform: translateY(0); box-shadow: 0 26px 72px rgba(0, 0, 0, 0.45); }
}
@keyframes av-sigil-unseal {
  0% { transform: scale(1) rotate(0); opacity: 1; }
  55% { transform: scale(1.35) rotate(160deg); opacity: 1; filter: drop-shadow(0 0 16px rgba(240, 169, 60, 0.7)); }
  100% { transform: scale(2.1) rotate(220deg); opacity: 0; }
}

/* Capitalise dynamic names (e.g. "admin" -> "Admin") without altering stored data. */
.av-cap { text-transform: capitalize; }

/* ---------- Auth split-screen (login brand panel + card) ---------- */
.av-auth { padding: clamp(1.5rem, 5vw, 3.5rem) 0; }
.av-auth-grid {
  display: grid;
  grid-template-columns: 1.02fr 0.98fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
  min-height: 58vh;
}
.av-auth-grid .av-vault { margin: 0; width: 100%; max-width: 30rem; justify-self: center; }
.av-auth-brand { max-width: 31rem; }
/* line-height matches the heading leading this element wore as an h2 — it is
   now a <p> (the card's h1 must open the outline) and reboot's p default
   would otherwise loosen it. */
.av-auth-title { font-size: clamp(2rem, 4vw, 3rem); margin: 0.2rem 0 1rem; line-height: 1.2; }
.av-auth-lead { font-size: 1.02rem; margin-bottom: 0; }
.av-auth-feats { list-style: none; padding: 0; margin: 2rem 0 0; display: grid; gap: 1.15rem; }
.av-auth-feat { display: flex; gap: 0.85rem; align-items: flex-start; }
.av-auth-feat-ico {
  flex: none;
  width: 38px; height: 38px;
  border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(240, 169, 60, 0.12);
  color: var(--av-amber);
  border: 0.5px solid var(--av-amber-line);
}
.av-auth-feat-ico svg { width: 19px; height: 19px; }
.av-auth-feat-txt { display: flex; flex-direction: column; gap: 0.12rem; }
.av-auth-feat-h { color: var(--av-text); font-weight: 500; font-size: 0.98rem; }
.av-auth-feat-p { color: var(--av-mut); font-size: 0.88rem; line-height: 1.5; }
@media (max-width: 860px) {
  .av-auth-grid { grid-template-columns: 1fr; gap: 2.4rem; min-height: 0; }
  .av-auth-brand { order: 2; max-width: 32rem; margin: 0 auto; text-align: center; }
  .av-auth-feats { text-align: left; max-width: 23rem; margin-left: auto; margin-right: auto; }
}

/* ---------- Privacy policy document ---------- */
.av-policy { max-width: 44rem; padding-top: clamp(1.6rem, 5vw, 3rem); padding-bottom: 1.5rem; }
.av-policy-head { margin-bottom: 1.4rem; }
.av-policy-title { font-size: clamp(2.2rem, 5vw, 3.2rem); margin: 0 0 0.6rem; }
.av-policy-meta {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--av-mut);
  margin: 0 0 1.3rem;
}
.av-policy-intro { font-size: 1.05rem; margin-bottom: 0; }
.av-policy-section { padding: 1.8rem 0; border-top: 0.5px solid var(--av-line); }
.av-policy-h {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.5rem;
  color: var(--av-text);
  margin: 0 0 0.7rem;
}
.av-policy-section p { color: var(--av-text-2); line-height: 1.75; margin: 0 0 0.8rem; }
.av-policy-section p:last-child { margin-bottom: 0; }
.av-policy-list { margin: 0.5rem 0 0; padding-left: 1.15rem; color: var(--av-text-2); line-height: 1.7; }
.av-policy-list li { margin-bottom: 0.55rem; }
.av-policy-section strong { color: var(--av-text); font-weight: 600; }

/* ---------- Footer ---------- */
.av-footer {
  border-top: 0.5px solid var(--av-line);
  padding: 1.7rem 0;
  margin-top: 4rem;
  color: var(--av-mut);
  font-size: 0.86rem;
  background: rgba(6, 11, 26, 0.4);
}
.av-footer a { color: var(--av-sky); }
.av-footer a:hover { color: var(--av-amber); }

/* ---------- Alerts / toast ---------- */
.alert {
  background: rgba(14, 22, 48, 0.92);
  border: 0.5px solid var(--av-line-2);
  border-left-width: 3px;
  color: var(--av-text);
  border-radius: 12px;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.alert-success { border-left-color: var(--av-ok-soft); }
.alert-danger { border-left-color: var(--av-coral); }
.alert-info { border-left-color: var(--av-sapphire); }
.alert-warning { border-left-color: var(--av-amber); }

/* ---------- Generic page heading ---------- */
.av-page-head { padding: clamp(2rem, 6vw, 4rem) 0 1rem; }

/* ---------- Data pages (Categories — Phase 4) ---------- */
.av-data { padding-top: clamp(1.6rem, 5vw, 3rem); }
.av-head-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.8rem;
}
.av-data-title { font-size: clamp(2rem, 5vw, 3rem); margin: 0 0 0.5rem; }
.av-head-row .av-lead { font-size: 0.98rem; }

/* ---------- List-page shell ----------
   The form pages have shared a surface language since av-vault-card; the 24
   list/index/dashboard pages never did — each hand-rolled its own panel
   (background/border/radius/shadow) with small drifts nobody chose: 1px
   borders against the app's 0.5px, 0.75rem radii against 16px, and no
   elevation at all. These two surfaces are that missing shell. Pages keep
   their genuinely page-specific rules; what they stop owning is the surface.

   .av-panel     — the standard raised block (filters, summaries, chat panes).
   .av-row-card  — one item in a vertical list (a notification, a trade
                   request, a review). Lighter: elev-1 at rest, and a flex
                   row because every consumer lays out [body | actions]. */
.av-panel {
  background: rgba(14, 22, 48, 0.72);
  border: 0.5px solid var(--av-line-2);
  border-radius: 16px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--av-elev-2);
  padding: 1.5rem 1.6rem;
}
.av-row-list { display: flex; flex-direction: column; gap: 0.85rem; }
.av-row-card {
  background: rgba(14, 22, 48, 0.55);
  border: 0.5px solid var(--av-line);
  border-radius: 12px;
  box-shadow: var(--av-elev-1);
  padding: 1.1rem 1.35rem;
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}
/* Attention accent (unread, needs-action): a left rule plus a wash — never
   colour alone, the same non-colour-only rule the unread dot follows. */
.av-row-card--accent {
  border-left: 3px solid var(--av-amber);
  background: color-mix(in srgb, var(--av-amber) 4%, rgba(14, 22, 48, 0.55));
}

/* Themed table surface.
   The layered background doubles as a static scroll cue for narrow screens
   (the classic background-attachment trick): the two edge "covers" scroll
   WITH the table content (attachment: local) while the two soft shadows stay
   pinned to the wrap's edges — so a shadow only shows on a side that still
   has columns hidden beyond it, and vanishes once you reach that end. Pure
   static CSS: no animation, nothing for prefers-reduced-motion to switch off.
   Covers are the panel colour (#0c132a = rgb(12,19,42)) and wider than the
   15px shadows, so a non-scrolling table shows no shadow at all. */
.av-table-wrap {
  background:
    linear-gradient(to right, #0c132a 45%, rgba(12, 19, 42, 0)) left center / 46px 100% no-repeat,
    linear-gradient(to left, #0c132a 45%, rgba(12, 19, 42, 0)) right center / 46px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(2, 5, 14, 0.9), transparent) left center / 15px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(2, 5, 14, 0.9), transparent) right center / 15px 100% no-repeat,
    rgba(12, 19, 42, 0.66);
  background-attachment: local, local, scroll, scroll;
  border: 0.5px solid var(--av-line-2);
  border-radius: 16px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--av-elev-3);
  /* overflow-x (not hidden) so narrow screens can scroll to trailing columns
     instead of having them clipped; border-radius still clips the corners. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* --bs-table-color matters as much as --bs-table-bg: Bootstrap paints every
   CELL with `.table > :not(caption) > * > *` (0-1-1), which silently beats
   any bare tone class (0-1-0) AND the table-level color below — so every
   plain td in the app was rendering Bootstrap's #fff, not the theme's
   muted text. Re-theming the variable makes Bootstrap's own rule carry the
   vault tone, per this file's re-theme-the-variables strategy. */
.av-table { color: var(--av-text-2); margin: 0; --bs-table-bg: transparent; --bs-table-color: var(--av-text-2); }
.av-table > :not(caption) > * > * { background: transparent; box-shadow: none; }
.av-table thead th {
  color: var(--av-mut);
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-bottom: 0.5px solid var(--av-line-2);
  padding: 0.95rem 1.2rem;
}
.av-table tbody td {
  border-top: 0.5px solid var(--av-line);
  padding: 0.85rem 1.2rem;
  vertical-align: middle;
}
.av-table tbody tr { transition: background-color 0.18s ease; }
.av-table tbody tr:hover { background: rgba(77, 124, 255, 0.07); }
/* .av-table-qualified so it outranks Bootstrap's 0-1-1 cell rule — a bare
   .av-cell-name silently lost to it (masked only because Bootstrap's #fff
   looks like --av-text on a dark panel). */
.av-table .av-cell-name { color: var(--av-text); }
.av-col-actions { width: 1%; white-space: nowrap; }

/* Monospace code pill — the category's identity */
.av-code {
  font-family: 'Cascadia Code', 'Consolas', ui-monospace, monospace;
  font-size: 0.82rem;
  letter-spacing: 0.03em;
  color: var(--av-sky);
  background: rgba(77, 124, 255, 0.12);
  border: 0.5px solid var(--av-line-2);
  border-radius: 7px;
  padding: 0.18rem 0.55rem;
}

/* Star rating — paired with the _StarRating partial, which is the only thing
   that renders these. Four pages defined .av-stars locally and two of them
   meant something different by it (text glyphs, not SVG). Base size is 1rem;
   pages that want a larger run scale it from an ancestor rather than
   redefining the class. */
.av-stars {
  display: inline-flex;
  gap: 0.08rem;
  vertical-align: middle;
  line-height: 1;
  font-size: 1rem;
  white-space: nowrap;
}
.av-star { width: 1em; height: 1em; }
.av-star.on { color: var(--av-amber); }
/* Hollow stars read in the muted TEXT tone. Never --av-line-2 here: the
   30%-alpha line token is a border colour and is unreadable as an icon fill,
   which leaves the empty stars invisible and loses the "out of 5" context.
   Shops/Details had already reasoned its way to --av-mut locally; Insights was
   still on the line token. Fixing it here is what stops the two diverging. */
.av-star.off { color: var(--av-mut); }

/* Provenance / ledger hash. Four pages had defined this class themselves —
   three byte-identical, and Wallet/Receipt differing only in how it breaks,
   because a receipt prints the whole hash where a table shows a truncated one.
   That single real difference is the --wrap modifier; everything else was copy.
   Note the print override lives with the receipt, which is the only page that
   is ever printed. */
.av-hash {
  font-family: var(--bs-font-monospace, monospace);
  font-size: 0.72rem;
  color: var(--av-mut);
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.av-hash--wrap {
  white-space: normal;
  word-break: break-all;
}

/* Shared status pill — ONE recipe (12% wash) for every status/sentiment pill,
   so pages stop hand-rolling drifting copies. Tones now come from the
   --av-ok / --av-bad / --av-warn state tokens rather than repeated literals.
   Base chrome + exactly one tone modifier. */
.av-pill {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: 2rem;
  padding: 0.2em 0.65em;
  border: 1px solid;
  white-space: nowrap;
}
.av-pill--good {
  color: var(--av-ok);
  border-color: var(--av-ok);
  background: color-mix(in srgb, var(--av-ok) 12%, transparent);
}
.av-pill--bad {
  color: var(--av-bad);
  border-color: var(--av-bad);
  background: color-mix(in srgb, var(--av-bad) 12%, transparent);
}
.av-pill--warn {
  color: var(--av-warn);
  border-color: var(--av-warn);
  background: color-mix(in srgb, var(--av-warn) 12%, transparent);
}
/* Provenance verify banner — shape only; the tone comes from composing with
   .av-pill--good / --bad. Was defined byte-identically on both provenance
   pages (Collection/Details and Marketplace/Details); it lives here now,
   next to the tones it borrows. */
.av-verify-banner {
  border: 1px solid;
  border-radius: 0.75rem;
  padding: 0.7rem 1rem;
  margin: 0 0 1rem;
  font-size: 0.9rem;
}

.av-pill--neutral {
  color: var(--av-mut);
  border-color: var(--av-line-2);
  background: color-mix(in srgb, var(--av-mut) 12%, transparent);
}
/* "Waiting on someone" — the palette's own amber, kept distinct from --warn so
   a review the AI HELD still reads differently from one merely PENDING. Both
   are warm; only these two ever sit side by side (Reviews/Moderate). */
.av-pill--amber {
  color: var(--av-amber);
  border-color: var(--av-amber);
  background: color-mix(in srgb, var(--av-amber) 12%, transparent);
}

.av-row-actions { display: inline-flex; gap: 0.4rem; justify-content: flex-end; }

/* Shared action cluster. Eight pages had invented their own name for "a row of
   buttons that wraps" — av-note-actions, av-req-actions, av-table-actions,
   av-broadcast-actions, av-mod-actions, av-convo-actions — and two of those
   were byte-identical. One base plus four modifiers covers every real
   difference between them; anything not expressible here stays on the page. */
.av-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.av-actions--wide { gap: 0.6rem; }
.av-actions--end { justify-content: flex-end; align-items: center; }
/* Stacked + right-aligned: moderation queues put their verdict buttons in a
   column beside the review text rather than under it. */
.av-actions--stack { flex-direction: column; align-items: flex-end; gap: 0.4rem; }
/* Detached from the content above it — form footers and panel footers. */
.av-actions--spaced { margin-top: 1.25rem; }
/* Destructive row actions read as dangerous at rest too — hover-only styling
   never shows on touch devices, and rest coral separates them from safe
   ghost buttons (same coral the validation text already uses). */
.av-btn-danger {
  color: var(--av-coral-soft);
  border-color: rgba(255, 122, 107, 0.4);
}
.av-btn-danger:hover {
  border-color: var(--av-coral);
  color: var(--av-coral-soft);
  background: rgba(255, 122, 107, 0.08);
  box-shadow: 0 0 0 3px rgba(255, 122, 107, 0.12);
}
/* Touch screens get taller row buttons (btn-sm is ~31px, under the 44px touch
   minimum — and on moderation pages the small targets are the destructive
   ones). Coarse-pointer only, so desktop table density is unchanged. The
   selector list names every btn-sm cluster (table rows, card grids, review
   moderation, shop owner forms) — a new cluster joins the list, it doesn't
   invent its own bump. */
@media (pointer: coarse) {
  .av-row-actions .btn-sm,
  .av-item-actions .btn-sm,
  .av-mod-actions .btn-sm,
  .av-owner-form .btn-sm { padding-block: 0.6rem; }
  /* Category chips are inline-flex + centred, so a floor grows them
     symmetrically; the .av-checks gap absorbs the row growth. */
  .av-check { min-height: 44px; }
  /* The intro gate's skip is a bare text button (~20px) on a full-screen
     scrim where a miss lands on nothing — give the box honest height and
     trim its margin so the gate's rhythm holds. */
  .av-gate-skip { min-height: 44px; padding-inline: 1rem; margin-top: 0.35rem; }
}
.av-table-count {
  color: var(--av-mut);
  font-size: 0.84rem;
  margin: 1rem 0.4rem 0;
  text-align: right;
}

/* Empty state */
.av-empty {
  text-align: center;
  padding: clamp(2.4rem, 7vw, 4rem) 1.5rem;
  background: rgba(12, 19, 42, 0.5);
  border: 0.5px dashed var(--av-line-2);
  border-radius: 18px;
}
/* The icon sits on an engraved amber plinth — the landing's .av-tile-ico
   language, so an empty vault reads as designed rather than unfinished.
   border-box sizing keeps the drawn glyph ~34px inside the 64px face. */
.av-empty svg {
  width: 64px;
  height: 64px;
  padding: 15px;
  color: var(--av-amber);
  opacity: 1;
  background: rgba(240, 169, 60, 0.10);
  border: 0.5px solid var(--av-amber-line);
  border-radius: 16px;
  box-shadow: var(--av-elev-1);
}
.av-empty-title { font-size: 1.6rem; margin: 1rem 0 0.3rem; }
.av-empty-sub { color: var(--av-mut); margin: 0; }

/* Form action row + hints (shared by Create / Edit / Details / Delete) */
.av-form-actions {
  display: flex;
  gap: 0.7rem;
  justify-content: flex-end;
  flex-wrap: wrap;
  margin-top: 1.6rem;
}
.av-field-hint { display: block; color: var(--av-mut); font-size: 0.8rem; margin-top: 0.4rem; }
/* A LINK wearing the hint class (Login's "Forgot password?") must not read as
   caption text: keep the hint's scale but restore link colour and add the
   WCAG 1.4.1 underline; 0-1-1 beats the class rule above, and the explicit
   hover keeps the house amber (source order would otherwise lose it). */
a.av-field-hint {
  color: var(--av-sky);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: from-font;
}
a.av-field-hint:hover { color: var(--av-amber); }
@media (pointer: coarse) {
  /* The established hit-halo pattern (av-interactions.css) — the ~19px link
     gets a 44px reach without growing the drawn text. */
  a.av-field-hint { position: relative; }
  a.av-field-hint::before { content: ""; position: absolute; inset: -12px; }
}

/* Detail list (Details / Delete) */
.av-detail-list { margin: 0; }
.av-detail-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.8rem 0;
  border-top: 0.5px solid var(--av-line);
}
.av-detail-row:first-child { border-top: 0; }
.av-detail-row dt {
  color: var(--av-mut);
  font-size: max(0.72rem, 12px); /* 12px floor at the 15px phone root */
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 500;
}
.av-detail-row dd { margin: 0; color: var(--av-text); text-align: right; }

/* Delete is a destructive primary action — tint the confirm button red.
   Rest bg is #c0392b (not the lighter coral) so white text clears WCAG AA
   4.5:1; hover brightens to coral with dark text, which passes on its own. */
.av-btn-confirm-danger {
  --bs-btn-bg: #c0392b;
  --bs-btn-border-color: #c0392b;
  --bs-btn-color: #fff;
  --bs-btn-hover-bg: var(--av-coral);
  --bs-btn-hover-border-color: var(--av-coral);
  --bs-btn-hover-color: #1a0c0a;
  --bs-btn-active-bg: var(--av-coral);
  --bs-btn-active-border-color: var(--av-coral);
}
.av-btn-confirm-danger:hover { box-shadow: 0 8px 24px rgba(255, 122, 107, 0.28); }

/* ---------- Collection items (Phase 5) ---------- */
/* Search bar above the table */
.av-search {
  display: flex;
  gap: 0.6rem;
  margin-bottom: 1.4rem;
  flex-wrap: wrap;
}
.av-search .form-control { flex: 1 1 16rem; }
/* Browse cards adds a franchise <select> to this same search bar; without a
   basis it shrinks to its content width in a flex row and crowds the input
   next to it, so give it a fixed lane like the text field gets. */
.av-search .form-select { flex: 0 1 11rem; }

/* Wider auth-style card for the item forms (more fields than login) */
.av-vault-wide { max-width: 38rem; }

/* Category tags shown inline in tables / details */
.av-cat-tags { display: inline-flex; flex-wrap: wrap; gap: 0.35rem; }
.av-cat-tags-end { justify-content: flex-end; }
.av-tag {
  font-size: 0.76rem;
  color: var(--av-sky);
  background: rgba(77, 124, 255, 0.12);
  border: 0.5px solid var(--av-line-2);
  border-radius: 999px;
  padding: 0.12rem 0.62rem;
  white-space: nowrap;
}
.av-mut-dash { color: var(--av-mut); }

/* "Available to trade" badge */
.av-badge-trade {
  display: inline-block;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--av-amber-soft);
  background: rgba(240, 169, 60, 0.13);
  border: 0.5px solid var(--av-amber-line);
  border-radius: 999px;
  padding: 0.16rem 0.66rem;
}

/* Category checkbox chips on Create / Edit */
.av-checks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
}
.av-check {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  background: rgba(6, 11, 26, 0.5);
  border: 0.5px solid var(--av-line-2);
  border-radius: 10px;
  padding: 0.5rem 0.85rem;
  color: var(--av-text-2);
  font-size: 0.92rem;
  transition: border-color 0.18s ease, color 0.18s ease, background-color 0.18s ease;
}
.av-check:hover { border-color: var(--av-sapphire); color: var(--av-text); }
.av-check input { accent-color: var(--av-amber); width: 1rem; height: 1rem; }
.av-check:has(input:checked) {
  border-color: var(--av-amber-line);
  background: rgba(240, 169, 60, 0.08);
  color: var(--av-text);
}
.av-check-wide { width: 100%; }

@media (max-width: 560px) {
  .av-row-actions { flex-wrap: wrap; }
  .av-head-row .btn-primary { align-self: stretch; }
}

/* ---------- Animations ---------- */
@keyframes av-spin { to { transform: rotate(360deg); } }
@keyframes av-pulse {
  0%, 100% { opacity: 0.6; transform: scale(0.85); }
  50% { opacity: 1; transform: scale(1.12); }
}
@keyframes av-breathe {
  0%, 100% { opacity: 0.6; transform: scale(0.96); }
  50% { opacity: 1; transform: scale(1.05); }
}
@keyframes av-shimmer { from { background-position: 220% center; } to { background-position: -120% center; } }
@keyframes av-rise { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }

/* Reveal-on-scroll (progressive enhancement: only hides when JS adds .js-reveal). */
.js-reveal .reveal { opacity: 0; transform: translateY(22px); }
.js-reveal .reveal.is-visible { animation: av-rise 0.7s var(--av-ease) forwards; }

/* ---------- Motion elevation ("the living vault") ---------- */

/* Stagger: a container marked .av-stagger gets a --av-i on each revealed child
   (set in site.js), so siblings cascade in one after another. For non-staggered
   reveals --av-i is unset, so the delay is zero. */
.js-reveal .reveal.is-visible { animation-delay: calc(var(--av-i, 0) * var(--av-stagger)); }

/* Count-up numbers shouldn't reflow as their digit count changes. */
.av-stat-num { font-variant-numeric: tabular-nums; }

/* The Unseal — the signature. When the vault card comes into view its sigil
   strokes draw themselves on (dasharray long enough to cover any one stroke),
   the centre seal pops, then the fields cascade. */
.av-seal-draw { stroke-dasharray: 1000; stroke-dashoffset: 1000; }
.js-reveal .av-vault.is-visible .av-seal-draw {
  animation: av-draw var(--av-t-seal) var(--av-ease) forwards;
}
.js-reveal .av-vault.is-visible .av-seal-draw.av-seal-2 { animation-delay: 0.22s; }
.js-reveal .av-vault.is-visible .av-seal-draw.av-seal-3 { animation-delay: 0.40s; }
.av-seal-dot { opacity: 0; transform-box: fill-box; transform-origin: center; }
.js-reveal .av-vault.is-visible .av-seal-dot { animation: av-pop 0.5s var(--av-ease) 1s forwards; }

.js-reveal .av-vault .av-cascade { opacity: 0; }
.js-reveal .av-vault.is-visible .av-cascade {
  animation-name: av-rise;
  animation-duration: var(--av-t-rise);
  animation-timing-function: var(--av-ease);
  animation-fill-mode: both;
}

/* Seal-tick — a hairline that "draws" under a page's eyebrow, carrying the
   unseal motif onto every elevated page. One-time, so it settles open under
   reduced motion too. */
.av-seal-tick::after {
  content: "";
  display: block;
  height: 1.5px;
  width: 0;
  margin-top: 0.55rem;
  background: linear-gradient(90deg, var(--av-amber), transparent);
  animation: av-tick 0.9s var(--av-ease) 0.4s forwards;
}

/* Bolder, more tactile data rows (only where a real cursor can hover): a warmer
   amber wash, the name lifts into amber, and the row's actions — dimmed at
   rest — surface on hover or keyboard focus. */
@media (hover: hover) {
  .av-row-actions { opacity: 0.42; transition: opacity var(--av-t-fast) ease; }
  .av-table tbody tr:hover .av-row-actions,
  .av-row-actions:focus-within { opacity: 1; }
}
.av-table tbody tr:hover { background: rgba(240, 169, 60, 0.07); }
.av-table tbody tr:hover .av-cell-name { color: var(--av-amber-soft); transition: color var(--av-t-fast) ease; }

@keyframes av-drift { from { transform: translateY(0); } to { transform: translateY(-18px); } }
@keyframes av-aura { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
@keyframes av-draw { to { stroke-dashoffset: 0; } }
@keyframes av-pop { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: none; } }
@keyframes av-tick { to { width: 2.6rem; } }

/* ============================================================
   Content-depth layer — a richer landing, a reusable "held"
   meter, page summary strips, and deeper dashboard stats.
   Everything here is built only from data the pages already
   have (presentation only) and extends the existing tokens and
   the existing .reveal motion rather than replacing anything.
   ============================================================ */

/* ---------- Section rhythm (marketing landing) ---------- */
.av-section { padding-top: clamp(3rem, 8vw, 5.5rem); }
.av-section-head { max-width: 40rem; margin: 0 0 2rem; }
.av-section-head .av-display { font-size: clamp(1.9rem, 4vw, 2.85rem); margin: 0 0 0.7rem; }
.av-section-head .av-lead { margin-bottom: 0; }

/* ---------- The held meter ----------
   How much of an item you still hold (current ÷ starting), or
   any other share. The bar reads sapphire→amber so a fuller
   hold leans toward the vault's amber. --av-pct sets the width;
   the fill scales in on reveal (and is simply full when JS is
   off or reduced motion is on). */
.av-meter {
  height: 5px;
  border-radius: 999px;
  background: rgba(77, 124, 255, 0.14);
  overflow: hidden;
}
.av-meter-fill {
  display: block;
  height: 100%;
  width: var(--av-pct, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, var(--av-sapphire), var(--av-amber));
  transform-origin: left center;
}
.av-meter-row { display: flex; align-items: center; gap: 0.65rem; }
.av-meter-row .av-meter { flex: 1; min-width: 3rem; }
.av-meter-val {
  font-variant-numeric: tabular-nums;
  font-size: 0.82rem;
  color: var(--av-text-2);
  white-space: nowrap;
}
.av-meter-val b { color: var(--av-text); font-weight: 600; }

/* Fill animates in when its revealed container becomes visible. */
.js-reveal .reveal .av-meter-fill { transform: scaleX(0); }
.js-reveal .reveal.is-visible .av-meter-fill {
  animation: av-meter 0.9s var(--av-ease) 0.2s forwards;
}
@keyframes av-meter { to { transform: scaleX(1); } }

/* ---------- Product preview: "A vault, in practice" ----------
   A faux collection ledger that shows the real product on the
   landing page instead of describing it. Reuses the table look
   and the item tags/badges so it reads as the genuine article. */
.av-ledger {
  background: rgba(12, 19, 42, 0.70);
  border: 0.5px solid var(--av-line-2);
  border-radius: 16px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.40);
  overflow: hidden;
}
.av-ledger-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.3rem;
  border-bottom: 0.5px solid var(--av-line);
  background: rgba(6, 11, 26, 0.45);
}
.av-ledger-seal {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--av-amber);
  box-shadow: 0 0 0 3px rgba(240, 169, 60, 0.16);
  animation: av-pulse 3.6s ease-in-out infinite;
}
.av-ledger-bar-label {
  font-family: var(--font-rune);
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--av-text-2);
}
.av-ledger-bar-count { margin-left: auto; font-size: 0.78rem; color: var(--av-mut); }
.av-ledger-head,
.av-ledger-row {
  display: grid;
  grid-template-columns: 1.5fr 1.3fr 1.1fr;
  gap: 1.1rem;
  align-items: center;
  padding: 0.9rem 1.3rem;
}
.av-ledger-head {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.64rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--av-mut);
  border-bottom: 0.5px solid var(--av-line);
}
.av-ledger-row { border-top: 0.5px solid var(--av-line); }
.av-ledger-row:first-of-type { border-top: 0; }
.av-ledger-name { color: var(--av-text); display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
@media (max-width: 620px) {
  .av-ledger-head { display: none; }
  .av-ledger-row { grid-template-columns: 1fr; gap: 0.55rem; }
}

/* ---------- Capability cards (landing) ----------
   Replaces the three thin tiles with deeper, specific cards,
   each closing on a concrete example from the product. */
.av-caps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  gap: 1.1rem;
}
.av-cap-card {
  display: flex;
  flex-direction: column;
  background: rgba(14, 22, 48, 0.55);
  border: 0.5px solid var(--av-line);
  border-radius: 16px;
  padding: 1.6rem 1.45rem;
  box-shadow: var(--av-elev-1);
  transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}
/* Was flat at rest and only grew a shadow on hover, so the cards read as
   painted onto the page until you touched one. elev-1 → elev-3 keeps the same
   lift while giving them a resting surface. */
.av-cap-card:hover {
  transform: translateY(-5px);
  border-color: var(--av-amber-line);
  box-shadow: var(--av-elev-3);
}
.av-cap-card h3, .av-cap-card h2 { font-size: 1.32rem; margin: 0 0 0.45rem; }
.av-cap-card p { font-size: 0.92rem; color: var(--av-mut); line-height: 1.6; margin: 0 0 1.1rem; }
.av-cap-eg {
  margin-top: auto;
  font-family: 'Cascadia Code', 'Consolas', ui-monospace, monospace;
  font-size: 0.74rem;
  letter-spacing: 0.02em;
  color: var(--av-sky);
  background: rgba(77, 124, 255, 0.10);
  border: 0.5px solid var(--av-line);
  border-radius: 8px;
  padding: 0.5rem 0.7rem;
}
.av-cap-eg span { color: var(--av-mut); }

/* ---------- Closing call-to-action band (landing) ---------- */
.av-cta-band {
  margin-top: clamp(3rem, 8vw, 5.5rem);
  text-align: center;
  padding: clamp(2.6rem, 6vw, 4.2rem) 1.5rem;
  border: 0.5px solid var(--av-line-2);
  border-radius: 22px;
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(640px 320px at 50% -30%, rgba(77, 124, 255, 0.18), transparent 62%),
    rgba(12, 19, 42, 0.55);
}
.av-cta-band .av-display { font-size: clamp(2rem, 5vw, 3rem); margin: 0 0 0.8rem; }
.av-cta-band .av-lead { margin: 0 auto 1.9rem; }
.av-cta-band .av-cta-row { justify-content: center; }

/* ---------- Dashboard stats: deeper ---------- */
.av-stat-sub { font-size: 0.82rem; color: var(--av-mut); line-height: 1.4; }
.av-stat-meter { margin-top: 0.5rem; }

/* ---------- Page summary strip (collection / categories) ---------- */
.av-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem 2.4rem;
  padding: 1.1rem 1.45rem;
  margin-bottom: 1.4rem;
  background: rgba(12, 19, 42, 0.50);
  border: 0.5px solid var(--av-line);
  border-radius: 14px;
}
.av-summary-item { display: flex; flex-direction: column; gap: 0.2rem; }
.av-summary-num {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.55rem;
  line-height: 1;
  color: var(--av-amber);
  font-variant-numeric: tabular-nums;
}
.av-summary-label {
  font-size: max(0.66rem, 12px); /* 12px floor — these labels carry real data */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--av-mut);
}

/* A subdued clarifier inside a table heading (e.g. "current / starting"). */
.av-th-note { text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--av-mut); opacity: 0.8; }

/* Held meter sized for the item-details row */
.av-detail-held { width: 14rem; max-width: 60vw; }

/* Held cell inside the collection table */
.av-held { display: flex; flex-direction: column; gap: 0.4rem; min-width: 7rem; }
.av-held-num { font-variant-numeric: tabular-nums; color: var(--av-text-2); font-size: 0.9rem; }
.av-held-num b { color: var(--av-text); font-weight: 600; }

/* ============================================================
   "Enter the vault" portal — the anonymous landing hero.
   The 3D seal (av-seal.js) renders into .av-seal-canvas; the
   .av-seal-fallback SVG takes over whenever the 3D layer can't
   run. A one-time intro gate (.av-gate) is revealed by JS for
   first-time visitors only. All presentation — no data.
   ============================================================ */
.av-portal { position: relative; }

/* Full-viewport stage only once the 3D layer is live. */
.av-portal.av-3d-on { min-height: clamp(560px, 92vh, 900px); display: flex; align-items: center; }
.av-portal.av-3d-on .av-portal-hero { width: 100%; }

/* The 3D canvas sits behind the copy; shown only when the 3D layer is on. */
.av-seal-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: none;
  cursor: grab;
  touch-action: none;
}
.av-portal.av-3d-on .av-seal-canvas { display: block; }

/* Inline 3D seal for the signed-in dashboard hero — same seal, no intro gate.
   When the 3D layer can't run, the square collapses and the SVG fallback shows. */
.av-hero-seal { position: relative; }
.av-hero-seal-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  cursor: grab;
  touch-action: none;
}
.av-hero-seal.av-3d-on { width: clamp(220px, 30vw, 360px); height: clamp(220px, 30vw, 360px); margin-inline: auto; }
.av-hero-seal.av-3d-on .av-hero-seal-canvas { display: block; }
.av-hero-seal.av-3d-on .av-seal-fallback { display: none; }

/* Copy floats above the canvas; the canvas keeps the pointer for drag/hover,
   while the actual links stay clickable. */
.av-portal-hero { position: relative; z-index: 2; }
.av-portal.av-3d-on .av-portal-hero { pointer-events: none; }
.av-portal.av-3d-on .av-portal-hero a,
.av-portal.av-3d-on .av-portal-hero button { pointer-events: auto; }

/* When the 3D seal is live the static SVG sigil is redundant — hide it.
   When it isn't (no WebGL / reduced motion) the SVG is the hero, as before. */
.av-portal.av-3d-on .av-seal-fallback { display: none; }

/* Copy fades in as the gate clears. */
.av-portal-copy { transition: opacity 0.8s var(--av-ease); }
.av-portal.av-gate-open .av-portal-copy { opacity: 0; }

/* Playable-seal hint — only meaningful when the 3D layer is live. */
.av-seal-controls { display: none; }
.av-portal.av-3d-on .av-seal-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 1.4rem;
  justify-content: center;
  position: absolute;
  left: 0; right: 0; bottom: 1.4rem;
  z-index: 2;
  margin: 0;
  pointer-events: none;
  font-family: var(--font-ui);
  font-size: max(0.75rem, 12px); /* 12px floor — visible copy, not ornament */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--av-mut);
  transition: opacity 0.5s ease;
}
.av-portal.av-gate-open .av-seal-controls,
.av-seal-controls.av-controls-hide { opacity: 0; }
.av-seal-controls span { display: inline-flex; align-items: center; gap: 0.5rem; }
.av-seal-controls span::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--av-amber);
  opacity: 0.7;
}

/* ---------- The one-time intro gate ---------- */
.av-gate {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1.5rem;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(60% 60% at 50% 45%, rgba(6, 11, 26, 0.35), rgba(6, 11, 26, 0.78) 75%);
  transition: opacity 0.7s var(--av-ease);
}
.av-portal.av-gate-open .av-gate { opacity: 1; pointer-events: auto; }
.av-gate-inner { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.av-gate-title {
  font-family: var(--font-rune);
  font-size: clamp(2.4rem, 7vw, 4rem);
  letter-spacing: 0.16em;
  color: var(--av-text);
  margin: 0.4rem 0 0.2rem;
  text-shadow: 0 0 34px rgba(6, 11, 26, 0.9);
}
.av-gate-sub {
  color: var(--av-text-2);
  font-size: 1rem;
  max-width: 26rem;
  margin: 0 0 1.6rem;
  text-shadow: 0 0 18px rgba(6, 11, 26, 0.9);
}
.av-gate .btn-primary { letter-spacing: 0.02em; }
.av-gate-skip {
  background: none;
  border: 0;
  color: var(--av-mut);
  font-size: 0.84rem;
  letter-spacing: 0.04em;
  margin-top: 0.9rem;
  cursor: pointer;
  transition: color 0.2s ease;
}
.av-gate-skip:hover { color: var(--av-text-2); }

/* ============================================================
   Splash — the standalone front door at "/". A sealed, idle 3D
   seal (av-seal.js → #av-splash-canvas) sits behind a single
   "Enter the vault" call to action that leads to /Home, where
   the seal plays its unseal "blowup". The .av-splash-fallback
   SVG is the centrepiece whenever the 3D layer can't run.
   All presentation — no data.
   ============================================================ */
.av-splash {
  position: relative;
  min-height: calc(100svh - 4.6rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Centre the copy stack so it sits directly OVER the centred seal — the mark
     reads through its own name rather than glowing in a separate band above. */
  justify-content: center;
  text-align: center;
  overflow: hidden;
  padding: clamp(2.5rem, 8vh, 6rem) 1.5rem clamp(3.5rem, 10vh, 6rem);
  isolation: isolate;
}

/* Ambient cinematic field — slow-drifting sapphire/amber light blobs and a
   glow pool that haloes the seal. Sits at the very back of the stage. */
.av-splash-ambient {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.av-blob {
  position: absolute;
  border-radius: 50%;
  /* Lighter blur + paint containment: a smaller rasterised layer the compositor
     can move cheaply. The drift animates translate ONLY (no scale — see the
     keyframes), so the blurred layer is rasterised once and never re-rastered;
     translateZ(0) + will-change pin it to its own GPU layer so each blob slides
     on the compositor instead of repainting the page. */
  filter: blur(42px);
  opacity: 0.45;
  transform: translateZ(0);
  will-change: transform;
  contain: paint;
}
.av-blob-1 {
  width: 42vw; height: 42vw;
  left: -6vw; top: 4vh;
  background: radial-gradient(circle, rgba(77, 124, 255, 0.55), rgba(77, 124, 255, 0) 70%);
  animation: avBlobDrift1 26s ease-in-out infinite;
}
.av-blob-2 {
  width: 38vw; height: 38vw;
  right: -8vw; top: -4vh;
  background: radial-gradient(circle, rgba(240, 169, 60, 0.40), rgba(240, 169, 60, 0) 70%);
  animation: avBlobDrift2 32s ease-in-out infinite;
}
.av-blob-3 {
  width: 46vw; height: 46vw;
  left: 30vw; bottom: -18vh;
  background: radial-gradient(circle, rgba(77, 124, 255, 0.34), rgba(77, 124, 255, 0) 72%);
  animation: avBlobDrift3 38s ease-in-out infinite;
}

/* Pool of light behind the seal — its halo. Centred on the stage, which is
   where the 3D seal (and the fallback sigil) render. */
.av-splash-pool {
  position: absolute;
  left: 50%; top: 50%;
  width: clamp(440px, 68vw, 760px);
  height: clamp(440px, 68vw, 760px);
  transform: translate(-50%, -50%);
  background:
    radial-gradient(circle, rgba(246, 197, 114, 0.18), rgba(246, 197, 114, 0) 42%),
    radial-gradient(circle, rgba(77, 124, 255, 0.26), rgba(77, 124, 255, 0) 64%);
  filter: blur(8px);
  animation: avPoolPulse 7s ease-in-out infinite;
}

/* The 3D canvas fills the stage and centres the seal (inset:0 from the base
   rule). The console no longer sits below it — the copy now overlays the seal —
   so there is no lift: the mark is centred behind the headline and glows
   through it. */
.av-splash .av-seal-canvas {
  z-index: 1;
}
.av-splash.av-3d-on .av-seal-canvas { display: block; }

/* Cinematic vignette — darkens the stage edges for depth. Centre stays clear
   so the seal glows through; the frosted console carries text readability. */
.av-splash-veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /* Pushed the dark ramp outward so the centred seal/logo stays clear — the
     vignette now only shades the true edges, not the mark itself. */
  background: radial-gradient(135% 130% at 50% 48%, rgba(6, 11, 26, 0) 58%, rgba(6, 11, 26, 0.42) 84%, rgba(6, 11, 26, 0.8) 100%);
}

/* Static sigil centrepiece — only when the 3D seal can't run. */
.av-splash-fallback {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: auto;
  width: clamp(260px, 58vw, 460px);
  height: clamp(260px, 58vw, 460px);
  opacity: 0.6;
  display: grid;
  place-items: center;
}
.av-splash-fallback svg { width: 100%; height: 100%; }
.av-splash.av-3d-on .av-splash-fallback { display: none; }

/* Pre-paint hide: when the head probe decides 3D will run, hide every static
   seal fallback before the first paint so a refresh never flashes the old static
   sigil. If 3D then fails to actually start (Three.js didn't load), av-seal.js
   sets av-3d-off on the container and the matching rule below restores it. The
   av-3d-off restores carry one more class than the probe rule, so they win. */
html.av-3d-probe .av-splash-fallback,
html.av-3d-probe .av-seal-fallback { display: none; }
.av-splash.av-3d-off .av-splash-fallback { display: grid; }
.av-portal.av-3d-off .av-seal-fallback { display: flex; }
.av-hero-seal.av-3d-off .av-seal-fallback { display: block; }

/* The copy console — now panel-less, sitting directly over the centred seal so
   the mark glows through the title. There is NO backdrop-filter: the old frosted
   panel re-blurred the live WebGL seal every frame (the chief source of the lag)
   and boxed the words off from the mark. Legibility instead comes from a single
   STATIC radial scrim — a darkened pool under the text that fades to nothing at
   the rim, rasterised once and never recomputed per frame. With the 3D layer
   live the console is pointer-transparent so the seal stays draggable behind it;
   only the actual links/buttons keep the pointer. */
.av-splash-inner {
  position: relative;
  z-index: 3;
  width: min(36rem, 100%);
  margin: 0 auto;
  padding: clamp(1.4rem, 4vw, 2.4rem) clamp(1.4rem, 5vw, 3rem);
  background: radial-gradient(120% 84% at 50% 46%,
    rgba(6, 11, 26, 0.74) 0%,
    rgba(6, 11, 26, 0.5) 40%,
    rgba(6, 11, 26, 0) 74%);
}
.av-splash.av-3d-on .av-splash-inner { pointer-events: none; }
.av-splash.av-3d-on .av-splash-inner a,
.av-splash.av-3d-on .av-splash-inner button { pointer-events: auto; }

.av-splash-eyebrow {
  font-family: var(--font-ui);
  font-size: max(0.75rem, 12px); /* letterpress micro-label, floored at legible */
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--av-amber-soft);
  margin: 0 0 0.5rem;
}
/* The signature: the mark seen through its own name. The fill is mostly
   transparent so the glowing seal behind shows through the letter interiors,
   while a luminous near-opaque stroke keeps every glyph crisp even where the
   seal runs dark. The sapphire glow ties the type to the mark; the tight dark
   halo holds legibility over the busy centre. */
.av-splash-title {
  font-family: var(--font-rune);
  font-size: clamp(2.6rem, 7.4vw, 4.3rem);
  letter-spacing: 0.16em;
  line-height: 1.04;
  margin: 0 0 0.85rem;
  color: rgba(228, 237, 255, 0.14);
  -webkit-text-stroke: 1.1px rgba(216, 230, 255, 0.92);
  text-shadow:
    0 0 28px rgba(77, 124, 255, 0.42),
    0 0 7px rgba(6, 11, 26, 0.72);
}
/* Where -webkit-text-stroke isn't honoured, fall back to a solid readable fill
   so the title is never a faint ghost. */
@supports not ((-webkit-text-stroke: 1px #000) or (text-stroke: 1px #000)) {
  .av-splash-title { color: var(--av-text); }
}
.av-splash-sub {
  color: var(--av-text-2);
  font-size: 1.02rem;
  line-height: 1.6;
  max-width: 28rem;
  margin: 0 auto 1.8rem;
}
.av-splash-cta { display: flex; justify-content: center; }
.av-splash-cta .btn {
  position: relative;
  letter-spacing: 0.04em;
  padding-inline: 2.2rem;
  box-shadow: 0 0 0 1px rgba(246, 197, 114, 0.25), 0 14px 34px -10px rgba(240, 169, 60, 0.55);
  transition: box-shadow 0.3s var(--av-ease), transform 0.3s var(--av-ease);
}
.av-splash-cta .btn:hover {
  box-shadow: 0 0 0 1px rgba(246, 197, 114, 0.45), 0 18px 46px -8px rgba(240, 169, 60, 0.72);
}
.av-splash-alt { margin: 1.4rem 0 0; color: var(--av-mut); font-size: 0.92rem; }

/* Playable-seal hint — only meaningful when the 3D seal is live. */
.av-splash-controls {
  display: none;
  position: absolute;
  left: 0; right: 0; bottom: 1.6rem;
  z-index: 3;
  justify-content: center;
  gap: 1.4rem;
  margin: 0;
  pointer-events: none;
  font-family: var(--font-ui);
  font-size: max(0.75rem, 12px); /* 12px floor — visible copy, not ornament */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--av-mut);
  transition: opacity 0.5s ease;
}
.av-splash.av-3d-on .av-splash-controls { display: flex; }
.av-splash-controls.av-controls-hide { opacity: 0; }
.av-splash-controls span { display: inline-flex; align-items: center; gap: 0.5rem; }
.av-splash-controls span::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--av-amber);
  opacity: 0.7;
}

/* Ambient drift — slow, low-amplitude wander so the field never feels static
   yet never distracts. Each blob travels on its own clock. Translate ONLY (kept
   on the GPU via translateZ(0)): scaling a blurred layer forces a per-frame
   re-rasterise, whereas translate is a pure compositor transform — the drift now
   costs almost nothing per frame while reading identically. */
@keyframes avBlobDrift1 {
  0%, 100% { transform: translate(0, 0) translateZ(0); }
  50% { transform: translate(6vw, 4vh) translateZ(0); }
}
@keyframes avBlobDrift2 {
  0%, 100% { transform: translate(0, 0) translateZ(0); }
  50% { transform: translate(-5vw, 6vh) translateZ(0); }
}
@keyframes avBlobDrift3 {
  0%, 100% { transform: translate(0, 0) translateZ(0); }
  50% { transform: translate(-4vw, -5vh) translateZ(0); }
}
/* Pool pulse — opacity only. Dropping the scale stops the blurred halo from
   re-rasterising each frame; the breathing now reads purely as a glow. */
@keyframes avPoolPulse {
  0%, 100% { opacity: 0.85; }
  50% { opacity: 1; }
}

/* Leaving transition — the console, vignette and ambient field fade while the
   seal swells toward the viewer as we follow "Enter" to /Home, where the blowup
   picks up. The 0.42s here matches the 420ms navigation timeout in av-seal.js,
   so the fade lands exactly as the next page loads — no flash, no hard cut. */
.av-splash.av-leaving .av-splash-inner,
.av-splash.av-leaving .av-splash-veil,
.av-splash.av-leaving .av-splash-ambient,
.av-splash.av-leaving .av-splash-controls {
  opacity: 0;
  /* Promote to a GPU layer for the duration of the leave so the fade is
     composited, not painted, frame by frame. */
  will-change: opacity;
  transition: opacity 0.42s var(--av-ease);
}
/* The seal swells on an ease-in curve so it accelerates outward as we leave —
   winding up into the unseal "blowup" that plays on /Home rather than a fade.
   will-change keeps the swell+fade on the compositor for a jank-free hand-off. */
.av-splash.av-leaving .av-seal-canvas,
.av-splash.av-leaving .av-splash-fallback {
  opacity: 0;
  transform: scale(1.18);
  will-change: transform, opacity;
  transition: opacity 0.42s var(--av-ease), transform 0.42s cubic-bezier(0.4, 0, 1, 1);
}

@media (prefers-reduced-motion: reduce) {
  .av-splash-fallback svg :where(.av-spin, .av-spin-rev) { animation: none; }
  .av-blob, .av-splash-pool { animation: none; }
}

/* ============================================================
   Pointer tilt (av-tilt.js) — opt-in depth on card surfaces.
   The JS sets the rotation + sheen position; CSS does the rest.
   ============================================================ */
[data-av-tilt] {
  transition: transform 0.16s var(--av-ease), box-shadow 0.3s ease, border-color 0.3s ease;
  transform-style: preserve-3d;
  will-change: transform;
}
[data-av-tilt]::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%), rgba(174, 194, 242, 0.12), transparent 45%);
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
[data-av-tilt]:hover::before { opacity: 1; }
[data-av-tilt]:hover {
  border-color: var(--av-amber-line);
  box-shadow: 0 22px 54px rgba(0, 0, 0, 0.42);
}

/* ---------- Collection as a card grid ----------
   Presentation of the very same items the table showed — name, tags, the
   current/starting held meter, the tradeable badge, and the same actions. */
.av-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  gap: 1.1rem;
}
.av-item-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  background: rgba(12, 19, 42, 0.66);
  border: 0.5px solid var(--av-line-2);
  border-radius: 16px;
  padding: 1.3rem 1.35rem;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--av-elev-2);
}
.av-item-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.6rem; }
.av-item-name { font-family: var(--font-display); font-size: 1.3rem; color: var(--av-text); margin: 0; line-height: 1.2; }
.av-item-card .av-cat-tags { margin: 0; }
.av-item-card .av-mut-dash { color: var(--av-mut); font-size: 0.88rem; }
.av-item-held { display: flex; flex-direction: column; gap: 0.4rem; margin-top: auto; }
.av-item-actions { display: flex; gap: 0.4rem; padding-top: 0.35rem; }
.av-item-actions .btn { flex: 1; }

/* ---------- Themed error page (404 / 500 / 403) ---------- */
.av-error {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 68vh;
  text-align: center;
  padding: 2.5rem 1rem;
}
.av-error-card { max-width: 36rem; }
.av-error-sigil { width: 84px; height: 84px; opacity: 0.92; }
/* The 3D seal sits a touch smaller here than on the dashboard. */
.av-error-seal.av-3d-on { width: clamp(190px, 26vw, 280px); height: clamp(190px, 26vw, 280px); margin: 0 auto 0.4rem; }
.av-error-code {
  font-family: var(--font-display);
  font-size: clamp(4rem, 14vw, 7rem);
  line-height: 1;
  color: var(--av-amber);
  margin: 0.3rem 0 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}
.av-error-title { font-size: clamp(1.6rem, 4vw, 2.4rem); margin: 0.2rem 0 0.7rem; }
.av-error-blurb { color: var(--av-text-2); font-size: 1.02rem; line-height: 1.65; max-width: 30rem; margin: 0 auto 1.8rem; }
.av-error-req { margin-top: 1.7rem; font-size: 0.8rem; color: var(--av-mut); }
.av-error-req code { color: var(--av-sky); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js-reveal .reveal { opacity: 1; transform: none; }
  /* Guarantee the motion-elevation elements are fully visible (never mid-draw)
     for anyone who has asked for reduced motion. */
  .js-reveal .av-vault .av-cascade { opacity: 1; }
  .av-seal-draw { stroke-dashoffset: 0; }
  .av-seal-dot { opacity: 1; }
  .av-seal-tick::after { width: 2.6rem; }
  /* Held meters settle full (at their --av-pct width) rather than mid-fill. */
  .js-reveal .reveal .av-meter-fill,
  .av-meter-fill { transform: none; }
}

/* ---------- Phase 8: Vault Connect + polish ---------- */

/* 1. Skip-link target offset — compensates for the sticky navbar so the
      browser doesn't scroll the heading under the bar. */
#main { scroll-margin-top: 5rem; }

/* 2. Privacy / policy in-prose links — underline so they meet WCAG 1.4.1
      (information conveyed by colour alone is insufficient). The auth and
      splash cross-links ("Create your vault", "Sign in", "Back to sign in")
      sit in the same muted prose and get the same treatment. */
.av-policy-intro a,
.av-policy-section a,
.av-altlink a,
.av-splash-alt a {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: from-font;
}

/* 3. Active-nav indicator — JS marks the current link with .active and
      aria-current="page"; we persist the amber underline that the ::after
      pseudo-element already draws on hover, and keep the text fully bright.
      (Below lg the navbar region hides the underline and swaps in a 2px
      left rule — these two rules are the lg-and-up half of the state.) */
.av-navbar .nav-link.active { color: var(--av-text); }
.av-navbar .nav-link.active::after { transform: scaleX(1); }

/* 4. Toast motion — slide-in on appear; JS adds .av-toast-hide before
      auto-dismiss so the element eases out before removal.
      The global prefers-reduced-motion block above already collapses
      animation/transition durations to 0.001ms, so no extra query needed. */
@keyframes av-toast-in {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: none; }
}
.av-toast { animation: av-toast-in 0.4s var(--av-ease); }
.av-toast.av-toast-hide {
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.35s var(--av-ease), transform 0.35s var(--av-ease);
}

/* 5. Held-preview widget for collection Create/Edit forms.
      JS builds: <div class="av-held-preview">
                   <div class="av-meter"><span class="av-meter-fill"></span></div>
                   <span class="av-held-preview-text"></span>
                 </div>
      and toggles .is-invalid when Current Held > Starting Held. */
.av-held-preview { margin-top: 0.6rem; }
.av-held-preview-text {
  display: block;
  font-size: 0.82rem;
  color: var(--av-text-2);
  margin-top: 0.45rem;
  font-variant-numeric: tabular-nums;
}
/* Error state — fill turns warm-red, label shifts to a lighter coral. */
.av-held-preview.is-invalid .av-meter-fill  { background: var(--av-coral); }
.av-held-preview.is-invalid .av-held-preview-text { color: var(--av-coral-soft); }

/* User avatar rendered by the shared _Avatar partial. The <img> points at the
   /Avatar/{userName} proxy, which returns either the stored photo or an SVG
   monogram of the same square shape. border-radius makes it a disc; object-fit
   keeps a non-square upload from stretching; the panel tint only shows in the
   brief moment before the image paints. */
.av-avatar {
  border-radius: 50%;
  object-fit: cover;
  background: var(--av-panel);
  border: 1px solid var(--av-line);
  vertical-align: middle;
  flex-shrink: 0;
}

/* Profile picture editor: the large preview beside its upload/remove controls.
   Wraps to a stacked column on narrow screens so nothing overflows the card. */
.av-avatar-edit {
  display: flex;
  gap: 1.2rem;
  align-items: flex-start;
  flex-wrap: wrap;
  margin-bottom: 1.6rem;
}
.av-avatar-lg { width: 96px; height: 96px; border-width: 2px; }
.av-avatar-controls { flex: 1 1 15rem; min-width: 13rem; }
.av-avatar-remove { margin-top: 0.7rem; }

/* ---------- Card art (W7) ---------- */
/* Sized by aspect-ratio so a missing image never causes a layout jump, and
   clipped with the same corner radius as the card it sits in.
   Token note: this file has no --av-space-* or --av-surface-* spacing/surface
   scale (checked before writing this) — the brief's --av-space-3 / --av-surface-2
   were placeholders. The bleed margin below is a literal 0.75rem (the same
   value the brief's own var() fallback used), and the placeholder background
   reuses --av-panel-2, the nearest existing "raised surface" tone in this
   file, instead of inventing a new token or a generic translucent white.

   Task 10 fix: a flat -0.75rem bleed only matched neither host card's real
   padding, so the art sat inset ~0.6rem from the rounded top corner instead
   of flush. .av-card-art is used inside two different padded surfaces —
   .av-item-card (Collection grid + Browse gallery, padding 1.3rem 1.35rem,
   border-radius 16px) and .av-vault-card (Details, padding 2.4rem 2.2rem,
   border-radius 20px) — so the bleed margin and top corner radius are scoped
   per host below instead of guessing one number that fits neither.

   Final-review fix: on Details, .av-vault-wide caps the card at 38rem and
   the old rule let the art bleed full-width — at ~608px wide with the 5/7
   aspect-ratio that is ~851px tall, so on a 1080p screen the item's <h1>
   sits below the fold. Fixed by capping .av-vault-card .av-card-art to
   18rem and centring it with margin:auto, instead of full bleed. The top
   margin stays -2.4rem (exactly cancels the card's own top padding) so the
   art is still flush against the card's top edge — the .av-vault-card
   parent already clips with overflow:hidden + its own 20px radius, so a
   centred, narrower image only needs rounding on the two corners that are
   still visible: the bottom ones. 14px (< the card's 20px outer radius) reads
   right on the narrower 18rem frame. The Collection grid rule
   (.av-item-card .av-card-art) is untouched by this — it was already a
   sane ~361px at the grid's own column width.

   Second fix, same review: .av-item-card is a flex column with gap: 0.85rem,
   and this rule's own 0.75rem bottom margin used to stack with that gap,
   leaving 1.6rem between the art and the item title — visibly looser than
   every other 0.85rem gap between this card's rows. Dropped the bottom
   margin component here so the flex gap alone sets the spacing, consistent
   with the rest of the card. */
.av-card-art {
  margin: -0.75rem -0.75rem 0.75rem;
  border-radius: 0.75rem 0.75rem 0 0;
  overflow: hidden;
  background: var(--av-panel-2);
}
.av-item-card .av-card-art {
  margin: -1.3rem -1.35rem 0;
  border-radius: 16px 16px 0 0;
}
.av-vault-card .av-card-art {
  max-width: 18rem;
  margin: -2.4rem auto 1.2rem;
  border-radius: 0 0 14px 14px;
}

.av-card-art img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;          /* the trading-card ratio all three APIs use */
  object-fit: cover;
  object-position: top center;
}

/* The tilt effect the item cards already use looks better with the art
   slightly larger than its frame, so a hover reveals more of the picture. */
.av-item-card[data-av-tilt]:hover .av-card-art img {
  transform: scale(1.03);
  transition: transform 240ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .av-item-card[data-av-tilt]:hover .av-card-art img { transform: none; }
}

/* ---------- Franchise card picker (W7 wave 2 polish) ----------
   Token note: same check as the .av-card-art comment above — this file has
   no --av-space-* / --av-surface-* scale, so spacing here is literal rem
   values and surfaces reuse the existing panel/line tokens rather than
   inventing new ones. Focus rings on the option buttons fall through to the
   file's global `:focus-visible` rule (no outline: none anywhere here), which
   is what makes the focus moves in av-card-picker.js (select / remove art)
   actually visible to a keyboard user. */
.av-card-picker {
  border: 0.5px solid var(--av-line-2);
  border-radius: 14px;
  padding: 1.1rem 1.15rem 1.3rem;
  margin: 0 0 1.4rem;
  background: rgba(12, 19, 42, 0.45);
}
.av-card-picker legend {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--av-mut);
  padding: 0 0.2rem;
}
.av-help {
  color: var(--av-mut);
  font-size: 0.86rem;
  margin: 0.2rem 0 0.9rem;
}

.av-card-picker-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 0.9rem;
}
.av-card-picker-controls .form-select { flex: 0 1 9rem; }
.av-card-picker-controls .form-control { flex: 1 1 12rem; }

/* Announced via aria-live in the markup; this is purely the visual side. */
.av-card-picker-results { min-height: 1.4rem; }
.av-card-picker-results .av-mut-dash { margin: 0; font-size: 0.86rem; }

/* Responsive thumbnail grid — auto-fill + minmax wraps to fewer columns as
   the viewport narrows rather than overflowing; verified against a 375px
   viewport inside the .av-vault-wide card. */
.av-card-picker-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: 0.7rem;
  max-height: 20rem;
  overflow-y: auto;
}
.av-card-picker-option {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.35rem;
  width: 100%;
  background: rgba(6, 11, 26, 0.5);
  border: 0.5px solid var(--av-line-2);
  border-radius: 10px;
  padding: 0.5rem;
  cursor: pointer;
  color: var(--av-text-2);
  font-size: 0.76rem;
  line-height: 1.3;
  text-align: left;
  transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease;
}
.av-card-picker-option:hover {
  border-color: var(--av-sapphire);
  color: var(--av-text);
  background: rgba(77, 124, 255, 0.08);
}
.av-card-picker-option img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  border-radius: 6px;
  background: var(--av-panel-2);
}

.av-card-picker-chosen {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.9rem;
  padding: 0.65rem 0.9rem;
  background: rgba(240, 169, 60, 0.08);
  border: 0.5px solid var(--av-amber-line);
  border-radius: 10px;
  color: var(--av-text);
  font-size: 0.88rem;
}

@media (max-width: 400px) {
  .av-card-picker-controls .form-select,
  .av-card-picker-controls .form-control { flex-basis: 100%; }
  .av-card-picker-list { grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr)); }
}

/* ============================================================
   Folio detailing kit (Phase 8 UI pass, 2026-08-01).
   The anti-"templated" layer: small bespoke details shared by the
   demo-path pages so surfaces read as crafted artifacts rather than
   themed Bootstrap. Everything here is decoration-only — no layout
   participation, no JS, and nothing for reduced-motion to switch off.
   ============================================================ */

/* Engraved corner ticks — four L-shaped registration marks, the same
   visual language as the seal's cardinal ticks. Pure background-images
   (two 1px strokes per corner), so they cost no elements and no reflow.
   Sits at the END of the file on purpose: surfaces like .av-panel set
   the `background` SHORTHAND, and these longhands must win per-property,
   which at equal specificity they only do by source order. */
.av-ticked {
  --av-tick: rgba(240, 169, 60, 0.5);
  background-image:
    linear-gradient(var(--av-tick), var(--av-tick)), linear-gradient(var(--av-tick), var(--av-tick)),
    linear-gradient(var(--av-tick), var(--av-tick)), linear-gradient(var(--av-tick), var(--av-tick)),
    linear-gradient(var(--av-tick), var(--av-tick)), linear-gradient(var(--av-tick), var(--av-tick)),
    linear-gradient(var(--av-tick), var(--av-tick)), linear-gradient(var(--av-tick), var(--av-tick));
  background-repeat: no-repeat;
  background-size:
    14px 1px, 1px 14px,
    14px 1px, 1px 14px,
    14px 1px, 1px 14px,
    14px 1px, 1px 14px;
  background-position:
    10px 10px, 10px 10px,
    calc(100% - 10px) 10px, calc(100% - 10px) 10px,
    10px calc(100% - 10px), 10px calc(100% - 10px),
    calc(100% - 10px) calc(100% - 10px), calc(100% - 10px) calc(100% - 10px);
}

/* The vault-desk ledger band — ONE ruled editorial band in place of a row
   of identical stat tiles. Hairline rules separate the figures; the lead
   figure is set larger and in amber (statement hierarchy: one number is
   the headline, the rest are supporting). Ink numbers use the display
   serif with tabular figures so the countup doesn't jitter the layout. */
.av-desk-band {
  display: flex;
  flex-wrap: wrap;
  row-gap: 1.6rem;
  /* background-COLOR longhand on purpose: the shorthand would reset the
     .av-ticked corner-mark background-images this band composes with —
     the exact per-property cascade trap the kit's header warns about. */
  background-color: rgba(14, 22, 48, 0.72);
  border: 0.5px solid var(--av-line-2);
  border-radius: 16px;
  box-shadow: var(--av-elev-2);
  padding: 2rem 2.2rem 1.8rem;
}
.av-desk-fig {
  flex: 1 1 10rem;
  min-width: 9rem;
  padding: 0.2rem 1.7rem 0;
  border-left: 0.5px solid var(--av-line);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.av-desk-fig:first-child { border-left: 0; padding-left: 0; }
.av-ink-num {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.3rem, 4vw, 3.1rem);
  line-height: 0.95;
  color: var(--av-text);
  font-variant-numeric: tabular-nums;
}
.av-desk-fig--lead .av-ink-num {
  font-size: clamp(3.4rem, 6.5vw, 4.8rem);
  color: var(--av-amber);
}
.av-desk-label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--av-text-2);
  padding-bottom: 0.4rem;
  /* Double hairline rule: the border is the lower line, the inset
     box-shadow trick draws the second 1px line 3px above it. */
  border-bottom: 1px solid var(--av-line-2);
  box-shadow: 0 -3px 0 -2px var(--av-line) inset;
  align-self: flex-start;
}
.av-desk-sub { font-size: 0.8rem; color: var(--av-mut); }

/* Phone width: the band stacks into a two-column ledger; rules rotate to
   row separators so the hairline language survives the reflow. */
@media (max-width: 640px) {
  .av-desk-band { padding: 1.5rem 1.4rem; }
  .av-desk-fig { flex-basis: 40%; border-left: 0; padding: 0.9rem 0 0; border-top: 0.5px solid var(--av-line); }
  .av-desk-fig:first-child { border-top: 0; padding-top: 0; flex-basis: 100%; }
}

/* Editorial page masthead (Folio) — title block left, a small ticked
   plaque right holding one ink figure. The oversized clamp is the
   statement move: a page title that reads as a masthead, not a form
   label. Used by the demo-path pages as they migrate off the uniform
   head-row rhythm. */
.av-masthead {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
  margin-bottom: 2.2rem;
}
.av-masthead-title { font-size: clamp(2.6rem, 6vw, 4.4rem); margin: 0 0 0.5rem; line-height: 1.02; }
.av-masthead-title em { color: var(--av-amber-soft); }
.av-plaque {
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.45rem;
  padding: 1.1rem 1.5rem 1rem;
  background-color: rgba(14, 22, 48, 0.55);
  border: 0.5px solid var(--av-line-2);
  border-radius: 12px;
  box-shadow: var(--av-elev-1);
}

/* Ruled section heading — the text sits on a hairline that runs out to
   the right edge, an editorial section break instead of a bare h2. */
.av-rule-head {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  font-size: 1.5rem;
  margin: 2.8rem 0 1.1rem;
}
.av-rule-head::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--av-line-2), transparent);
}

/* ============================================================
   Wallet & Insights finish pass (2026-08-01, appended after the
   folio kit on purpose — same source-order rule as .av-ticked).
   Three pieces: the navbar Vault-Coins chip (WalletChip view
   component), the wallet hero's gilded top edge, and the Insights
   "what this measures" line under chart-card titles.
   ============================================================ */

/* ── Navbar Vault-Coins chip ─────────────────────────────────────────
   A quiet pill beside the bell: hairline border, amber balance, muted
   coin glyph in the bell's stroke language. Deliberately QUIETER than
   the bell's unread state — the bell announces, the chip just states.
   44px min height keeps the touch target honest between the greeting
   and the bell. */
.av-coin-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 44px;
  /* No side margins: the right rail's own column-gap (navbar region) is
     the one source of spacing between rail neighbours. */
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--av-line-2);
  border-radius: 999px;
  color: var(--av-mut);
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease,
              background-color 0.15s ease;
}
.av-coin-chip:hover,
.av-coin-chip:focus-visible {
  border-color: var(--av-amber);
  color: var(--av-amber);
  background: rgba(240, 169, 60, 0.08);
}
.av-coin-chip svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.av-coin-chip-num {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--av-amber);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.av-coin-chip-unit {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--av-mut);
  line-height: 1;
}
/* Collapsed navbar (below lg) stacks the nav items — the pill keeps its
   shape but stops floating oddly by picking up a little block breathing
   room, mirroring the bell's collapsed adjustment. */
@media (max-width: 991.98px) {
  .av-coin-chip { margin-block: 0.25rem; }
}
/* Tightest phones: the amber number IS the meaning; the "VC" unit is
   the first thing to go when the rail runs out of room. */
@media (max-width: 420px) {
  .av-coin-chip-unit { display: none; }
}
/* Compact laptop band (992–1199px): the same trade as the phone rule
   above — the navbar's tightest desktop band (see the navbar
   responsiveness section) needs the width more than the chip needs its
   unit, and the amber number still carries the meaning. */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .av-coin-chip { padding: 0.25rem 0.6rem; }
  .av-coin-chip-unit { display: none; }
}

/* ── Gilded top edge ─────────────────────────────────────────────────
   The app's quiet-gold signature ornament: a 3px strip across a card's
   top edge, amber → soft amber → amber, sliding slowly. Gradient starts
   and ends on the same amber so the 200% tile loops seamlessly. Reusable:
   any card can take .av-gilded-top (the Wallet balance hero is the first).
   position:relative + overflow:hidden keep the strip inside the card's
   rounded corners. */
.av-gilded-top {
  position: relative;
  overflow: hidden;
}
.av-gilded-top::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg,
    var(--av-amber), var(--av-amber-soft), var(--av-amber));
  background-size: 200% 100%;
  animation: av-gild-slide 7s linear infinite;
  pointer-events: none;
}
@keyframes av-gild-slide {
  from { background-position: 0% 0; }
  to   { background-position: 200% 0; }
}
/* Static fallback: the gilded strip STAYS (it is information-free
   decoration either way) — only the slide stops. */
@media (prefers-reduced-motion: reduce) {
  .av-gilded-top::before { animation: none; }
}

/* ── Insights chart-card "what this measures" line ───────────────────
   One muted sentence tucked under .av-chart-card-title (which carries
   1.1rem bottom margin — the negative top margin pulls the sentence to
   0.4rem below its title so the pair reads as one unit, with the full
   1.1rem restored before the chart). */
.av-chart-sub {
  color: var(--av-mut);
  font-size: 0.84rem;
  line-height: 1.5;
  margin: -0.7rem 0 1.1rem;
  max-width: 62ch;
}
