/* ──────────────────────────────────────────────────────────────────────────
   Vectro Family — token sheet.

   Drop this at the TOP of internal/web/static/app.css, above the existing
   component rules, then replace hard-coded hex values in those rules with the
   variables below. No preprocessor, no build step — this is the whole system.

   Light is the default. Dark is an explicit override in two directions:
   the media query honours prefers-color-scheme, and [data-theme] on <html>
   (written by the parent's own choice, persisted server-side) wins over it.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Self-hosted type (phase 8) ───────────────────────────────────────────
   Figtree ships as ONE variable-font woff2 (wght 300–900 in the file; this
   @font-face declares the 400–800 range the product actually uses) instead
   of four static files, so every weight below comes from a single request.
   Fraunces (the display face) is one static instanced weight, declared 400.

   Both are self-hosted under internal/web/static/fonts — no external host,
   ever (see scripts/design-guards.sh external). Reproduce or re-subset with
   scripts/subset-fonts.sh; internal/web/static/fonts/fonts-manifest.txt?v=a4e60474
   records the sha256 of each shipped file and proves ș (U+0219) / ț (U+021B)
   survive the subset. font-display: swap so first paint is never blocked on
   the font request. */
@font-face {
  font-family: "Figtree";
  src: url("/static/fonts/figtree-variable.woff2?v=5b75f745") format("woff2-variations"),
       url("/static/fonts/figtree-variable.woff2?v=5b75f745") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

/* Fraunces, not the design's Caprasimo: Caprasimo has no Romanian diacritics
   in any build (upstream TTF and Google's hosted latin-ext both lack
   Ăă/Șș/Țț, cmap-verified), so the child hero could not be spelled in it.
   Replacement approved by the product owner 2026-08-05 — decisions record #6
   in vectro-goals.md. This is a static Black instance (opsz 144, SOFT 100)
   served as the face's one weight, the same single-weight contract Caprasimo
   had. */
@font-face {
  font-family: "Fraunces";
  src: url("/static/fonts/fraunces-display.woff2?v=0c94b8a8") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ── Colour, light ── */
  --v-bg:          #f5ead8; /* page ground. Nothing is written directly on it. */
  --v-paper:       #fbf6ec; /* surface for real values */
  --v-sunk:        #eee4d0; /* table headers, inert strips */

  --v-ink:         #173f35; /* brand + primary text + the "real" material */
  --v-ink-2:       #49675f; /* secondary text — 6.3:1 on paper */
  --v-ink-3:       #516961; /* labels, eyebrows, 9.5–12px notes — 4.7:1 min */

  --v-hair:        rgba(23,63,53,.16);
  --v-hair-strong: rgba(23,63,53,.38);

  --v-edu:         #7a8a5e; /* the educational voice. Never on real money. */
  --v-edu-ink:     #4a5636; /* text on educational tints */
  --v-edu-tint:    #eef3e0;

  --v-attn:        #8c491a; /* "this is waiting for you" / "this price is old" */
  --v-attn-tint:   #ffe9d8; /* NOT an error colour */

  --v-err:         #8a2f25;
  --v-err-tint:    #fbe7e2;
  --v-ok:          #2f6b53;
  --v-ok-tint:     #e2efe6;

  --v-focus:       #c06b2e; /* unchanged: 3px ring, 3px offset */
  --v-hatch:       rgba(23,63,53,.22); /* the missing-value hatch */

  /* ── Shape ── */
  --r-xs: 6px; --r-sm: 10px; --r-md: 14px; --r-lg: 24px; --r-pill: 999px;
  /* --r-sm/--r-md = parent register (dense, instrument-grade)
     --r-lg        = child register (warm, generous) */

  /* ── Space, 4px scale ── */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 18px;
  --s-5: 26px; --s-6: 36px; --s-7: 52px;

  /* ── Elevation ── */
  --e-1: 0 1px 2px rgba(32,30,29,.10);
  --e-2: 0 3px 10px rgba(32,30,29,.13);
  --e-3: 0 14px 34px rgba(32,30,29,.20);

  /* ── Motion ── */
  --t-fast: 120ms; --t-mid: 220ms; --t-slow: 420ms;
  --ease: cubic-bezier(.2,.7,.3,1);

  /* ── Type ── */
  --font-body:    "Figtree", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-display: "Fraunces", Georgia, serif; /* child register + landing ONLY */

  /* Fluid scale, step ~1.22. Note the page title tops out at 2.6rem, not
     6.5rem: the poster headline is kept for the landing page and the child
     register and removed from every utility form. */
  --fs-eyebrow: 0.66rem;
  --fs-note:    0.72rem;
  --fs-small:   0.79rem;
  --fs-body:    clamp(0.88rem, 0.85rem + 0.2vw, 0.95rem);
  --fs-lead:    clamp(1rem, 0.95rem + 0.4vw, 1.13rem);
  --fs-h3:      clamp(1.1rem, 1rem + 0.6vw, 1.3rem);
  --fs-h2:      clamp(1.35rem, 1.2rem + 1vw, 1.65rem);
  --fs-h1:      clamp(1.6rem, 1.35rem + 2vw, 2.6rem);
  --fs-hero:    clamp(2.2rem, 1.6rem + 5vw, 3.4rem); /* landing + child h1 */
}

/* Money, units, percentages, dates — everywhere, without exception. */
.num { font-variant-numeric: tabular-nums; letter-spacing: -0.005em; }

/* A missing value. Never a zero, never a colour swap alone: the hatch has to
   read as "unknown" in greyscale and at any colour vision. */
.hatch {
  background-image: repeating-linear-gradient(
    135deg, var(--v-hatch) 0 1.5px, transparent 1.5px 6px);
}

/* ── Dark, in both directions ─────────────────────────────────────────── */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
  :root:not([data-theme="light"]) {
    --v-bg:#191712; --v-paper:#211f19; --v-sunk:#12110d;
    --v-ink:#f0e8d8; --v-ink-2:#bdb5a3; --v-ink-3:#8d8677;
    --v-hair:rgba(240,232,216,.16); --v-hair-strong:rgba(240,232,216,.34);
    --v-edu:#aebf92; --v-edu-ink:#cddcb4; --v-edu-tint:#282f20;
    --v-attn:#f6a06b; --v-attn-tint:#3a2617;
    --v-err:#f0a79b; --v-err-tint:#3a201b;
    --v-ok:#8ec7ab;  --v-ok-tint:#1c2c25;
    --v-focus:#f6a06b;
    --v-hatch:rgba(240,232,216,.24);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --v-bg:#191712; --v-paper:#211f19; --v-sunk:#12110d;
  --v-ink:#f0e8d8; --v-ink-2:#bdb5a3; --v-ink-3:#8d8677;
  --v-hair:rgba(240,232,216,.16); --v-hair-strong:rgba(240,232,216,.34);
  --v-edu:#aebf92; --v-edu-ink:#cddcb4; --v-edu-tint:#282f20;
  --v-attn:#f6a06b; --v-attn-tint:#3a2617;
  --v-err:#f0a79b; --v-err-tint:#3a201b;
  --v-ok:#8ec7ab;  --v-ok-tint:#1c2c25;
  --v-focus:#f6a06b;
  --v-hatch:rgba(240,232,216,.24);
}

/* ── The three materials ──────────────────────────────────────────────────
   These four rules are the whole four-concepts-must-not-blur answer. Fill,
   edge, radius and mark are fixed per material, so colour is never the only
   carrier. Set the class from Go; never hand-style a card. */

.m-real {                      /* the family's real money — private */
  background: var(--v-paper);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
}
.m-shared {                    /* the slice of the real a child may see */
  background: var(--v-edu-tint);
  border: 1px dashed var(--v-edu);
  border-radius: var(--r-sm);
}
.m-edu {                       /* the child's virtual, educational money */
  background: var(--v-edu-tint);
  border: 1.5px solid var(--v-edu);
  border-top-width: 5px;
  border-radius: var(--r-lg);
}
/* The mark that travels with an educational amount, so a screenshot torn out
   of context is still honest. Rendered by the `money` partial, not by hand. */
.edu-mark {
  font-size: 0.62em; font-weight: 800; vertical-align: super;
  letter-spacing: .06em; margin-left: 2px;
}

/* Motion: the existing blanket guarantee stays, so anything added later is
   disabled for a reader who asked for less movement. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ══ END TOKEN SHEET ══════════════════════════════════════════════════════
   Everything below is the product's existing component rules, in their
   original order, with their comments intact. They now read the tokens above
   instead of carrying their own hex, radius and spacing values. */

/* Additions the sheet has no name for, kept below it so everything above stays
   byte-identical to docs/design/vectro-redesign/tokens.css — verify with
   `head -153 internal/web/static/app.css | diff - docs/design/vectro-redesign/tokens.css`.
   Each one exists because a rule below needed a value the sheet does not carry. */
:root {
  /* Text on a --v-ink fill. Paper is ink's opposite in both themes, so this
     needs no dark override; it is named so the primary button does not look
     like it is painting its label with a surface colour by accident. */
  --v-on-ink: var(--v-paper);

  /* A quiet raised surface on the page ground: the secondary button, nav hover,
     the install hint. --v-sunk is the wrong token for these, and wrong only in
     dark: there it recesses below --v-bg, so a cancel button painted with it
     becomes the darkest thing on the screen and reads as a hole punched in the
     page rather than as the quieter of two buttons. In dark, "quiet" is a step
     lighter than the ground, not darker. */
  --v-quiet: var(--v-sunk);

  /* Layout, outside the 4px space scale by nature: that scale stops at 52px and
     these are page-sized. Naming them also makes two of the comments below true
     by construction rather than by promise — .chrome-inner has to repeat
     .shell's horizontal padding, and its column has to match .shell-narrow's. */
  --gutter:     clamp(var(--s-4), 5vw, 5rem);  /* .shell and every band of chrome */
  /* 7vw put the first heading about 90px below the chrome on a desktop window,
     before the eyebrow and the heading's own margins were counted — airy on a
     poster, but it spent the top of every screen that has an answer to give.
     This keeps a comfortable floor on a phone and stops growing early. */
  --page-pad-y: clamp(var(--s-5), 2.5vw, var(--s-7));
  /* Parent screens are scan surfaces: their first useful answer should stay
     above the fold, and a card should not spend more height on its frame than
     on the fact it contains. These are the shared density values for the
     parent register; child screens keep the base rhythm and their deliberate
     warmer, more generous spacing. */
  --parent-page-pad-y: clamp(var(--s-4), 1.8vw, var(--s-6));
  --parent-section-gap: var(--s-4);
  --parent-card-pad: var(--s-4);
  --measure:    38rem;  /* readable text column: .lead, .fine-print */
  --column-max: 48rem;  /* .shell-narrow: the child's screens and the pre-auth flow */
  --page-max:   72rem;  /* every parent screen, and the chrome that aligns to it */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --v-quiet: var(--v-paper); }
}

:root[data-theme="dark"] { --v-quiet: var(--v-paper); }

:root {
  color-scheme: light;
  font-family: var(--font-body);
  background: var(--v-bg);
  color: var(--v-ink);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
}

/* Content starts at the top. That is the default because it is what all but a
   handful of screens want: a balance sheet, a ledger or a list is read from its
   first row, and a column centred inside min-height: 100svh pushes that first
   row toward the middle of the window on any page shorter than the viewport —
   which, with the old --page-pad-y on top of it, was most of the space wasted
   above the fold.

   .shell-centred is the deliberate exception, and the landing page, the sign-in
   flow, the PIN screens and the error pages are the whole list: one short
   statement and one control, which read as composed rather than stranded when
   they sit in the middle of the window. */
.shell {
  display: flex;
  min-height: 100svh;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--page-pad-y) var(--gutter);
  flex-direction: column;
  justify-content: flex-start;
}

.shell-centred {
  justify-content: center;
}

.eyebrow {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.16em;
}

/* The bottom margin belongs on the heading, not on whatever happens to follow
   it. With `margin: 0` the spacing below a heading came entirely from .lead's
   own top margin, so the ten or so forms that have no lead paragraph — most of
   the portfolio and child-management forms — ran their first field label
   straight into the heading above it.

   .shell is a flex column and flex items do not collapse margins, so the rule
   below zeroes .lead's top margin when it follows a heading. That keeps the gap
   on lead-bearing pages to one margin rather than the sum of two.

   The size is --fs-h1, the token the sheet has always carried, and it is the
   DEFAULT rather than something a page opts into. This rule used to hardcode a
   6.5rem poster, which is what the design named as a defect in so many words:
   "titlul de pagină coboară de la 6.5rem la 2.6rem maxim: afișul de azi arată
   bine pe pagina de intrare și absurd pe un formular de evaluare". A poster is
   the exception — see .hero below — so the exception is what carries a class.
   Phase 3 had fixed this the other way round, with a --fs-h1-parent that only
   the eighteen mains someone remembered to mark .page-parent ever got; the
   thirty-eight that were never marked, /portfolio and /children and /orders
   among them, kept the poster. One default and one opt-in cannot drift apart
   that way. Line height and tracking come from that same phase-3 rule: they
   were tuned for a title, and the 0.98/-0.055em they replace was tuned for a
   poster that no longer renders here. */
h1 {
  max-width: 24ch;
  margin: 0 0 var(--s-5);
  font-size: var(--fs-h1);
  line-height: 1.06;
  letter-spacing: -0.03em;
}

/* The poster headline: the display face on exactly two h1s — the landing and
   the child register's hero — as README.md names, restored to both once
   Fraunces replaced Caprasimo (which could not spell ă/ț; see the @font-face
   note and decisions record #6). The face ships one instanced weight declared
   400 — declare it here too, or the browser fakes a bold; the design sets the
   display face untracked at ~1.06, not the Figtree-tuned negative tracking
   above. Money is never set in the display face: the money partial does not
   accept a font override.

   It is also the only h1 allowed to be poster-sized, and it now says so in its
   own rule instead of inheriting it: --fs-hero tops out at 3.4rem against the
   title's 2.6rem. Both are below the 6.5rem this used to inherit — the design
   draws these two headlines at 40px and 30px, so the poster was over even its
   own mark. */
.hero {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-hero);
  letter-spacing: normal;
  line-height: 1.06;
}

.lead {
  max-width: var(--measure);
  margin: var(--s-6) 0;
  font-size: var(--fs-lead);
  line-height: 1.55;
  color: var(--v-ink-2);
}

h1 + .lead {
  margin-top: 0;
}

/* ── Parent density ───────────────────────────────────────────────────────
   The wide shell is the parent register. Keeping this contract on the shared
   shell means a spacing decision reaches every parent page that uses it,
   while the narrow shell remains the child/pre-auth register. Local page
   treatments below may still make a deliberate exception, but they no longer
   need to repeat the baseline rhythm. */
main.shell:not(.shell-narrow) {
  --page-pad-y: var(--parent-page-pad-y);
}

main.shell:not(.shell-narrow) > h1 {
  margin-bottom: var(--s-4);
}

main.shell:not(.shell-narrow) > .lead {
  margin-block: var(--s-4);
}

main.shell:not(.shell-narrow) > h1 + .lead {
  margin-top: 0;
}

main.shell:not(.shell-narrow) > .card:not(.attention-card) {
  margin-block: var(--parent-section-gap);
  padding: var(--parent-card-pad);
}

main.shell:not(.shell-narrow) > .card:not(.attention-card) > h2:not(.attention-head) {
  margin: 0;
  font-size: var(--fs-h2);
  line-height: 1.2;
}

main.shell:not(.shell-narrow) > .card:not(.attention-card) > .fine-print {
  margin: 0;
}

main.shell:not(.shell-narrow) > .card:not(.attention-card) > .item-list {
  margin-top: 0;
}

main.shell:not(.shell-narrow) .item-actions {
  margin-block: var(--s-4);
}

/* Formerly .status, and renamed because that name is what went wrong. One
   outlined pill served "there is nothing here yet", "here is a neutral fact"
   and "this number may be wrong" on 48 sites across the twelve view packages,
   so a reader could not tell an empty list from a stale price. The states moved
   out to .state-note (see the ten of them at the foot of this file); what is
   left here is what a chip actually is — a short, inert label: a currency a
   form cannot change, a visibility, a lifecycle word. It is not interactive and
   must not look it. */
.chip {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-pill);
  padding: var(--s-3) var(--s-4);
  font-size: 0.85rem;
  font-weight: 700;
}

.shell-narrow {
  max-width: var(--column-max);
}

.stack {
  display: grid;
  width: min(100%, 34rem);
  gap: var(--s-3);
}

label {
  font-weight: 750;
}

/* A read-only value shown beside a form's fields — a currency chip, not an
   input — needs a name, not a <label>: a <label> with no `for` and no
   wrapped control labels nothing for a mouse or a screen reader. */
.field-label {
  margin: 0;
  font-weight: 750;
}

input {
  width: 100%;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  background: var(--v-paper);
  color: inherit;
  font: inherit;
}

select,
textarea {
  width: 100%;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  background: var(--v-paper);
  color: inherit;
  font: inherit;
}

textarea { min-height: 6rem; resize: vertical; }

.item-list { display: grid; gap: var(--s-3); margin: var(--s-6) 0 0; padding: 0; list-style: none; }
.item-list > li > a { display: grid; gap: var(--s-1); border: 1px solid var(--v-hair-strong); border-radius: var(--r-sm); padding: var(--s-4); color: inherit; text-decoration: none; }
.item-list span { color: var(--v-ink-2); }

.item-list-compact { margin-top: var(--s-4); }

/* The decision history is a returning, scan-first surface. Its rows already
   carry the detail, so the page does not need the poster-like breathing room
   used by longer forms and first-run screens. Keep this scoped to history:
   the queue and the other item lists still use the shared rhythm. */
.order-history-page {
  padding-top: var(--s-5);
}

.shell.order-history-page > .lead {
  margin-inline: 0;
  margin-bottom: var(--s-4);
}

.order-history-page > .item-actions {
  margin-block: var(--s-3);
}

.order-history-page > .item-list {
  margin-top: var(--s-4);
}

.order-history-page > .item-list > li > a {
  padding-block: var(--s-3);
}

.order-history-page .item-list .fine-print {
  margin-top: var(--s-3);
}


/* The batch price screen's rows (tracker issue 254). Before this, name,
   price and field label ran together as one line with no break, and a
   price's age showed only once webview.Money's own stale treatment kicked
   in — so a parent could not tell which of nine fields mattered without
   leaving the row. .price-row borrows .item-list > li > a's boxed shape for a
   plain <li> that is not a link; .price-row-head groups what the row is and
   what it is worth today, .price-row-field groups the label and its input
   as a second, visually separate line. */
.price-row { border: 1px solid var(--v-hair-strong); border-radius: var(--r-sm); padding: var(--s-4); display: grid; gap: var(--s-2); }
.price-row-head { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--s-2); row-gap: var(--s-1); }
.price-row-field { display: grid; gap: var(--s-1); }
.price-age { color: var(--v-ink-2); font-size: 0.85em; }

/* The parent dashboard's per-child readiness checklist (readiness issue,
   tracker number 253), nested inside a dashed .state-note (see .state-note.
   state-missing above .item-list). .item-list > li > a already gives order
   history, the catalog list and others a boxed link there — a border is a
   real affordance on its own when the list sits directly on the card — but
   it inherits colour and drops the underline, and a plain bordered sub-box
   read as decoration once the note wrapping it is itself bordered and
   dashed: two links a parent could not tell from a caption. This borrows
   the first-run card's language (.first-run-link's shape,
   firstRunChevronGlyph's glyph) rather than a third way to draw "this is a
   link", under its own classes: sharing .first-run-link itself would leave
   internal/dashboard's own TestParentDashboardReadinessChecklistStaysInsideItsNote
   (tracker issue 171) unable to tell this list apart from the first-run
   card's. */
.readiness-list { display: grid; gap: var(--s-3); margin: var(--s-3) 0 0; padding: 0; list-style: none; }
.readiness-step { border: 1px solid var(--v-hair-strong); border-radius: var(--r-sm); background: var(--v-paper); }
.readiness-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-height: 44px;
  padding: var(--s-4);
  color: inherit;
  text-decoration: none;
  font-weight: 700;
}
.readiness-link:hover,
.readiness-link:focus-visible { background: var(--v-sunk); }
.item-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: baseline; gap: var(--s-2) var(--s-4); border-bottom: 1px solid var(--v-hair); padding: var(--s-3) 0; }
.item-amount { color: var(--v-ink) !important; font-weight: 750; text-align: right; }
.item-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); margin: var(--s-5) 0; }
.action-form { margin: 0; }
.valuation-history { display: grid; gap: var(--s-3); margin: var(--s-4) 0 var(--s-6); }
.valuation-row { display: grid; grid-template-columns: minmax(8rem, auto) minmax(0, 1fr); gap: var(--s-4); border: 1px solid var(--v-hair-strong); border-radius: var(--r-sm); padding: var(--s-4); background: var(--v-paper); }
.valuation-row dt { font-weight: 800; }
.valuation-row dd { display: grid; gap: var(--s-1); margin: 0; color: var(--v-ink-2); }

/* Every primary control — buttons, link-styled buttons, and form inputs —
   keeps at least a 44x44 CSS px touch target, matching the phase 009 mobile
   UX requirement. min-height/min-width are used instead of fixed height so
   controls can still grow with their content or a larger font size. */
button,
.button-link,
input,
select,
textarea {
  min-height: 44px;
}

button,
.button-link {
  min-width: 44px;
}

/* The box stays 24px because a 44px checkbox reads as a button. The touch target is
   the label that wraps it — .availability-toggle below carries min-height: 44px and
   the associated text, so the whole row toggles the box. A checkbox rendered outside
   such a label would be a real 24px target, so keep them inside one. */
input[type="checkbox"],
input[type="radio"] {
  width: 1.5rem;
  height: 1.5rem;
  min-height: 1.5rem; /* design-guards: target-ok — hit area is the wrapping label */
  min-width: 1.5rem; /* design-guards: target-ok — hit area is the wrapping label */
  accent-color: var(--v-ink);
}

button,
.button-link {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--r-pill);
  padding: var(--s-3) var(--s-4);
  background: var(--v-ink);
  color: var(--v-on-ink);
  font: inherit;
  font-weight: 750;
  text-decoration: none;
  cursor: pointer;
}

/* Must stay AFTER the block above, not before it. Both selectors are a single
   class, so the later declaration wins: while this rule sat earlier in the file
   its background and colour were silently overridden and every secondary
   control — every "Anulează" and "Înapoi" in the product — rendered as a filled
   primary button, keeping only the inset ring. A cancel that looks exactly like
   the save button beside it is not an escape hatch.

   The fill is deliberate rather than the transparent-with-a-ring this rule
   originally described. Three things have to stay tellable apart:

     primary action    dark fill,  white text   — do the thing
     secondary action  light fill, dark text    — leave, go back, cancel
     .chip             no fill,    1px border   — not interactive at all

   .chip is already a --r-pill pill with a thin --v-hair-strong border and
   bold text, so an outlined secondary button is indistinguishable from a label
   that cannot be clicked. Filling it keeps "button" and "not a button" apart,
   while the light fill against the dark primary keeps "do it" and "don't"
   apart.

   The fill is --v-quiet, not --v-sunk. In dark the two roles swap luminance —
   the primary becomes the light one — and --v-sunk would put this button below
   the page ground. --v-quiet stays a step away from the ground in whichever
   direction the theme needs, so the three-way distinction above survives both
   themes. */
.button-link-secondary {
  background: var(--v-quiet);
  color: var(--v-ink);
  box-shadow: none;
}

/* Copy controls keep the visible source and its action together. On a narrow
   viewport the action becomes a full-width row, which is easier to hit than a
   small icon floating beside a long URL or code block. */
.copy-field {
  display: grid;
  gap: var(--s-2);
  margin: var(--s-5) 0;
}
.copy-field > .fine-print { margin-top: 0; }
.copy-field > p { margin: 0; }
.copy-field input { width: 100%; }
.copy-action { display: flex; }
.copy-button { gap: var(--s-2); }
.copy-icon { width: 1.15em; height: 1.15em; flex: 0 0 auto; }
.copyable-code { margin: var(--s-4) 0; }
.copyable-code .doc-code-wrap { margin: 0; }
.copyable-code .copy-action { margin-top: var(--s-2); }

/* A pairing code is a value to read and transfer, not a fine-print status.
   The monospace face makes the eight code characters easy to scan, the box
   gives the value a visual boundary, and the fluid size keeps it prominent
   without letting the formatted code overflow a narrow phone. */
.pairing-code-field {
  gap: var(--s-4);
}
.pairing-code {
  width: 100%;
  margin: 0;
  padding: var(--s-4) var(--s-3);
  border: 2px solid var(--v-hair-strong);
  border-radius: var(--r-md);
  background: var(--v-sunk);
  text-align: center;
  overflow: hidden;
}
.pairing-code strong {
  display: block;
  color: var(--v-ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: clamp(2.35rem, 11vw, 4.75rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.1em;
  white-space: nowrap;
}
.pairing-code-field .copy-action,
.pairing-code-field .copy-button {
  width: 100%;
}
.pairing-code-input {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: clamp(1.2rem, 5vw, 1.5rem);
  font-weight: 750;
  letter-spacing: 0.12em;
  text-align: center;
  text-transform: uppercase;
}

/* Primary navigation, rendered by view.Layout on every authenticated page: at
   most four destinations for the parent and three for the child (Slice 1C
   gives each child destination one distinct intent),
   matching the child; a fifth slot is out of scope — see the spec's own
   "documented fifth-slot objection is stronger under a grid, not weaker").
   Each link keeps the same 44x44 touch target as every other primary
   control.

   The two roles share these declarations but keep separate class names on
   purpose: the phase 009 four-destination cap is enforced by counting
   `child-nav-link` occurrences in the rendered body, so a parent nav reusing
   that class would silently defeat the test. */
/* chrome-inner aligns every band of chrome — both navigations, the install
   hint, the settings footer — with the page's own text column, by repeating
   .shell-narrow's max-width and .shell's horizontal padding. The bands
   themselves stay full-bleed so their rules span the window.

   --column-max matches shell-narrow — both read the same token instead of
   repeating 48rem and hoping. Narrow is now the child's screens and the pre-auth
   flow; every parent screen is --page-max, and the :has() rule below widens the
   chrome to match whichever column the page above it uses. A fixed narrow column
   left the dashboard's navigation inset by about 190px from its own content,
   which reads as a rendering fault. A browser without :has() support keeps the
   narrow column, which is merely narrow rather than broken.

   The parent's pages all share --page-max on purpose: the dashboard used to be
   the only wide one, so moving between it and the portfolio resized the whole
   frame by 384px mid-navigation. Prose still stops at --measure and .stack at
   34rem, so widening the shell gave the room to tables and cards without
   stretching a line of text or a form field. */
/* WCAG 2.4.1 (Bypass Blocks). Off-screen rather than opacity:0 or
   height:0 — either of those would also remove it from the tab order in some
   browsers, defeating the one thing this exists to do. */
.skip-link {
  position: absolute;
  top: 0;
  left: -9999px;
  z-index: 10;
  padding: var(--s-2) var(--s-3);
  background: var(--v-paper);
  color: var(--v-ink);
  font-weight: 700;
  text-decoration: none;
}

.skip-link:focus-visible {
  left: 0;
  outline: 3px solid var(--v-focus);
  outline-offset: 3px;
}

/* The one brand anchor shared by anonymous, parent and child screens. The
   image is decorative because the adjacent wordmark names the destination;
   the link's aria-label explains the role-aware home route to assistive tech. */
.brand-bar {
  padding: var(--s-3) 0 var(--s-2);
}

.brand-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-1) var(--s-2) var(--s-1) 0;
  border-radius: var(--r-sm);
  color: var(--v-ink);
  text-decoration: none;
}

.brand-link:hover,
.brand-link:focus-visible {
  background: var(--v-quiet);
}

.brand-mark {
  display: block;
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  object-fit: contain;
}

.brand-wordmark {
  font-size: var(--fs-lead);
  font-weight: 800;
  letter-spacing: -0.02em;
}

.chrome-inner {
  max-width: var(--column-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

body:has(main.shell:not(.shell-narrow)) .chrome-inner {
  max-width: var(--page-max);
}

.chrome-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

/* Each link carries its own horizontal padding, so the row starts a link's
   worth of padding to the left. Without this the navigation text sat about
   12px right of the heading beneath it — close enough to look accidental.
   Kept as its own declaration (rather than folded into the rule below) so it
   still applies if a future change ever gives this row a different column
   layout.
   .chrome-row itself stays flex/wrap (:below): the parent footer and the
   public footer both reuse the bare class for a link list that MAY wrap
   safely, so the three-column link grid is scoped to .parent-nav-inner .chrome-row
   specifically rather than changed globally.

   Spec 019, requirement 5: at 360px four flex destinations with `flex-wrap:
   wrap` put "Cereri" alone on a second row. The brand now owns the home slot,
   so the three remaining links use a fixed three-column grid beside it. */
.parent-nav-inner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: stretch;
  gap: var(--s-4);
}

.parent-nav-inner .chrome-row {
  margin-inline-start: 0;
  display: grid;
  align-items: stretch;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.parent-footer .chrome-row {
  margin-inline-start: calc(var(--s-2) * -1);
}

/* The parent keeps the band at the top. The child's bar moved to the bottom of
   the viewport in phase 3 — see the tab-bar rules at the foot of this file — so
   it is no longer part of this rule. */
.parent-nav {
  margin: 0 0 var(--s-5);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--v-hair-strong);
}

.child-nav-link,
.parent-nav-link {
  /* This used to be what kept a lone link on a wrapped last row from
     stretching across it and centring its own label — the real bug, but the
     wrong fix: it could only discourage that outcome, never prevent the wrap
     that caused it. The parent nav no longer wraps at all (see
     .parent-nav-inner .chrome-row, above), so this rule is no longer what holds
     that layout together; grid-template-columns is. It stays declared
     because .child-nav (below) is already a grid too and never depended on
     it either way — removing a no-op risks nothing, and it is cheaper than
     re-deriving, from scratch, why a future edit should not delete it. The
     44x44 target is already guaranteed by the min-height/min-width and
     padding below regardless of which layout mode is in effect. */
  flex: 0 1 auto;
  display: flex;
  min-height: 44px;
  min-width: 44px;
  align-items: center;
  justify-content: center;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--v-ink);
  font-weight: 700;
  text-decoration: none;
  text-align: center;
}

/* The mark itself aligns to the text column; the other parent links keep
   their optical padding inside the three-column section grid. */
.parent-nav-inner .brand-link {
  padding-inline-start: 0;
  padding-left: 0;
}

.child-nav-link:hover,
.child-nav-link:focus-visible,
.parent-nav-link:hover,
.parent-nav-link:focus-visible {
  background: var(--v-quiet);
}

/* Account-level destinations, kept out of the primary nav because they are
   visited rarely. The 44px minimum still applies: these are ordinary links,
   not decoration, and the accessibility scan enforces target size. */
.parent-footer {
  margin: var(--s-6) 0 0;
  padding: var(--s-4) 0 var(--s-6);
  border-top: 1px solid var(--v-hair-strong);
}

.parent-footer nav {
  gap: var(--s-2) var(--s-5);
}

/* min-width matters as much as min-height here: "Cont" is short enough to
   render about 36px wide on its own, which fails WCAG 2.5.8 even though the
   row is tall enough. The sign-out control (spec 019, requirement 4) is a
   <button> inside its own <form>, sitting beside "Cont" and
   "Confidențialitate" as a third, equally weighted destination — not the
   filled pill button, .button-link, reads elsewhere: a pill here would make
   leaving read as more consequential than every other footer link, when it
   is exactly as easy to reach as either of them. */
.parent-footer a,
.parent-footer button {
  display: flex;
  min-height: 44px;
  min-width: 44px;
  align-items: center;
  justify-content: center;
  padding: 0 var(--s-2);
  color: var(--v-ink);
  font-weight: 600;
}

.parent-footer button {
  border: 0;
  border-radius: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

/* The anonymous counterpart of .parent-footer (spec 019, requirement 3):
   the five internal/publicinfo pages, reachable from the landing page, the
   sign-in page, and every one of those five pages itself. The two footers
   are mutually exclusive — see view.publicFooterVisible — so sharing the
   band styling below risks no confusion about which reader is looking at
   which. */
.public-footer {
  margin: var(--s-6) 0 0;
  padding: var(--s-4) 0 var(--s-6);
  border-top: 1px solid var(--v-hair-strong);
}

.public-footer nav {
  gap: var(--s-2) var(--s-5);
}

.public-footer a {
  display: flex;
  min-height: 44px;
  min-width: 44px;
  align-items: center;
  justify-content: center;
  padding: 0 var(--s-2);
  color: var(--v-ink);
  font-weight: 600;
}

/* Which build is answering — the last line of both footers, from view.versionNote.
   Deliberately the quietest text on the page: it is for the one reader
   troubleshooting a deploy, and it must cost nothing to the many who are not.

   --v-ink-3 is the ramp declared for notes at this size (4.7:1 minimum), which is
   what keeps e2e/accessibility.spec.ts's colour-contrast scan green in both themes;
   a fainter grey would fail it. The inline-start padding matches the padding each
   footer link carries above, so the revision lines up with the link text rather
   than with the gutter. Tabular numerals so the digits sit still between deploys. */
.chrome-version {
  margin: var(--s-2) 0 0;
  padding-inline-start: var(--s-2);
  color: var(--v-ink-3);
  font-size: var(--fs-note);
  font-variant-numeric: tabular-nums;
}

/* Contextual, dismissible install guidance. Rendered only for an
   authenticated parent, only after a first authenticated page view, and
   never on a /child/* page — see web.withInstallGuidancePrompt. Narrowed to
   /account alone in spec 019, requirement 6, which also added the "Amână"
   snooze beside the existing dismissal — see .install-hint-actions below. */
.install-hint {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin: 0 0 var(--s-5);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  background: var(--v-quiet);
}

.install-hint p {
  margin: 0;
  font-size: 0.95rem;
  color: var(--v-ink);
}

/* Holds "Amână" and "Nu, mulțumesc" side by side, wrapping onto their own
   row under the message rather than onto two rows of their own — .install-hint
   above already wraps the whole aside at a narrow width, so this only needs
   to keep the pair together at every width wide enough to fit them. */
.install-hint-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.install-hint-dismiss {
  display: inline-flex;
  min-height: 44px;
  min-width: 44px;
  align-items: center;
  justify-content: center;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-pill);
  color: var(--v-ink);
  font-weight: 700;
  text-decoration: none;
  box-shadow: inset 0 0 0 1px var(--v-hair-strong);
}

button:focus-visible,
.button-link:focus-visible,
input:focus-visible {
  outline: 3px solid var(--v-focus);
  outline-offset: 3px;
}

select:focus-visible,
textarea:focus-visible,
a:focus-visible { outline: 3px solid var(--v-focus); outline-offset: 3px; }

@media (max-width: 32rem) {
  .item-row, .valuation-row { grid-template-columns: 1fr; }
  .item-row { gap: var(--s-1); }
  .item-amount { text-align: left; }
  .item-actions > *, .item-actions .button-link, .item-actions button { width: 100%; text-align: center; }
  .copy-action .copy-button { width: 100%; }
}

/* A form marked pending by /static/app.js?v=d156988b while its submit is in flight. The
   button keeps its 44x44 target from the block above — only its emphasis
   changes — so the WCAG 2.5.8 target-size criterion the accessibility scan
   enforces on /sign-in is unaffected. Contrast stays legible rather than the
   usual washed-out disabled grey: this button is doing something, not
   unavailable. */
button:disabled {
  opacity: 0.75;
  cursor: progress;
}

.form-error {
  margin: 0;
  color: var(--v-err);
  font-weight: 700;
}

.fine-print {
  max-width: var(--measure);
  margin-top: var(--s-6);
  color: var(--v-ink-2);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* Parent pages keep the wide shell so their navigation frame does not resize
   between the dashboard and a form. Narrow direct content still belongs in
   the middle of that frame, though: without this, a 34rem form or a readable
   prose/state column hugs the shell's left content edge and leaves the right
   half of a desktop page visibly empty. Wide cards and grids are deliberately
   not listed, so the dashboard and portfolio keep their full content width. */
.shell > .stack,
.shell > .lead,
.shell > .fine-print,
.shell > .state-note {
  margin-inline: auto;
}

/* ══ PHASE 2 — THE SEVEN PARTIALS ═════════════════════════════════════════
   Every rule below is the treatment of one partial in internal/web/view. The
   partial is the only writer of these classes: no page styles an amount, a
   state or a chart by hand, which is the whole point of the phase. If a screen
   needs a treatment that is not here, it belongs here first.
   ───────────────────────────────────────────────────────────────────────── */

/* ── money ─────────────────────────────────────────────────────────────────
   Digits, currency, the EDU mark, and the two treatments that keep a number
   honest. .num above already carries tabular figures; this adds the weight and
   the material voice. */
.money { display: inline-flex; align-items: baseline; gap: var(--s-1); }
.money-figure { font-weight: 800; }
.money-currency { font-size: 0.86em; font-weight: 700; }
.money-real .money-figure, .money-shared .money-figure { color: var(--v-ink); }
.money-real .money-currency, .money-shared .money-currency { color: var(--v-ink-2); }
.money-edu .money-figure, .money-edu .money-currency, .money-edu .edu-mark { color: var(--v-edu-ink); }

/* Stale. The value is still shown — withholding it would be worse than
   qualifying it — with a dotted amber underline, a clock, and the effective-at
   moment always visible. Amber is attention, never error: this number is old,
   not wrong. */
.money.state-stale { border-radius: var(--r-xs); background: var(--v-attn-tint); padding: 0 var(--s-1); }
.money.state-stale .money-figure {
  text-decoration: underline dotted var(--v-attn);
  text-underline-offset: 3px;
}
.money-clock { flex: 0 0 auto; width: 0.95em; height: 0.95em; color: var(--v-attn); }
.money-asof { color: var(--v-attn); font-size: 0.78em; font-weight: 700; font-variant-numeric: tabular-nums; }

/* Missing. The number's slot is replaced, not filled with a zero and not
   filled with a guess. The hatch reads as "unknown" in greyscale and at any
   colour vision; the chip says so in words. */
.money-missing { gap: var(--s-2); }
.money-hatch {
  display: inline-block;
  width: 3.5rem;
  height: 1em;
  border: 1px dashed var(--v-hair-strong);
  border-radius: var(--r-xs);
}
.chip-attn { border-color: var(--v-attn); background: var(--v-attn-tint); color: var(--v-attn); }
.chip-attn, .money .chip { padding: var(--s-1) var(--s-2); font-size: var(--fs-note); }

/* ── stateNote — the ten states ────────────────────────────────────────────
   All ten are declared here, and StateNote can emit any of them. Four have no
   site in the product yet (success, unauthorised, conflict, and in-flight for a
   server render) because the surfaces that need them are phases 4 and 6: the
   vocabulary is complete, the sites are not. That is deliberate — a state
   invented at the moment it is first needed is how a fifth pill appears. */
.state-note {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  max-width: var(--measure);
  margin: var(--s-3) 0;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  font-size: var(--fs-body);
  line-height: 1.5;
  font-weight: 600;
}
/* Beats `.item-list span`, so a state note keeps its own voice inside a list. */
.state-note .state-text { color: inherit; }
.state-glyph { flex: 0 0 auto; width: 1.15em; height: 1.15em; }

.state-note.state-empty { border-style: dashed; background: var(--v-sunk); color: var(--v-ink-2); }
.state-note.state-stale { border-color: var(--v-attn); background: var(--v-attn-tint); color: var(--v-attn); }
.state-note.state-missing { border-style: dashed; border-color: var(--v-hair-strong); color: var(--v-ink-2); }
.state-note.state-mixed-currency { border-color: var(--v-ink); background: var(--v-sunk); color: var(--v-ink); }
.state-note.state-success { border-color: var(--v-ok); background: var(--v-ok-tint); color: var(--v-ok); }
.state-note.state-invalid { border-color: var(--v-err); background: var(--v-err-tint); color: var(--v-err); }
/* Unauthorised is identical to not-found by design, so it is styled as an
   intentional outcome rather than as a fault: no error colour anywhere. */
.state-note.state-unauthorised { background: var(--v-paper); color: var(--v-ink-2); }
.state-note.state-conflict { border-color: var(--v-ink); background: var(--v-sunk); color: var(--v-ink); }
.state-note.state-unavailable { border-style: dashed; border-color: var(--v-hair); color: var(--v-ink-3); }

/* Loading / in-flight: the button keeps its label and its 44x44 target and
   gains a 3px indeterminate rail beneath. No spinner. /static/app.js?v=d156988b sets
   aria-busy on a submitting form, so the rail arrives without the template
   knowing; .state-in-flight is the same treatment for a server render that
   already knows a request is outstanding. The prefers-reduced-motion blanket in
   the token sheet covers the animation. */
.state-in-flight,
form[aria-busy="true"] button[type="submit"] { position: relative; overflow: hidden; }
.state-in-flight::after,
form[aria-busy="true"] button[type="submit"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--v-focus);
  animation: v-rail var(--t-slow) linear infinite;
}
@keyframes v-rail { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

/* ── materialCard ──────────────────────────────────────────────────────────
   .m-real / .m-shared / .m-edu in the token sheet carry fill, edge and radius.
   This carries the fourth property — the mark — and the card's own box. */
.card { display: grid; gap: var(--s-3); margin: var(--s-5) 0; padding: var(--s-5); }
.material-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  margin: 0;
  color: var(--v-ink-3);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.m-edu > .material-mark { color: var(--v-edu-ink); }
.m-shared > .material-mark { color: var(--v-edu-ink); }
.material-glyph { flex: 0 0 auto; width: 1.1em; height: 1.1em; }

/* ── portfolio summary ────────────────────────────────────────────────────
   The parent portfolio is a balance sheet, not a vertical stack of sentences.
   Keep the single large answer (net worth) at the top, then let the numbers
   that explain it sit beside one another. The composition and disclosure
   blocks use the existing chart/state partials; these rules only give them a
   tighter page-specific rhythm. */
.portfolio-shell > .card {
  gap: var(--s-5);
  margin-top: var(--s-3);
}

.portfolio-summary-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-5);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--v-hair);
}

.summary-kicker {
  margin: 0 0 var(--s-1);
  color: var(--v-ink-3);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.12em;
}

.portfolio-net-worth { margin: 0; }
.portfolio-net-worth .money-figure { font-size: var(--fs-figure); line-height: 1.05; }
.portfolio-net-worth .money-currency { font-size: 0.95em; }

.portfolio-summary-context {
  display: grid;
  gap: var(--s-1);
  color: var(--v-ink-2);
  font-size: var(--fs-small);
  text-align: right;
}
.portfolio-summary-context span:first-child { color: var(--v-ink); font-weight: 750; }

.portfolio-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3);
}

.portfolio-metric {
  display: grid;
  gap: var(--s-1);
  min-width: 0;
  padding-right: var(--s-3);
  border-right: 1px solid var(--v-hair);
}
.portfolio-metric:last-child { padding-right: 0; border-right: 0; }
.portfolio-metric > span { color: var(--v-ink-2); font-size: var(--fs-small); }
.portfolio-metric > strong { min-width: 0; font-size: var(--fs-h3); }
.portfolio-metric .money { flex-wrap: wrap; }

.portfolio-section { padding-top: var(--s-2); }
.portfolio-section-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
}
.portfolio-section-heading h2,
.portfolio-disclosure h2 { margin: 0; font-size: var(--fs-h3); line-height: 1.2; }
.portfolio-section-heading > span { color: var(--v-ink-2); font-size: var(--fs-note); }
.portfolio-section .chart { margin: var(--s-3) 0 0; }

.portfolio-disclosures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5);
  padding-top: var(--s-2);
  border-top: 1px solid var(--v-hair);
}
.portfolio-disclosure .item-list { margin-top: var(--s-3); }
.portfolio-disclosure > .item-list > li {
  display: grid;
  gap: var(--s-1);
  color: var(--v-ink-2);
  font-size: var(--fs-small);
}
.portfolio-disclosure > .item-list > li > strong { color: var(--v-ink); }

.portfolio-shell > .item-actions { margin-top: var(--s-2); }

@media (max-width: 32rem) {
  .portfolio-summary-top { align-items: flex-start; flex-direction: column; gap: var(--s-3); }
  .portfolio-summary-context { text-align: left; }
  .portfolio-metrics { grid-template-columns: 1fr; gap: 0; }
  .portfolio-metric,
  .portfolio-metric:last-child {
    padding: var(--s-3) 0;
    border-right: 0;
    border-bottom: 1px solid var(--v-hair);
  }
  .portfolio-metric:last-child { border-bottom: 0; }
  .portfolio-disclosures { grid-template-columns: 1fr; gap: var(--s-4); }
  .portfolio-section-heading { align-items: flex-start; flex-direction: column; gap: var(--s-1); }
}

/* ── compositionBar ────────────────────────────────────────────────────────
   A flex row and a table. The bar derives from the same numbers the <details>
   table prints, and the table is the source. */
.chart { margin: var(--s-4) 0; }
.composition-bar {
  display: flex;
  align-items: stretch;
  height: var(--s-4);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-xs);
  background: var(--v-sunk);
  overflow: hidden;
}
/* The axis refuses to close when something in it cannot be computed: a bar
   that closes claims the whole is accounted for. */
.composition-open {
  border-right: 2px dashed var(--v-attn);
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.composition-seg { flex: 0 0 auto; min-width: var(--s-1); background: var(--seg-color, var(--v-ink)); } /* design-guards: target-ok — a bar in a chart, not a control */
.composition-seg.state-stale { outline: 2px dotted var(--v-attn); outline-offset: -3px; }
.composition-seg.state-missing { flex: 0 0 var(--s-6); background: transparent; }

.composition-legend { display: grid; gap: var(--s-2); margin: var(--s-3) 0 0; padding: 0; list-style: none; }
.composition-legend-item { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); font-size: var(--fs-small); }
.composition-legend-item .composition-seg {
  flex: 0 0 auto;
  width: var(--s-3);
  height: var(--s-3);
  border-radius: var(--r-xs);
}
.composition-legend-label { font-weight: 700; }
.composition-legend-share { color: var(--v-ink-2); }
.composition-legend-note { color: var(--v-attn); font-weight: 700; }

.chart-figures { margin: var(--s-3) 0 0; font-size: var(--fs-small); }
.chart-figures summary { display: flex; align-items: center; min-height: 44px; font-weight: 700; cursor: pointer; }
.chart-table { width: 100%; border-collapse: collapse; }
.chart-table th, .chart-table td {
  border-bottom: 1px solid var(--v-hair);
  padding: var(--s-2) 0;
  text-align: left;
}
.chart-table .chart-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── API documentation (phase 015 P3) ────────────────────────────────────
   /account/api-keys's endpoint reference and Apps Script tutorial. A JSON
   example or a whole script cannot be reflowed to fit 360px without
   destroying it, and an error-code table has three columns of real prose —
   so both scroll sideways inside their own bounded container instead of
   ever widening the page itself. */
.doc-code-wrap,
.doc-table-wrap {
  overflow-x: auto;
  margin: var(--s-4) 0;
}
.doc-code {
  margin: 0;
  padding: var(--s-4);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  background: var(--v-sunk);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre;
}
.doc-table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
}
.doc-table th,
.doc-table td {
  border-bottom: 1px solid var(--v-hair);
  padding: var(--s-2) var(--s-3);
  text-align: left;
  vertical-align: top;
}
.doc-table code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 0.85em;
}

/* ── avatar ────────────────────────────────────────────────────────────────
   The circular tinted holder is drawn inside the SVG, so the animal and its
   holder cannot be separated by a layout change. Sizes come from the caller. */
.avatar { flex: 0 0 auto; display: block; }

/* ── attentionRow ─────────────────────────────────────────────────────────*/
.attention-queue { display: grid; gap: 0; margin: var(--s-4) 0 0; padding: 0; list-style: none; }
.attention-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  border-top: 1px solid var(--v-hair);
  padding: var(--s-3) 0;
}
.attention-body { display: grid; gap: var(--s-1); }
.attention-text { margin: 0; }
.attention-meta { margin: 0; color: var(--v-ink-3); font-size: var(--fs-note); }
.attention-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s-6);
  height: var(--s-6);
  border-radius: var(--r-xs);
  background: var(--v-attn-tint);
  color: var(--v-attn);
}
.attention-tile svg { width: 60%; height: 60%; }

/* ── signalCard ───────────────────────────────────────────────────────────*/
/* The dedicated habit-signals screen's own header row: the child's avatar
   beside "Ce s-a întâmplat până acum · <nickname>" (README 1h). */
.signals-page-head { display: flex; align-items: center; gap: var(--s-2); }
.signals-page-head h1 { margin: 0; }
.signal-list { display: grid; gap: var(--s-3); margin: var(--s-4) 0 0; padding: 0; list-style: none; }
.signal-card {
  display: grid;
  gap: var(--s-2);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  padding: var(--s-4);
  background: var(--v-paper);
}
.signal-card.signal-insufficient { border-style: dashed; background: var(--v-bg); }
.signal-label { margin: 0; font-weight: 800; }
.signal-visual { height: var(--s-3); border-radius: var(--r-xs); background: var(--v-edu); }
.signal-insufficient .signal-visual { border: 1px dashed var(--v-hair-strong); background: transparent; }
/* cash_share and largest_position_concentration: a sunk track with a fill
   sized to the same percent Value already states in words — decorative, not
   a chart, so it carries no role="img" of its own (see signal.templ). */
.signal-visual-fill { position: relative; overflow: hidden; background: var(--v-sunk); }
.signal-visual-fill-bar { position: absolute; inset: 0 auto 0 0; height: 100%; background: var(--v-edu); border-radius: inherit; }
/* category_diversification: one filled square per category counted in Value,
   capped at 8 (signal.templ's signalVisualCells) so it cannot overflow the
   card at 360px. */
.signal-visual-count { display: flex; gap: var(--s-1); background: none; }
.signal-visual-count-cell { flex: 1; min-width: var(--s-3); border-radius: var(--r-xs); background: var(--v-edu); } /* design-guards: target-ok — a cell in a count visual, not a control */
.signal-value { margin: 0; font-size: var(--fs-h3); font-weight: 800; }
.signal-arith { margin: 0; color: var(--v-ink-2); font-size: var(--fs-small); }
.signal-facts { display: grid; gap: var(--s-1); margin: 0; padding: 0; list-style: none; color: var(--v-ink-3); font-size: var(--fs-note); }

/* 360px: the action drops below the text instead of shrinking below 44x44, and
   the attention row's three columns become two. Never shorten the Romanian. */
@media (max-width: 32rem) {
  .attention-row { grid-template-columns: auto minmax(0, 1fr); }
  .attention-row .button-link { grid-column: 1 / -1; width: 100%; }
  .card { padding: var(--s-4); }
}

/* A money block wraps well before the 32rem breakpoint above: on a record
   grid the VALOARE track only needs to be as wide as the widest amount, and
   without this a wide figure — a long stale as-of date beside it — inflates
   that whole column at tablet widths where nothing else on the page is yet
   in its narrow-viewport layout. */
@media (max-width: 48rem) {
  .money { flex-wrap: wrap; }
}

/* The prefers-reduced-motion blanket that used to close this file is gone: the
   token sheet at the top carries the identical block, and two copies of a
   standing guarantee is one copy that can quietly fall out of step with the
   other. The guarantee itself is unchanged — see the sheet's own note on it. */

/* ══ PHASE 3 — THE PARENT'S IA AND THE TWO NAVIGATIONS ════════════════════
   Three things the chrome could not say before: which page you are on, that
   something is waiting, and how a child leaves. Plus the parent dashboard's
   own register, which is not the landing page's.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* --fs-h1-parent lived here: the parent register's page title, sized to keep
     a balance sheet's attention queue above the fold. It is gone because the h1
     rule at the top of this file now IS that size for every page, which is what
     this phase wanted in the first place — it just scoped the fix to a class
     the templates had to remember, and thirty-eight mains did not. Nothing
     reads the token any more, and .page-parent is off the templates with it. */
  /* The one figure on the screen that is allowed to be large. */
  --fs-figure: clamp(2rem, 1.5rem + 2.2vw, 2.5rem);
  /* The child's tab bar, and the room the page leaves for it. A fixed bar that
     covers the last line of a page is worse than no bar. */
  --tabbar-h: 58px;
}

/* .page-parent h1 stood here and set exactly what the base h1 rule now sets,
   so the class had no declarations left and came off the templates. A page is a
   page: it needs no marker to be given the page title. */

/* This rule used to carry justify-content: flex-start, because .shell centred
   its content and a dashboard must not: the attention queue belongs under the
   heading at the top of the screen, not floating in the middle of it. Flowing
   from the top is now .shell's own default and .shell-centred is the exception,
   so a parent page needs nothing here — the reasoning moved to .shell. */

/* ── The active destination ────────────────────────────────────────────────
   A 2.5px inset rail AND a fill. Never colour alone: aria-current="page" is on
   the link as well, so the indicator survives both a greyscale screenshot and a
   screen reader. */
.parent-nav-link.is-active {
  background: var(--v-sunk);
  box-shadow: inset 0 2.5px 0 0 var(--v-ink);
  color: var(--v-ink);
  font-weight: 800;
}

/* The one badge in the product. It is not a touch target — it is a count inside
   a link that already exceeds 44x44 — so it carries no min-width of its own;
   its width comes from the digits and its padding, which keeps a two- or
   three-digit count from cramping. White on --v-attn in both themes. */
.parent-nav-link { position: relative; }
.nav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 18px;
  margin-inline-start: var(--s-1);
  padding-inline: var(--s-1);
  border-radius: var(--r-pill);
  background: var(--v-attn);
  color: var(--v-paper);
  font-size: var(--fs-note);
  font-weight: 800;
  line-height: 1;
}

/* ── The child's header ────────────────────────────────────────────────────
   Rendered by Layout on every child page. The exit is here rather than in a
   menu or in the tab bar: it must be one visible tap from anywhere, and it must
   not consume one of the three destinations. */
.child-chrome {
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--v-edu);
  background: var(--v-edu-tint);
}

.child-chrome-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.child-chrome-name {
  margin: 0;
  color: var(--v-edu-ink);
  font-size: var(--fs-small);
  font-weight: 700;
}

.child-exit-form { margin: 0; margin-inline-start: auto; }

/* The design draws this pill 34px tall. It ships at 44px: the target-size floor
   is not a value this product trades against a proportion, and the accessibility
   scan measures every button on every child page. */
.child-exit {
  gap: var(--s-1);
  min-height: 44px;
  padding: var(--s-2) var(--s-3);
  border: 1.5px solid var(--v-edu);
  background: var(--v-paper);
  color: var(--v-edu-ink);
  font-size: var(--fs-small);
}

.chrome-glyph { flex: 0 0 auto; width: 1.15em; height: 1.15em; }

/* ── The child's bottom tab bar ────────────────────────────────────────────
   Three equal columns, 58px tall, icon above label, and „Cererile mele" wrapping
   to two lines rather than being cut down to fit a phone. Fixed to the bottom of
   the viewport, with the page reserving exactly that much room below its content
   so the bar never covers the last thing on the page. */
.child-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--v-hair-strong);
  background: var(--v-paper);
}

/* Scrolling something into view must not park it behind the bar. The browser
   aligns a focused control, an anchor target, or a scripted scrollIntoView to the
   viewport edge, and the bar owns the last 58px of that edge — so a submit button
   below the fold ended up underneath it, and a real thumb would have hit the bar.
   scroll-padding-bottom is the property that tells the browser where the usable
   viewport actually ends; it goes on the scrolling element, not on main. */
html:has(.child-nav) {
  scroll-padding-bottom: calc(var(--tabbar-h) + var(--s-3));
}

body:has(.child-nav) main {
  /* Two parts of one guarantee: nothing a child needs may sit under the bar.
     The padding reserves the bar's height at the end of the page, and the reduced
     min-height stops a SHORT page from centring its content into the strip the bar
     occupies.

     There was a third part — justify-content: flex-start — and it took a failing
     test to find: back when .shell centred its content, a CENTRED flex column
     taller than its box overflowed past its own padding, so on the allocation
     form, the tallest child page in the product, the submit button ended up
     underneath the bar and the bar swallowed the click. Playwright reported it as
     "child-nav-link intercepts pointer events", which is exactly what a child's
     thumb would have done. Flowing from the top is now .shell's default, and no
     child screen opts into .shell-centred, so the override is redundant rather
     than removed — content that flows from the top cannot overflow the padding
     that follows it. */
  min-height: calc(100svh - var(--tabbar-h));
  padding-bottom: calc(var(--tabbar-h) + var(--s-6));
}

.child-nav-link {
  flex-direction: column;
  gap: var(--s-1);
  min-height: var(--tabbar-h);
  padding: var(--s-2) var(--s-1);
  border-top: 2.5px solid transparent;
  border-radius: 0;
  color: var(--v-ink-2);
  font-size: var(--fs-note);
  line-height: 1.1;
}

.child-nav-link .nav-label { text-align: center; }

.child-nav-link.is-active {
  border-top-color: var(--v-edu);
  background: var(--v-edu-tint);
  color: var(--v-edu-ink);
  font-weight: 800;
}

/* ── The attention queue's own card ───────────────────────────────────────
   Bordered in --v-attn, with a tinted header that states the count. Amber is
   attention, never error: nothing in this card has gone wrong, it is simply
   waiting for a person. */
.attention-card {
  border: 1px solid var(--v-attn);
  border-radius: var(--r-sm);
  padding: 0;
  background: var(--v-paper);
}

main.shell:not(.shell-narrow) > .attention-card {
  margin-block: var(--parent-section-gap);
}

.attention-head {
  margin: 0;
  border-bottom: 1px solid var(--v-attn);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  padding: var(--s-3) var(--s-4);
  background: var(--v-attn-tint);
  color: var(--v-attn);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.12em;
}

.attention-card > .attention-queue,
.attention-card > .state-note,
.attention-card > .item-actions {
  margin: var(--s-4);
}

.attention-card .attention-row:first-child { border-top: 0; }

/* ── The balance-sheet figure ─────────────────────────────────────────────*/
.figure-xl { margin: 0; }
.figure-xl .money-figure { font-size: var(--fs-figure); line-height: 1.05; }
.figure-note { margin: 0; color: var(--v-ink-2); font-size: var(--fs-small); }
.section-caveat { margin: 0; color: var(--v-edu-ink); font-size: var(--fs-small); font-weight: 700; }

/* Phase 13: the children column becomes an actual grid on a wide viewport,
   instead of the single stacked column every viewport got before. At a narrow
   viewport auto-fit only has room for one track, which is the same 360px
   column the page already had — min(26rem, 100%) is what keeps that track
   from forcing a 416px minimum and overflowing a 360px screen sideways;
   plain minmax(26rem, 1fr) does not shrink below its minimum. */
.children-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr));
  gap: var(--s-4);
}

/* .child-card is a plain, unpadded wrapper around one child's .m-edu card,
   and it is the container query's own box (phase 13, "Adopted: container
   queries"). It carries no padding of its own so the inline size it reports
   to @container rules is the grid track's width, not the padded card's own
   narrower content box — the child card has to serve two very different
   homes, a single 360px column and a cell inside a wider desktop grid, and
   only its own inline size, not the viewport's, can tell it which one it is
   in right now. */
.child-card { container-type: inline-size; container-name: child-card; }

/* ── One child's strip ────────────────────────────────────────────────────
   Avatar, nickname, the three numbers, and the pending chip. The child's
   identity travels onto the parent's screen as a face rather than as an ID. */
.child-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
}

.child-strip-body { flex: 1 1 12rem; display: grid; gap: var(--s-1); }
.child-strip-body h3 { margin: 0; font-size: var(--fs-h3); }
.child-strip-meta { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1); margin: 0; color: var(--v-edu-ink); font-size: var(--fs-small); }
.child-strip-detail { margin: 0; }
.child-strip-detail summary { display: flex; align-items: center; min-height: 44px; font-weight: 700; cursor: pointer; }
.child-strip-detail h4 { margin: var(--s-4) 0 0; font-size: var(--fs-body); }

/* The strip's chip drops under the text rather than squeezing the nickname
   whenever the child card's OWN box is narrow — a 360px column, or a cell
   squeezed into a desktop grid — rather than whenever the viewport itself is
   narrow. A viewport media query cannot see a grid cell's own width; this is
   exactly the case phase 13 introduces container queries for. Nothing here
   shortens a label. */
@container child-card (max-width: 24rem) {
  .child-strip .chip { flex: 1 1 100%; }
}

/* The queue's card keeps its full-width action at a narrow viewport; this one
   stays a plain viewport media query because .attention-card is not a
   per-child card and does not need its own container-query box. */
@media (max-width: 32rem) {
  .attention-card > .attention-queue,
  .attention-card > .state-note,
  .attention-card > .item-actions {
    margin: var(--s-3);
  }
}

/* ══ PHASE 4 — THE CHILD'S DECISION ═══════════════════════════════════════
   Two steps: a direction and an amount, then a review. The units are derived
   and shown, never typed, so nothing on these screens asks a child for a number
   the product intends to ignore.
   ───────────────────────────────────────────────────────────────────────── */

/* ── The direction, as two cards ───────────────────────────────────────────
   Links, not radios: choosing one re-renders the step, which is what makes the
   whole screen work with no JavaScript. The chosen one carries a rail and a fill
   as well as aria-current — the same rule the navigation follows, for the same
   reason. */
.direction-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--s-3);
  margin: var(--s-4) 0;
}

.direction-card {
  display: flex;
  min-height: 88px;
  align-items: center;
  gap: var(--s-3);
  border: 1.5px solid var(--v-edu);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  background: var(--v-paper);
  color: var(--v-edu-ink);
  font-size: var(--fs-lead);
  font-weight: 750;
  text-decoration: none;
}

.direction-card.is-active {
  border-top-width: 5px;
  background: var(--v-edu-tint);
  box-shadow: inset 0 0 0 1px var(--v-edu);
}

/* The mark is a shape, not a tick in a colour: it fills only on the chosen card,
   so the choice survives greyscale. */
.direction-mark {
  flex: 0 0 auto;
  width: var(--s-5);
  height: var(--s-5);
  border: 2px solid var(--v-edu);
  border-radius: var(--r-pill);
}

.direction-card.is-active .direction-mark {
  background: var(--v-edu);
  box-shadow: inset 0 0 0 3px var(--v-edu-tint);
}

/* The weekly choice belongs on Acasă: allocating and keeping are two equal
   answers, so they share one labelled region and one visual treatment. */
.weekly-decision-options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: var(--s-3);
  margin-top: var(--s-4);
}

.weekly-decision {
  margin-top: var(--s-2);
}

.weekly-decision h2 { margin: 0; }
/* line-height is explicit, not "normal": "normal" resolves per the font
   actually painted, and the option card's height must not depend on whether
   Figtree or a fallback face rendered this heading. */
.weekly-decision-option h3 { margin: 0; font-size: var(--fs-body); font-weight: 700; line-height: 1.2; }

.weekly-decision-option {
  display: grid;
  align-content: start;
  gap: var(--s-2);
  min-width: 0;
  padding: var(--s-3) var(--s-4);
  border: 1.5px solid var(--v-edu);
  border-radius: var(--r-lg);
  background: var(--v-paper);
}

.weekly-decision-control {
  align-self: stretch;
  width: 100%;
  min-height: 44px;
  text-align: center;
}

.weekly-decision-form {
  display: grid;
  gap: var(--s-2);
  margin: 0;
}

.weekly-decision-form input {
  width: 100%;
}

.weekly-decision-control:focus-visible,
.weekly-decision-form input:focus-visible {
  outline: 3px solid var(--v-focus);
  outline-offset: 3px;
}

@media (max-width: 32rem) {
  /* The parent brand shares the same single row as the three section links.
     Keep only the mark at phone width so the nav retains its four-destination
     layout without adding the vertical band that this header replaced. */
  body:has(.parent-nav) .parent-nav-inner {
    gap: var(--s-1);
  }

  body:has(.parent-nav) .parent-nav-inner .brand-link {
    min-width: 44px;
    padding-inline-end: 0;
  }

  body:has(.parent-nav) .parent-nav-inner .brand-mark {
    flex-basis: 30px;
    width: 30px;
    height: 30px;
  }

  body:has(.parent-nav) .parent-nav-inner .brand-wordmark {
    display: none;
  }

  body:has(.parent-nav) .parent-nav-inner .chrome-row .parent-nav-link {
    padding-inline: var(--s-1);
    font-size: var(--fs-small);
  }

  /* The child tab bar is fixed, so the brand must remain visible without
     spending another band of the page's usable height. The child chrome keeps
     its own row on this same top band; reserve the logo's horizontal slot so
     the avatar and the home link never compete for the same pixels. */
  body:has(.child-nav) .brand-bar {
    position: fixed;
    inset: 0 0 auto;
    z-index: 3;
    padding: var(--s-2) 0;
    pointer-events: none;
  }

  body:has(.child-nav) .brand-link {
    pointer-events: auto;
  }

  body:has(.child-nav) .brand-wordmark {
    display: none;
  }

  body:has(.child-nav) .child-chrome-row {
    flex-wrap: nowrap;
    padding-inline-start: calc(var(--s-6) + var(--s-4));
  }

  body:has(.child-nav) .child-chrome-name {
    min-width: 0;
    overflow: hidden;
    flex: 1 1 auto;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body:has(.child-nav) .child-exit-form {
    flex: 0 0 auto;
  }

  /* Keep the cash reference and its educational disclaimer intact while
     leaving room for both weekly answers before the fixed tab bar. */
  .child-home-cash-card .card {
    gap: var(--s-2);
    margin-bottom: var(--s-2);
    padding: var(--s-3);
  }
  .child-home-cash-card h2,
  .child-home-cash-card p { margin: 0; }
  .child-home-cash-card h2 { font-size: var(--fs-h3); }
  /* The two answers remain named and full-sized, but their stacked mobile
     layout must keep both 44px controls above the fixed tab bar. */
  .weekly-decision { padding: var(--s-2); }
  .weekly-decision-options {
    grid-template-columns: 1fr;
    gap: var(--s-2);
    margin-top: var(--s-2);
  }
  .weekly-decision-option {
    gap: var(--s-2);
    padding: var(--s-1) var(--s-2);
  }
}

/* ── The amount, its chips and its slider ─────────────────────────────────*/
.amount-controls { display: grid; gap: var(--s-3); }
.chip-row { display: flex; flex-wrap: wrap; gap: var(--s-2); }
/* A chip is a real target: at 360px four of them wrap rather than shrink. */
.chip-link { padding: var(--s-3) var(--s-4); }
.chip-note { margin: 0; color: var(--v-ink-2); font-size: var(--fs-note); }

.amount-slider {
  width: 100%;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  accent-color: var(--v-edu);
}

.chosen-all { margin: 0; color: var(--v-edu-ink); }

/* ── The derived block ────────────────────────────────────────────────────
   Dashed, because it is an estimate and must not read as a receipt. It is the
   htmx fragment's target, and @starting-style lifts a new figure into place
   instead of letting it jump — covered by the reduced-motion blanket in the
   token sheet like everything else. */
.derived {
  border: 1px dashed var(--v-edu);
  border-radius: var(--r-md);
  padding: var(--s-4);
  background: var(--v-edu-tint);
  transition: translate var(--t-mid) var(--ease), opacity var(--t-mid) var(--ease);

  @starting-style {
    translate: 0 6px;
    opacity: 0;
  }
}

.derived-line { margin: 0 0 var(--s-2); color: var(--v-edu-ink); font-size: var(--fs-lead); }
.derived-figures {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-1) var(--s-3);
  margin: 0;
}
.derived-figures dt { color: var(--v-ink-2); font-size: var(--fs-small); }
.derived-figures dd { margin: 0; text-align: right; }
.derived-note { margin: var(--s-3) 0 0; color: var(--v-ink-2); font-size: var(--fs-note); }
.derived-empty {
  margin: 0;
  border: 1px dashed var(--v-hair-strong);
  border-radius: var(--r-md);
  padding: var(--s-4);
  color: var(--v-ink-2);
}

/* ── Step 2, the review ───────────────────────────────────────────────────*/
.review-sentence { margin: 0; font-size: var(--fs-lead); line-height: 1.5; text-wrap: pretty; }
.review-table { width: 100%; border-collapse: collapse; margin: var(--s-4) 0 0; }
.review-table th, .review-table td {
  border-bottom: 1px solid var(--v-hair);
  padding: var(--s-2) 0;
  text-align: left;
}
.review-table th { font-weight: 600; color: var(--v-ink-2); }
.review-table td { text-align: right; }

/* ── Pending: three stops, and the truth about the money ──────────────────
   The design offers „banii sunt puși deoparte". Nothing is set aside, so the
   copy states what actually holds — see pendingTimeline in
   internal/order/view/pages.templ. */
.request-timeline {
  display: grid;
  gap: var(--s-2);
  margin: var(--s-3) 0;
  padding: 0;
  list-style: none;
  counter-reset: stop;
}

.timeline-step {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--v-ink-2);
  font-size: var(--fs-small);
}

.timeline-step::before {
  content: "";
  flex: 0 0 auto;
  width: var(--s-3);
  height: var(--s-3);
  border: 2px solid var(--v-hair-strong);
  border-radius: var(--r-pill);
}

.timeline-step.is-done::before { border-color: var(--v-ok); background: var(--v-ok); }
.timeline-step.is-current::before { border-color: var(--v-attn); background: var(--v-attn-tint); }
.timeline-step.is-current { color: var(--v-attn); font-weight: 700; }

.request-reassurance { margin: var(--s-2) 0 0; color: var(--v-edu-ink) !important; font-weight: 700; }

/* ── The parent's two prices ──────────────────────────────────────────────
   Side by side, and the one that will actually be used is bordered in --v-ink
   and says so. A parent deciding against a single figure cannot tell whether the
   market moved since the child asked. */
.price-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--s-3);
  margin: var(--s-4) 0;
}

.price-card {
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  padding: var(--s-4);
  background: var(--v-paper);
}

.price-card-current { border: 2px solid var(--v-ink); }

.price-card-label {
  margin: 0 0 var(--s-2);
  color: var(--v-ink-3);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.1em;
}

.price-card-current .price-card-label { color: var(--v-ink); }
.price-card-figure { margin: 0; font-size: var(--fs-h3); }
.price-card-note { margin: var(--s-2) 0 0; color: var(--v-ink-2); font-size: var(--fs-note); }

/* ── The approval detail's own decision card ──────────────────────────────
   id="order-decision" (see orderDecisionCard in internal/order/view) is htmx
   fragment 3's own target: approve/reject re-render exactly this element for
   an HX-Request, instead of a redirect or a full page. Its background
   carries the same state a pending request has everywhere else in the
   product — .is-pending sits on --v-attn-tint, the same tint the attention
   queue itself is bordered in, because this card is what that queue is
   pointing at. A rejection gets neither tint: "nu acum" stays a message, not
   a verdict, matching the child's own reply screen. */
.order-decision.is-pending { background: var(--v-attn-tint); }
.order-decision.is-settled { background: var(--v-ok-tint); }

/* Motion 4 — settling (README's fourth motion case): the one render that
   follows an approval actually committing carries is-settling in addition to
   is-settled, and crosses from --v-attn-tint to --v-ok-tint once, over
   --t-slow. No JavaScript: the keyframe plays because is-settling is present
   the moment htmx inserts this element into the DOM (or, for a plain reload,
   the moment the browser paints it) — nobody re-triggers it on a later visit
   because a later visit never carries is-settling again. The blanket
   prefers-reduced-motion rule at the top of this file already forces every
   animation-duration to .01ms, so a reader who asked for less motion gets the
   end state instantly rather than the crossing. */
.order-decision.is-settling { animation: v-settle var(--t-slow) var(--ease) 1; }
@keyframes v-settle {
  from { background: var(--v-attn-tint); }
  to   { background: var(--v-ok-tint); }
}

/* The child's own reason, quoted rather than paraphrased. */
.child-quote {
  margin: 0;
  border-inline-start: 3px solid var(--v-edu);
  padding: var(--s-2) 0 var(--s-2) var(--s-4);
  color: var(--v-edu-ink);
  font-size: var(--fs-lead);
  line-height: 1.5;
  text-wrap: pretty;
}

/* ── A refusal, drawn as a message ────────────────────────────────────────
   No --v-err anywhere in this block, deliberately: the child did nothing wrong,
   and „nu acum" is a sentence in a conversation, not a fault. The chip is neutral
   ink on the quiet surface, and the parent's note is the largest text. */
.parent-reply {
  display: grid;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  background: var(--v-quiet);
}

.reply-chip {
  justify-self: start;
  margin: 0;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-pill);
  padding: var(--s-1) var(--s-3);
  color: var(--v-ink-2);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.12em;
}

.reply-note {
  margin: 0;
  border: 0;
  padding: 0;
  color: var(--v-ink) !important;
  font-size: var(--fs-lead);
  line-height: 1.5;
  text-wrap: pretty;
}

.reply-intact { margin: 0; color: var(--v-edu-ink) !important; font-weight: 700; }

/* ══ PHASE 5 — THE OTHER TWO CHARTS ═══════════════════════════════════════
   The composition bar landed with the partials in phase 2. These are the ring
   and the time series, under the same two rules: no library, and each one must
   be able to say "I do not know".
   ───────────────────────────────────────────────────────────────────────── */

/* ── progressRing ─────────────────────────────────────────────────────────
   A conic-gradient with the centre punched out by an inner circle in the
   surface colour. The unknown ring is the hatch, so "cannot be computed" reads
   the same way here as it does in a bar and in an amount. */
.chart-ring { display: grid; justify-items: center; gap: var(--s-2); }

.ring-holder {
  position: relative;
  display: grid;
  place-items: center;
  width: 140px;
  height: 140px;
}

.ring {
  width: 100%;
  height: 100%;
  border-radius: var(--r-pill);
  background: conic-gradient(
    var(--v-edu) 0 var(--ring-share, 0%),
    var(--v-sunk) var(--ring-share, 0%) 100%
  );
}

/* The punched centre. An inner circle in the page's own surface colour rather
   than a mask, so it works in both themes with no second declaration. */
.ring-holder::after {
  content: "";
  position: absolute;
  width: 66%;
  height: 66%;
  border-radius: var(--r-pill);
  background: var(--v-paper);
}

/* An unknown share is hatched and has no sweep at all: a ring drawn at 0% would
   read as "nothing saved yet", which is a different statement from "we cannot
   work this out". */
.ring-unknown { background: var(--v-sunk); border: 1px dashed var(--v-hair-strong); }

.ring-centre {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: var(--s-1);
  text-align: center;
}

.ring-share { color: var(--v-edu-ink); font-size: var(--fs-h3); font-weight: 800; }
.ring-caption { margin: 0; color: var(--v-ink-2); font-size: var(--fs-small); text-align: center; }

/* ── priceSeries ──────────────────────────────────────────────────────────
   A polyline for what was recorded, and a dashed half-opacity segment for what
   was not. The dashed box around the plot is the reminder that the horizontal
   axis is time nobody promised was continuous. */
.series-plot {
  width: 100%;
  height: 120px;
  border: 1px dashed var(--v-hair-strong);
  border-radius: var(--r-xs);
  background: var(--v-paper);
  /* The stroke must not stretch with the box: preserveAspectRatio="none" scales
     user space, and vector-effect keeps the line one width at every viewport. */
  overflow: visible;
}

.series-line {
  fill: none;
  stroke: var(--v-ink);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.series-point { fill: var(--v-ink); }

/* The gap. Dashed, half opacity, and in the attention colour rather than the ink
   colour — it is not a price, it is the absence of one. */
.series-gap {
  stroke: var(--v-attn);
  stroke-width: 2;
  stroke-dasharray: 4 4;
  opacity: 0.5;
  vector-effect: non-scaling-stroke;
}

.series-gap-flag {
  display: block;
  color: var(--v-attn);
  font-size: var(--fs-note);
  font-weight: 700;
}

/* ══ PHASE 023 — THE MULTI-SERIES VALUE CHART (S3) ════════════════════════
   ValueSeries (series.templ) draws several recorded lines — assets by
   category, liabilities, net worth — over one time axis, as a staircase: a
   diagonal segment would assert a value moved between two rungs when nothing
   was recorded there (R5). A liability is dashed, net worth is heavier, and
   an asset category cycles through the same tint family compositionBar
   already uses, so none of the three is ever mistaken for another.
   ───────────────────────────────────────────────────────────────────────── */
/* overflow-x: hidden is the backstop for the rightmost-label defect (phase
   023 S7 measured 373px scrollWidth against a 360px clientWidth): the plot's
   own box is what may never let a descendant widen the document, so nothing
   here relies on every future label staying inside it by luck. The two edge
   classes below are the actual fix; this is only the guarantee that a third
   one, someday, cannot repeat the bug. */
.chart-value-series .value-series-plot { position: relative; overflow-x: hidden; }

.value-series-svg { height: 220px; overflow: visible; }

.value-line-path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.value-point { fill: currentColor; }

/* A point reached without a new record is still drawn, and still connected —
   chart.templ's CarriedForward — but its own marker is hollow rather than
   filled, so it reads as "known, unchanged" rather than as fresh news. */
.value-point-carried { fill: var(--v-paper); stroke: currentColor; stroke-width: 1; }

.value-line-solid { color: var(--v-ink); }
.value-line-solid.value-line-c1 { color: color-mix(in oklab, var(--v-ink) 58%, var(--v-paper)); }
.value-line-solid.value-line-c2 { color: color-mix(in oklab, var(--v-edu) 82%, var(--v-paper)); }
.value-line-solid.value-line-c3 { color: color-mix(in oklab, var(--v-ink) 30%, var(--v-paper)); }

/* A liability never looks like an asset: dashed, in the secondary ink rather
   than any accent colour, because a debt is a fact, not an alert. */
.value-line-dashed { color: var(--v-ink-2); }
.value-line-dashed .value-line-path { stroke-dasharray: 5 3; }

/* Net worth is the sum, never one input among several: the same ink as a
   plain asset line, drawn heavier so it reads as the total. */
.value-line-emphasis { color: var(--v-ink); }
.value-line-emphasis .value-line-path { stroke-width: 3; }
.value-line-emphasis .value-point { r: 1.8; }

.value-series-labels { position: absolute; inset: 0; pointer-events: none; }

/* Every point carries its own value as text (phase 023 R6) — the spreadsheet
   this phase competes with labels every point for the same reason, and it is
   right to. Hidden from a screen reader: the figures table below states the
   same numbers in a structure a screen reader can actually navigate. */
.value-series-label {
  position: absolute;
  transform: translate(-50%, -140%);
  background: var(--v-paper);
  border-radius: var(--r-xs);
  padding: 0 var(--s-1);
  font-size: var(--fs-note);
  white-space: nowrap;
}

/* The two ends of the time axis anchor to the plot's own edge rather than
   centring on their point — the fix for the rightmost label hanging half
   past the right edge and widening the whole document (phase 023 S7
   measured 373px scrollWidth against a 360px clientWidth at left: 100%
   translated only -50%). Every interior point keeps the centred transform
   above; only x: 0% and x: 100% need a different anchor. */
.value-series-label-start { transform: translate(0, -140%); }
.value-series-label-end { transform: translate(-100%, -140%); }

/* Stated because it is a rule, not a coincidence of the pixel grid
   (valueLabelShown, series.templ): once a line is dense enough that its own
   labels would collide, the chart keeps only a defensible subset on its own
   face and says so here, in the same voice as .value-carried-flag, rather
   than leaving a sighted reader to notice the gap unassisted. */
.value-series-thin-note {
  margin: var(--s-2) 0 0;
  color: var(--v-ink-2);
  font-size: var(--fs-note);
}

.value-series-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  padding: 0;
  list-style: none;
}

.value-series-legend-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-small);
}

.value-series-swatch {
  display: inline-block;
  width: var(--s-4);
  height: 3px;
  border-radius: var(--r-xs);
  background: currentColor;
  color: var(--v-ink);
}
.value-series-swatch.value-line-dashed {
  height: 0;
  background: none;
  border-top: 2px dashed currentColor;
}
.value-series-swatch.value-line-emphasis { height: 4px; }

.value-series-table { margin-top: var(--s-3); width: 100%; }
.value-series-table caption {
  text-align: left;
  font-weight: 700;
  padding-bottom: var(--s-1);
}

/* Reported, not new: a step resting on no new record gets its own note, in
   the secondary ink rather than the attention colour PriceSeries's gap uses
   — this is not an absence, it is the opposite of one. */
.value-carried-flag {
  display: block;
  color: var(--v-ink-2);
  font-size: var(--fs-note);
  font-weight: 700;
}

/* ── Mixed currency: subtotals instead of a bar ───────────────────────────
   A composition bar has one axis and therefore one unit. Where the money is in
   two currencies there is no such unit, so the bar is not drawn at all and these
   take its place. The ≠ between them is doing real work: it says these figures
   must not be added, because no exchange rate exists in this product. */
.currency-subtotals {
  display: grid;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  padding: 0;
  list-style: none;
}

.currency-subtotal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.currency-separator {
  color: var(--v-ink-2);
  font-size: var(--fs-h3);
  font-weight: 800;
}

/* ══ PHASE 9 — THE TWO LEDGER SCREENS ═════════════════════════════════════
   2a: the real portfolio list becomes a record grid with a total strip and
   the ≠ exclusion note, and the item detail page's append-only history
   becomes a connected timeline. 2b: the educational ledger's correction
   renders as the pair the data already models — the amber correction row
   above, the original struck through below it, never edited.
   ───────────────────────────────────────────────────────────────────────── */

/* ── record grid: the shared shape behind 2a's portfolio list and 2b's
   ledger history ─────────────────────────────────────────────────────────
   One bordered surface, a sunk header row, hairline rows, and a sunk total
   strip — the same shape the design draws for both screens. Each row is a
   `display:contents` wrapper around its cells, so classing the wrapper still
   reaches its children through the descendant selectors below even though
   the wrapper itself generates no box. */
.record-table {
  margin: var(--s-4) 0 0;
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-sm);
  background: var(--v-paper);
  overflow: hidden;
}

.record-grid {
  display: grid;
  grid-template-columns: minmax(11rem, 1.6fr) minmax(0, auto) auto auto;
  overflow-x: auto;
  font-size: var(--fs-small);
}

.record-grid-3col { grid-template-columns: auto minmax(0, 1fr) auto; }

.record-head {
  padding: var(--s-2) var(--s-4);
  background: var(--v-sunk);
  color: var(--v-ink-3);
  font-size: var(--fs-eyebrow);
  font-weight: 800;
  letter-spacing: 0.12em;
}

.record-head-right { text-align: right; }

.record-cell {
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--v-hair);
  color: var(--v-ink);
}

.record-cell-right { text-align: right; }
.record-cell-empty { color: var(--v-ink-3); }

/* The element cell doubles as the row's link on 2a: a real anchor rather
   than the whole row, so the click target stays an ordinary, unambiguous
   interactive element inside a `display:contents` row. */
a.record-cell-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.record-cell-name { margin: 0; font-weight: 700; color: var(--v-ink); overflow-wrap: break-word; }
.record-cell-meta { margin: var(--s-1) 0 0; font-size: var(--fs-note); color: var(--v-ink-2); overflow-wrap: break-word; }
.record-cell-meta-attn { color: var(--v-attn); font-weight: 700; }

.record-total {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--v-hair-strong);
  background: var(--v-sunk);
  font-weight: 700;
}
.record-total .money { margin-left: auto; font-size: var(--fs-h3); }

.record-note {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--v-hair);
  color: var(--v-ink-2);
  font-size: var(--fs-note);
}
.record-note p { margin: 0 0 var(--s-1); }
.record-note p:last-child { margin-bottom: 0; }
.record-note-glyph { flex: 0 0 auto; font-weight: 800; color: var(--v-ink-3); }

/* The shared-with chip: the dashed edge is the Shared material's own edge
   (docs/design/vectro-redesign/README.md's three-material table), reused
   here rather than invented, so "shared" reads the same everywhere it
   appears. */
.chip-shared {
  border-color: var(--v-edu);
  border-style: dashed;
  color: var(--v-edu-ink);
  padding: var(--s-1) var(--s-2);
  font-size: var(--fs-note);
}

/* The row wrapper itself generates no box — see the record-table comment
   above — so this has to live in the stylesheet rather than as an inline
   style="display:contents" attribute: the CSP's style-src only tolerates
   inline styles for server-composed chart geometry (see withSecurityHeaders
   in internal/web/app.go), not for a constant layout declaration every row
   shares. */
.record-row { display: contents; }

/* The record grid stops being a table below 32rem.

   This query sits *after* .record-row's own `display: contents` rather than in
   the general mobile block higher up the sheet, and that is load-bearing: the
   two selectors have equal specificity, so the one written later wins. Placed
   earlier, every declaration here that competes with a base .record-* rule was
   silently discarded and the rows kept laying out as four columns — the
   labels rendered, which made it look like it had worked. */
@media (max-width: 32rem) {
  /* The record grid stops being a table and becomes one card per item.
     Four columns at 360px left the header row above the first item and
     nothing above any of the others, so every row after the first read as
     four unlabelled lines. The headers are doing real work at 1280px and
     none at all here, so they come off and each cell names itself from its
     own data-label instead. */
  .record-grid { grid-template-columns: 1fr; }
  .record-head { display: none; }
  .record-row {
    display: block;
    border-top: 1px solid var(--v-hair);
  }
  .record-row .record-cell { border-top: 0; }
  .record-cell[data-label] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-3);
    padding-block: var(--s-1);
    text-align: left;
  }
  .record-cell[data-label]::before {
    content: attr(data-label);
    color: var(--v-ink-3);
    font-size: var(--fs-eyebrow);
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }
  /* The value stays the loudest thing in the card; the label beside it is
     the quiet half of the pair. */
  .record-cell[data-label] .money { text-align: right; }
}

/* ── 2b: the correction pair ──────────────────────────────────────────────
   The amber row above states what it cancels; the original below it keeps
   its dashed top border, its struck-through amount, and the sentence that
   makes the strike-through readable without colour or decoration alone. */
.record-row-correction .record-cell { background: var(--v-attn-tint); }
.record-row-correction .record-movement-title { color: var(--v-ink); }
.record-row-correction .record-cell-right { color: var(--v-err); }

.record-row-struck .record-cell { border-top: 1px dashed var(--v-attn); }
.record-row-struck .record-movement-title {
  color: var(--v-ink);
  text-decoration: line-through;
  text-decoration-color: var(--v-ink-3);
}
.record-row-struck .record-cell-right {
  color: var(--v-ink-3);
  text-decoration: line-through;
}

.record-movement-title { margin: 0; font-weight: 700; }
.record-movement-note { margin: var(--s-1) 0 0; font-size: var(--fs-note); color: var(--v-ink-2); }

/* The inline correction control: a native disclosure rather than a fifth
   htmx fragment, spanning every column of the row it belongs to. */
.record-correct {
  grid-column: 1 / -1;
  border-top: 1px solid var(--v-hair);
  padding: 0 var(--s-4);
  background: var(--v-quiet);
}
.record-correct summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  color: var(--v-ink-2);
  font-weight: 700;
  font-size: var(--fs-small);
  cursor: pointer;
}
.record-correct form { padding: 0 0 var(--s-4); }

/* ── 2a: the item detail's append-only history, as a timeline ─────────────
   One dot per dated record, connected by a rail. The newest record's dot is
   filled solid; every earlier one is hollow, and nothing here is ever an
   edit — each row is a row that was appended. */
.value-timeline {
  display: grid;
  grid-template-columns: var(--s-4) minmax(0, 1fr) auto;
  gap: 0 var(--s-3);
  margin: var(--s-3) 0 0;
}

/* Same reasoning as .record-row above: this wrapper generates no box of its
   own, so the declaration lives here rather than as an inline
   style="display:contents" attribute. */
.value-timeline-row { display: contents; }

.value-timeline-dot-cell {
  position: relative;
  display: grid;
  justify-items: center;
  padding-top: var(--s-1);
}

.value-timeline-dot {
  width: 10px;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--v-hair-strong);
}

.value-timeline-row:first-child .value-timeline-dot { background: var(--v-ink); }

.value-timeline-row:not(:last-child) .value-timeline-dot-cell::after {
  content: "";
  position: absolute;
  top: 9px;
  bottom: calc(-1 * var(--s-3));
  width: 1.5px;
  background: var(--v-hair-strong);
}

.value-timeline-body { padding-bottom: var(--s-4); }
.value-timeline-date { margin: 0; font-weight: 700; color: var(--v-ink); }
.value-timeline-row:not(:first-child) .value-timeline-date { color: var(--v-ink-2); font-weight: 700; }
.value-timeline-note { margin: var(--s-1) 0 0; font-size: var(--fs-note); color: var(--v-ink-2); }
.value-timeline-value { padding-bottom: var(--s-4); text-align: right; font-weight: 700; color: var(--v-ink); }
.value-timeline-row:not(:first-child) .value-timeline-value { color: var(--v-ink-2); }

@media (max-width: 32rem) {
  .record-grid,
  .record-grid-3col { grid-template-columns: 1fr; }
  .record-cell-right { text-align: left; }
  .record-head-right { text-align: left; }
}

/* ══ PHASE 11 — FIRST CONTACT: landing, first run, empty states, PIN handoff
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1i: the landing page's three avatars ─────────────────────────────── */
.landing-avatars {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin: 0 0 var(--s-6);
}

/* ── 1j: the parent's first-run checklist ─────────────────────────────── */
.first-run-list { display: grid; gap: var(--s-3); margin: var(--s-4) 0 0; padding: 0; list-style: none; }
.first-run-step { border: 1px solid var(--v-hair-strong); border-radius: var(--r-sm); background: var(--v-paper); }
.first-run-link {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 44px;
  padding: var(--s-4);
  color: inherit;
  text-decoration: none;
}
/* A muted fill marks a done step, never opacity: opacity dims the text
   itself, and axe's colour-contrast check caught exactly that stacking on
   top of --v-ink-2's already-measured ratio and failing it in the light
   theme. The fill alone, at full text strength, keeps the row legible and
   still visually "put away". */
.first-run-step-done {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 44px;
  padding: var(--s-4);
  background: var(--v-quiet);
}
.first-run-number {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-pill);
  background: var(--v-ink);
  color: var(--v-paper);
  font-size: var(--fs-note);
  font-weight: 700;
}
.first-run-step-done .first-run-number { background: var(--v-ok); }
.first-run-body { flex: 1 1 auto; display: grid; gap: var(--s-1); }
.first-run-title { margin: 0; font-weight: 700; }
.first-run-detail { margin: 0; color: var(--v-ink-2); font-size: var(--fs-small); }
.first-run-glyph { flex: 0 0 auto; width: 1.1em; height: 1.1em; color: var(--v-ink-3); }
.first-run-step-done .first-run-glyph { color: var(--v-ok); }

main.shell:not(.shell-narrow) > .first-run-card .first-run-list {
  gap: var(--s-2);
  margin-top: 0;
}

main.shell:not(.shell-narrow) > .first-run-card .first-run-link {
  padding-block: var(--s-3);
}

/* ── 1j: the child empty state — the 96px avatar leads the page ───────── */
.child-empty-lead {
  display: grid;
  justify-items: center;
  gap: var(--s-3);
  margin: var(--s-5) 0;
  padding: var(--s-5) var(--s-4);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-lg);
  background: var(--v-paper);
  text-align: center;
}
.child-empty-lead .state-note { justify-content: center; }
.child-empty-lead .item-actions { justify-content: center; margin: 0; }

/* ── 2c: "who is using the phone" — the restyled /children list ───────── */
.entry-list { display: grid; gap: var(--s-3); margin: var(--s-4) 0 0; padding: 0; list-style: none; }
.entry-item { display: grid; gap: var(--s-2); }
.entry-row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 60px;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-lg);
  background: var(--v-paper);
  color: inherit;
  text-decoration: none;
}
a.entry-row:hover,
a.entry-row:focus-visible { background: var(--v-sunk); }
.entry-row-locked { opacity: 0.65; }
.entry-name { flex: 1 1 auto; display: grid; gap: var(--s-1); font-weight: 700; }
.entry-lock-note { font-weight: 600; color: var(--v-ink-2); font-size: var(--fs-small); }
.entry-glyph { flex: 0 0 auto; width: 1.1em; height: 1.1em; color: var(--v-ink-3); }
.entry-manage-link { justify-self: start; font-size: var(--fs-small); }

/* ── 2c: the PIN keypad ────────────────────────────────────────────────── */
.pin-input {
  min-height: 60px;
  font-size: 1.4rem;
  text-align: center;
  letter-spacing: 0.4em;
}
.pin-keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-2);
  margin: var(--s-3) 0;
}
.pin-keypad button {
  min-width: 60px;
  min-height: 60px;
  border: 1px solid var(--v-hair);
  border-radius: var(--r-lg);
  background: var(--v-paper);
  color: var(--v-ink);
  font-size: 1.2rem;
  font-weight: 700;
}
.pin-keypad-gap { visibility: hidden; }

/* On a phone the real input's numeric keyboard is a valid alternative to the
   on-screen keypad. app.js adds this class only after the visual viewport
   actually shrinks, so the native keyboard cannot cover the keypad. */
@media (max-width: 32rem) {
  [data-pin-keypad].pin-native-keyboard .pin-keypad { display: none; }
}

/* ══ PHASE 13 — THE REMAINING CHILD SURFACES ═══════════════════════════════
   The child card's container queries live beside .child-strip above (search
   .children-grid / .child-card). What is left here: the risk ladder (1e),
   the "DE LA MAMA ȘI TATA" activity rows (1d), and the child instrument
   detail's own explanation, which is the largest body text on that page.
   ───────────────────────────────────────────────────────────────────────── */

/* ── 1e: risk as a four-step ladder, never a word alone ───────────────────
   The bars are aria-hidden; the Romanian word beside them is the only thing
   a screen reader announces, and it is always present too. */
.risk-ladder-wrap { display: inline-flex; align-items: center; gap: var(--s-2); }
.risk-ladder { display: inline-flex; gap: var(--s-1); }
.risk-step {
  width: 13px;
  height: 6px;
  border-radius: var(--r-xs);
  background: var(--v-sunk);
}
.risk-step-filled { background: var(--v-edu); }
.risk-ladder-label { font-weight: 700; font-size: var(--fs-small); color: var(--v-ink-2); }

/* ── 1e: the parent's own explanation, largest body text on the page ─────
   15px/1.55 per the README, at --fs-body's own upper end rather than a new
   literal size, in the primary ink rather than .lead's muted tone: this is
   not a lede, it is the reason the price below it matters. */
.child-explanation {
  max-width: var(--measure);
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--v-ink);
}

/* ── 1d: "DE LA MAMA ȘI TATA" as sentences with a status glyph, never a
   table ───────────────────────────────────────────────────────────────── */
.parent-activity-list { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.parent-activity-row {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-top: 1px solid var(--v-hair);
}
.parent-activity-row:first-child { border-top: 0; }
.parent-activity-text { display: grid; gap: var(--s-1); min-width: 0; }
/* Issue 291: the parent's own message reads first and largest -- it used to
   be a trailing clause after a settlement sentence, the least prominent
   thing in a row where it was the only living content. The settlement/status
   detail (also used standalone, e.g. a pending request) stays the smaller,
   muted line it always was. */
.parent-activity-message { margin: 0; font-size: var(--fs-body); font-weight: 600; line-height: 1.45; color: var(--v-ink); }
.parent-activity-detail { margin: 0; font-size: var(--fs-small); line-height: 1.45; color: var(--v-ink-2); }
.activity-glyph {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: var(--r-pill);
}
.activity-glyph svg { width: 13px; height: 13px; }
.activity-glyph-ok { background: var(--v-ok-tint); color: var(--v-ok); }
.activity-glyph-attn { background: var(--v-attn-tint); color: var(--v-attn); }
/* Neutral, never red: a "not now" reply is a message, not a verdict. */
.activity-glyph-neutral { background: var(--v-sunk); color: var(--v-ink-2); }

/* ══ PHASE 12 — MOTION AND THE LAST TWO FRAGMENTS ═══════════════════════════
   Motion 1 (view transitions, list ↔ detail, asset name as the shared
   element) and motion 4 (settling, beside .order-decision above) are the
   README's last two motion cases. Fragments 2 and 3 need no CSS of their own
   beyond .order-decision above; this section is motion 1 plus the
   reduced-motion guard it needs on its own, because ::view-transition-* is
   not an ordinary element and the blanket `*, *::before, *::after` rule near
   the top of this file cannot select it. */

/* Cross-document view transitions: every same-origin navigation this product
   already does with a plain <a href> (no SPA, no client router) becomes an
   animated transition automatically once the browser supports @view-transition
   navigation: auto — nothing else in the request/response cycle changes.
   Gated on (scripting: enabled) — measured, not assumed: with scripting
   fully disabled, Chromium's own cross-document transition never completes
   its internal lifecycle and the next page becomes unclickable until the
   test/reader gives up, which is a direct violation of "keep every page
   working without JavaScript". (scripting: enabled) is the one thing that
   correlates with what was actually measured: it was on in every case that
   worked, off in every case that hung, across a JS-enabled control page and
   a `browser.newContext({ javaScriptEnabled: false })` page in the exact
   same flow. No browser currently ships @view-transition support without
   also supporting the scripting media feature, so this costs nothing where
   the feature would have worked anyway. */
@media (scripting: enabled) {
  @view-transition {
    navigation: auto;
  }
}

/* view-transition-name is set inline, per instrument, on the element that
   carries the asset's name in both the list and the detail page — see
   internal/catalog/view's instrumentTransitionStyle, used on CatalogList's
   and ChildCatalogList's <strong> and on InstrumentDetailPage's and
   ChildInstrumentDetail's <h1>. A shared, unique name is what tells the
   browser which outgoing element morphs into which incoming one; without it
   every navigation would just cross-fade the whole page. */

/* The transition's own top-layer overlay must never be clickable: the UA
   stylesheet does not promise pointer-events: none on these pseudo-elements,
   and the outgoing page's captured frame sits above the incoming page's real
   content for the brief duration of the animation. Without this, a reader
   (or a test driving a real click) can have their pointer event swallowed by
   a transition that is otherwise purely decorative. */
::view-transition,
::view-transition-group(*),
::view-transition-image-pair(*),
::view-transition-old(*),
::view-transition-new(*) {
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* The browser's own default cross-document transition draws its animation
     on ::view-transition-group/-old/-new, a top-layer pseudo-element the page
     did not have before the navigation started — *, *::before, *::after
     cannot select it, so it needs its own instruction to stay off for a
     reader who asked for less movement. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* ══ PHASE 019 (workstream E) — CATALOG RISK ATTRIBUTION, ONE AVAILABILITY
   CONTROL, AND FOUR CHILD-PROFILE ACTION GROUPS ═══════════════════════════
   specs/archive/019-pilot-readiness/spec.md requirements 9, 10 and 11. Appended at
   the end of the sheet, below every existing rule, per this phase's
   single-writer note on app.css. */

/* Requirement 9: "Etichetă aleasă de părinte" beside every rendered risk
   label (internal/catalog/view/pages.templ's riskAttribution), on both the
   parent's own screens and the child's four-step ladder — so neither ever
   reads as a platform classification. Small and muted rather than styled as
   a fact or a warning: it is a footnote about authorship, not a status. */
.risk-attribution {
  display: block;
  margin-top: var(--s-1);
  font-size: var(--fs-note);
  color: var(--v-ink-3);
}

/* Requirement 10: the permitted-asset list's enable/disable control is now a
   checkbox beside its own label, inside the same form as the allocation cap,
   with one Salvează button — never two buttons offering opposite actions at
   once. The checkbox square itself keeps the product's existing 1.5rem
   input[type="checkbox"] sizing; what this rule adds is the 44px minimum on
   the whole label, so the touch target is the checkbox AND its words, not
   the small square alone. */
.availability-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-2) 0;
  cursor: pointer;
}

/* .item-list span is styled muted for secondary detail lines (type, risk,
   currency); the toggle's own span is the wording of an actionable control,
   not a detail, and must not read as one just because it sits in the same
   list. */
.availability-toggle span {
  color: var(--v-ink);
}

/* .action-form's margin: 0 leaves no gap between the toggle label and the
   Salvează button, and templ emits no whitespace between them — so the
   button's left edge abuts the label's last word. */
.availability-form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
}

/* Requirement 11: the child profile's twelve flat links become four
   sections. Each gets its own h2 (unstyled, like every other h2 in this
   sheet) and normal .item-actions spacing below it; the group itself only
   needs the vertical rhythm between one heading and the next. */
.profile-action-group {
  margin-top: var(--s-6);
}
.profile-action-group h2 {
  margin: 0;
}
.profile-action-group .item-actions {
  margin-top: var(--s-3);
}

/* The Siguranță zone carries block/unblock and archive/restore — the two
   destructive or state-changing actions the redesign's own action-grouping
   goal keeps away from routine ones. --v-attn is this sheet's existing
   "pay attention, not an error" colour (see the token sheet's own comment on
   --v-attn), which is exactly the register a safety zone needs: noticeable
   without reading as a fault. */
.profile-action-group-danger {
  border: 1px solid var(--v-attn);
  border-radius: var(--r-md);
  padding: var(--s-4);
  background: var(--v-attn-tint);
}
.profile-action-group-danger h2 {
  color: var(--v-attn);
}

/* Issues 256 and 261: "Ce se repetă" gets the same "own block, visually
   distinct" treatment as Siguranță above, for the opposite reason — not a
   safety zone to keep away from routine actions, but the two routine
   actions themselves, promoted out of a lineup where nothing distinguished
   them from the ten a parent touches once. --v-edu is this sheet's
   educational-voice tint (see its own token comment): both actions concern
   the child's educational account, never real money, so the safety zone's
   --v-attn register would be the wrong signal here. */
.profile-action-group-recurring {
  border: 1px solid var(--v-edu);
  border-radius: var(--r-md);
  padding: var(--s-4);
  background: var(--v-edu-tint);
}
.profile-action-group-recurring h2 {
  color: var(--v-edu-ink);
}

/* The child profile is a management hub, not a long form. Keep the identity
   together in one compact header and give the actions a bounded card grid so
   a wide parent shell is used intentionally instead of producing one narrow
   column with a large empty half. The grid collapses at the point where two
   cards would make their labels harder to scan than a single column. */
.profile-shell {
  gap: 0;
}

.profile-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-6);
  margin-bottom: var(--s-6);
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-md);
  background: var(--v-paper);
}

.profile-identity {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: var(--s-5);
}

.profile-identity .avatar {
  flex: 0 0 auto;
}

.profile-identity-copy {
  min-width: 0;
}

.profile-identity-copy .eyebrow {
  margin-bottom: var(--s-2);
}

.profile-identity-copy h1 {
  margin-bottom: var(--s-3);
}

.profile-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-4);
  color: var(--v-ink-2);
  line-height: 1.4;
}

.profile-meta .chip {
  color: var(--v-ink);
}

.profile-meta .state-note {
  max-width: none;
  margin: 0;
}

.profile-hero-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

.profile-action-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: var(--s-5);
}

.profile-action-grid .profile-action-group {
  min-width: 0;
  margin: 0;
  padding: var(--s-5);
  border: 1px solid var(--v-hair-strong);
  border-radius: var(--r-md);
  background: var(--v-paper);
}

.profile-action-grid > .profile-action-group-recurring,
.profile-action-grid > .profile-action-group-danger {
  grid-column: 1 / -1;
}

.profile-action-grid .profile-action-group h2 {
  font-size: var(--fs-h3);
  line-height: 1.15;
}

.profile-action-grid .profile-action-group .item-actions {
  margin: var(--s-4) 0 0;
}

.profile-action-grid .profile-action-group-recurring {
  border-color: var(--v-edu);
}

.profile-action-grid .profile-action-group-danger {
  border-color: var(--v-attn);
}

.profile-archived-actions {
  display: grid;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
  padding: var(--s-5);
  border: 1px dashed var(--v-hair-strong);
  border-radius: var(--r-md);
  background: var(--v-sunk);
}

.profile-archived-actions h2,
.profile-archived-actions p {
  margin: 0;
}

.profile-archived-actions p {
  color: var(--v-ink-2);
}

.profile-shell > .profile-privacy-note {
  margin-inline: 0;
}

@media (max-width: 48rem) {
  .profile-hero {
    grid-template-columns: 1fr;
    gap: var(--s-5);
    padding: var(--s-5);
  }

  .profile-hero-actions {
    justify-content: flex-start;
  }

  .profile-action-grid {
    grid-template-columns: 1fr;
  }

  .profile-action-grid > .profile-action-group-recurring,
  .profile-action-grid > .profile-action-group-danger {
    grid-column: auto;
  }
}

@media (max-width: 32rem) {
  .profile-identity {
    align-items: flex-start;
    gap: var(--s-3);
  }

  .profile-identity .avatar {
    width: 72px;
    height: 72px;
  }

  .profile-action-grid .profile-action-group,
  .profile-archived-actions {
    padding: var(--s-4);
  }
}
