/* index.css - Life Balance Index — formal / academic self-assessment design system */

:root {
  /* Color tokens — warm-paper professional palette.

     These names used to describe the palette this app had BEFORE the move to
     warm paper, and were kept on the argument that JS inline styles referenced
     them. The names lost that argument: --color-obsidian, "obsidian", held
     #eceae4 — a near-white rail fill. A token whose name says black and whose
     value is off-white is a trap for whoever next reaches for black, and on a
     solo project that is the author. Renamed to say what they are:
       gold    -> accent   (burgundy #6d2e3f; never was gold)
       astral  -> slate    (navy-slate; never was neon)
       nectar  -> success
       obsidian-> track    (progress-bar and rail backgrounds)
     Safe to do mechanically, and safe to do again: no call site builds a
     property name dynamically — zero hits for getPropertyValue and for
     var(--...${...}) across the codebase — so every reference is a literal a
     find-replace can see. --color-navy and --color-crimson kept their names
     because #24344d and #a23b3b are in fact navy and crimson. */
  --bg-primary: #f7f5f0;           /* warm paper */
  --color-card-bg: #ffffff;        /* cards */
  --color-card-border: #e3ded4;    /* muted borders */
  /* Form-control boundaries, deliberately NOT --color-card-border.
     WCAG 1.4.11 wants 3:1 for the visual boundary of an input, and #e3ded4 on
     the field's own white fill is 1.34:1. Card edging is decorative and exempt,
     so the two cases need two tokens; #767676 is 4.54:1 on #ffffff. */
  --color-input-border: #767676;

  /* Restrained burgundy accent */
  --color-accent: #6d2e3f;
  --color-accent-light: #8a4a5a;
  --color-accent-glow: rgba(109, 46, 63, 0.14);

  --color-navy: #24344d;           /* primary / navy */

  /* Quiet navy-slate for links, progress fills, and focus rings */
  --color-slate: #3a5170;
  --color-slate-dark: #24344d;
  --color-slate-glow: rgba(36, 52, 77, 0.08);

  --color-success: #3f7d5a;        /* success */
  --color-crimson: #a23b3b;        /* danger */
  --color-crimson-light: #c25a5a;

  --color-text-primary: #1f2733;   /* ink */
  --color-text-secondary: #5c6672; /* secondary */
  --color-text-chart: #1f2733;     /* chart labels */
  --color-track: #eceae4;          /* progress track / rail background */

  /* Typography: Source Serif 4 headings, Inter body, Sarabun for Thai */
  /* Each face is subsetted by unicode-range, so ONE stack serves both
     scripts: Latin resolves to Source Serif 4, Thai to Maitree — including
     inside a single mixed run. Sarabun stayed in this stack for years as a
     silent last resort, which meant every Thai heading rendered in a SANS
     and the serif/sans contrast this design is built on did not exist in
     the app's primary language. Maitree is Sarabun's serif companion from
     the same foundry; see assets/fonts/fonts.css. */
  --font-serif: 'Source Serif 4', 'Maitree', Georgia, serif;
  --font-sans: 'Inter', 'Sarabun', system-ui, sans-serif;
  --font-mono: ui-monospace, 'Sarabun', 'SFMono-Regular', Menlo, monospace;

  /* Type scale.
     Before this existed the sheet carried 25 distinct font sizes and the JS
     inline styles carried 10 more, sixteen of them inside the 0.62-0.95rem
     span — a 5px range holding sixteen steps, which is nudging, not a scale.
     The steps below were chosen so the three most-used values (0.75, 0.8,
     0.85) keep their exact numbers and roughly two thirds of call sites
     render identically; only the strays moved, and none moved by more than
     0.8px. Two tiers on purpose: ~1.06-1.11 ratios through the UI text range,
     where small differences carry hierarchy inside a paragraph, and ~1.3
     jumps for display figures, where the number IS the content.
     One deliberate hold-out: the 16px literal in the mobile form-control
     block near the end of this file. iOS zooms the viewport on focus for any
     input under 16px, so that value is a behavioural guard, not a type
     choice, and must not be folded into this scale. */
  --text-2xs: 0.68rem;
  --text-xs: 0.75rem;
  --text-sm: 0.8rem;
  --text-base: 0.85rem;
  --text-md: 0.9rem;
  --text-lg: 1rem;
  --text-xl: 1.15rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.9rem;

  /* Layout */
  --radius: 4px;
  --transition-speed: 0.2s;
}

/* Reset */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-sans);
  background-color: var(--bg-primary);
  color: var(--color-text-primary);
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  line-height: 1.55;
  position: relative;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* --- Thai typography ------------------------------------------------------
 * Thai is this app's primary language and, until this release, the stylesheet
 * had NOT ONE rule scoped to it. Everything below fixes a Latin typographic
 * habit that is actively wrong for Thai script.
 *
 * `lang` is set on <html> by i18n.js on every language switch, so these rules
 * follow the reader without a re-render.
 *
 * 1. LEADING. Thai stacks vowels above and tone marks above those, and
 *    descends below the baseline for others; the glyph column is roughly twice
 *    the height of a Latin x-height run. 1.55 puts the tone marks of one line
 *    into the descenders of the line above. 1.75 clears them.
 * 2. TRACKING. Every negative or positive letter-spacing in this sheet was set
 *    by eye against Latin. Thai renders as shaped clusters — a base consonant
 *    plus its marks — and spacing between them is decided by the font's own
 *    shaping, not by the designer. Tracking pulls a cluster apart or collides
 *    it with its neighbour, and the -0.01em on .brand h1 did the former to the
 *    app's own name. Reset to normal wherever this sheet sets it.
 * 3. UPPERCASE. text-transform: uppercase is a no-op on Thai, so the small-cap
 *    labels lose the one signal that made them read as labels. They keep their
 *    size and colour; only the tracking that no longer helps is dropped.
 */
/* ON BODY, NOT ON html. The first version of this set line-height on <html>
   and inherited nothing: `body` DECLARES line-height 1.55, and a declared
   value beats an inherited one whatever the specificity of the ancestor that
   set it. The 1.75 reached no text at all. */
html[lang="th"] body {
  line-height: 1.75;
}

/* Headings can be tighter than body copy but never as tight as Latin — and
   never tighter than what they already had. These inherited 1.55 before any
   Thai rule existed; an earlier draft of this block set 1.45 and so made the
   commonest heading in the app WORSE for the readers it was written for.
   1.6 is looser than the Latin default and tighter than the Thai body. */
html[lang="th"] h1,
html[lang="th"] h2,
html[lang="th"] h3,
html[lang="th"] h4,
html[lang="th"] .card-header {
  letter-spacing: normal;
  line-height: 1.6;
}

/* Prose that declares its own leading, and so never sees the body rule above.
   Only rules carrying running sentences are listed — a chip or a table cell
   holding two words gains nothing from more leading and would only get taller. */
html[lang="th"] .care-banner-text,
html[lang="th"] .onb-why,
html[lang="th"] .gauge-note,
html[lang="th"] .gauge-summary,
html[lang="th"] .field-note,
html[lang="th"] .profile-note,
html[lang="th"] .onb-note,
html[lang="th"] .grade-explainer p,
html[lang="th"] .balance-index-caption {
  line-height: 1.75;
}

/* The twelve rules in this sheet that set letter-spacing for Latin. Listed
   explicitly rather than reset with a wildcard: a wildcard would also flatten
   the Latin runs — source names, instrument acronyms, currency codes — that
   sit inside Thai copy and still want their tracking.
   Two selectors sharing a declaration both need listing: listing one left a
   chip tracked to a Latin eye once (tests/typography.test.mjs). */
html[lang="th"] .criterion-chip,
html[lang="th"] .prefill-chip,
html[lang="th"] .percentile-band,
html[lang="th"] .grade-badge,
html[lang="th"] .grade-none,
html[lang="th"] .grade-unranked,
html[lang="th"] .provenance-table thead th,
html[lang="th"] .aspect-grade-band,
html[lang="th"] .balance-band {
  letter-spacing: normal;
}

/* Reserve the scrollbar's column whether or not a scrollbar is present.
   Without this the centered .app-container shifts sideways by the scrollbar
   width every time a route swaps a tall view for a short one — the dashboard
   scrolls, the Weekly Review often does not — so the header, the tabs, and the
   whole card grid visibly jump on navigation. `stable` keeps the gutter
   allocated on the inline-end edge at all times. It belongs on <html>, the
   scrolling element; body already carries overflow-x: hidden, which would
   suppress the gutter rather than reserve it. */
html {
  scrollbar-gutter: stable;
}

/* Scrollbar — quiet neutral */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: #cfc9bd;
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: #b9b2a3;
}

/* The frame: every screen is full-bleed since redesign R6. #main-view is the
   container the stage pages measure --px against (css/stage-page.css). */
.app-container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  position: relative;
}
#main-view { container-type: inline-size; }

/* symbols.md S1: 32 px is the smallest size that keeps the engraved lines and
   the hole. width/height are also on the element, so nothing shifts while the
   sprite loads. */
.brand-star {
  width: 32px;
  height: 32px;
  flex: none;
}

/* Footer: privacy / source / version links. */
.app-footer {
  margin-top: auto;
  padding-top: 20px;
  border-top: 1px solid var(--color-card-border);
  display: flex;
  gap: 10px;
  /* The links below are 24px tall while the "·" separators are only as tall as
     their own text, so without this the dots hang from the top of the row. */
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

/* WCAG 2.2 SC 2.5.8 (Target Size, Minimum) asks for 24x24 CSS px. These links
   were a 19.8px line box — the height of their own text and nothing more — so
   all three failed on the dashboard, both failed on privacy.html, and they
   failed at DESKTOP width too: 2.5.8 governs pointer input generally, not just
   touch. Width was never the problem; the narrowest is "Methodology" at 79.8.

   min-height rather than vertical padding: padding would have to be recomputed
   the day --text-sm moves, whereas a floor stays a floor. It also reads as a
   floor in the source, which is the point of the rule.

   This is the AA criterion. SC 2.5.5 (Target Size) asks 44x44 and is AAA — a
   44px floor here would add 28px to the two-row phone footer, and this is
   secondary navigation sitting under a screen whose length was the whole
   subject of the two preceding releases. Left at AA deliberately. */
.app-footer a {
  color: var(--color-text-secondary);
  text-decoration: underline;
  text-underline-offset: 2px;
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

.app-footer a:hover {
  color: var(--color-navy);
}

/* The tab bar that stood here was replaced by the burger menu in the
   redesign's first release (css/frame.css, views/menu.js). */

/* Cards / panels */
.card {
  background: var(--color-card-bg);
  border: 1px solid var(--color-card-border);
  box-shadow: 0 1px 2px rgba(31, 39, 51, 0.03);
  border-radius: var(--radius);
  padding: 22px 24px;
  margin-bottom: 18px;
  position: relative;
}

.card-header {
  font-family: var(--font-serif);
  font-size: var(--text-xl);
  font-weight: 600;
  border-bottom: 1px solid var(--color-card-border);
  padding-bottom: 10px;
  margin-bottom: 18px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--color-navy);
}

/* Buttons */
.btn {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--text-md);
  padding: 9px 18px;
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--transition-speed) ease, border-color var(--transition-speed) ease, color var(--transition-speed) ease;
  border: 1px solid var(--color-card-border);
  background: #ffffff;
  color: var(--color-navy);
}

.btn:hover {
  background: #f2efe8;
  border-color: #cfc9bd;
}

.btn-primary {
  background: var(--color-navy);
  color: #fff;
  border-color: var(--color-navy);
}

.btn-primary:hover {
  background: #1a2739;
  border-color: #1a2739;
  color: #fff;
}

.btn-danger {
  color: var(--color-crimson);
  border-color: #e0cccc;
  background: #ffffff;
}
.btn-danger:hover {
  background: var(--color-crimson);
  color: #fff;
  border-color: var(--color-crimson);
}

/* Pressed state.
   Every button above had :hover and nothing else, which is a desktop-shaped
   assumption: a finger produces no hover, so on the app's primary surface the
   only confirmation that a tap landed was whatever the tap went on to do —
   and a slow render reads as a dead button, so people tap again. Same failure
   as the v51 level tooltip: an affordance delivered only through hover is
   not delivered at all on a phone.
   translateY(1px) rather than a colour shift, because the colour change has
   to survive :hover having already fired on desktop while never firing on
   touch; 1px of travel reads identically in both cases. transition is
   deliberately not extended to transform — the press must feel immediate,
   and easing it in would reintroduce the lag this is here to deny.
   Deliberately absent from the prefers-reduced-motion block further down:
   1px over 0ms is feedback, not decoration, and provokes no vestibular
   response. Suppressing it would withdraw the tap confirmation from exactly
   the readers most likely to need confirming. */
.btn:active {
  transform: translateY(1px);
}

/* Onboarding & Forms */

.form-group {
  margin-bottom: 16px;
}

.form-group label {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: 500;
  margin-bottom: 6px;
  color: var(--color-text-primary);
}

.form-control {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--color-input-border);
  border-radius: var(--radius);
  background: #ffffff;
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
}

.form-control:focus {
  outline: none;
  border-color: var(--color-navy);
  box-shadow: 0 0 0 3px var(--color-slate-glow);
  background: #ffffff;
}

/* Inline onboarding validation message (validation.js / doSubmit) */
.field-error {
  display: block;
  margin-top: 4px;
  color: var(--color-crimson);
  font-size: var(--text-sm);
  font-weight: 600;
}

/* Blank-first forms: the red "*" mandatory marker and invalid-control cues. */
.req {
  color: var(--color-crimson);
  font-weight: 700;
  margin-left: 1px;
}
.input-invalid {
  border-color: var(--color-crimson) !important;
}
.survey-question-invalid {
  border-color: var(--color-crimson);
}
.survey-question-invalid legend {
  color: var(--color-crimson);
}

.survey-question {
  background: #faf9f6;
  border: 1px solid var(--color-card-border);
  padding: 16px;
  border-radius: var(--radius);
  margin-bottom: 14px;
  min-inline-size: auto; /* fieldset shrink-wrap fix */
}

/* The container widened to 1184px so the option row fits on one line. The
   question text must not inherit that as its measure: a 1100px line is roughly
   170 characters, well past the ~75 where the eye starts losing its place on
   the return sweep. The option row still gets the full width; only the prose
   is capped. ch, not px, so the cap tracks the font rather than fighting it. */
.survey-question legend {
  font-weight: 500;
  font-size: var(--text-md);
  color: var(--color-text-primary);
  padding: 0 6px;
  max-width: 75ch;
}

.instrument-block {
  border-top: 1px solid var(--color-card-border);
  padding-top: 16px;
  margin-top: 20px;
}

.instrument-title {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: var(--text-lg);
  color: var(--color-navy);
  margin-bottom: 12px;
}

.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

@media (max-width: 600px) {
  .grid-2 {
    grid-template-columns: 1fr;
  }
}

/* The header's own controls are styled in css/frame.css. */

/* The Remove control on each pledge row. Its font-size and padding lived in a
   style="" attribute on the button (views/quests.js:36) — the same trap
   index.html documents for the header buttons: an inline declaration outranks
   every media query, so the mobile layer could never have reached it. Moved
   here for the same reason .btn-compact exists, and floored at 24px.

   Measured at 1440px: 68.1x21 (EN), 36.8x22 (TH) — under the 24px floor of
   WCAG 2.2 SC 2.5.8 (Target Size Minimum, AA) on HEIGHT only. Width passed in
   both languages; an earlier note claiming the Thai button also failed on
   width was wrong. Desktop-only, because the mobile layer floors .btn at 44px.

   inline-flex and centring rather than taller padding: padding would widen the
   button as well, and three of these sit in a column beside the pledge text
   where the extra width has nowhere to go. */
.pledge-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 24px;
  padding: 2px 10px;
  font-size: var(--text-xs);
}

.radio-group {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

.radio-option {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-md);
  cursor: pointer;
  color: var(--color-text-secondary);
  transition: color var(--transition-speed) ease;
}

.radio-option:hover {
  color: var(--color-text-primary);
}

.radio-option input[type="radio"] {
  accent-color: var(--color-navy);
}

/* Says, in plain sight, that the badge above is an age and the bar beside it is
   this year's points — two unrelated clocks. This was a title attribute until it
   was pointed out that a title is a hover affordance and the app is used mostly
   on a phone, so the reader who most needs the sentence never saw it. 0.75rem is
   the type floor the rest of the sheet holds to; the secondary colour keeps it a
   footnote to the name rather than a third figure competing on the card. */
.level-note {
  font-family: var(--font-serif);
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  margin-top: 4px;
}

/* Popups & dialogs */
.popup-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  background: rgba(31, 39, 51, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.popup-card {
  background: #ffffff;
  border: 1px solid var(--color-card-border);
  border-top: 3px solid var(--color-accent);
  border-radius: var(--radius);
  width: 90%;
  max-width: 440px;
  padding: 28px;
  box-shadow: 0 20px 50px rgba(31, 39, 51, 0.25);
  text-align: center;
}

.popup-title {
  font-family: var(--font-serif);
  font-size: var(--text-2xl);
  font-weight: 700;
  margin-bottom: 10px;
  color: var(--color-navy);
}

.aspect-title {
  font-family: var(--font-serif);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--color-navy);
}

.aspect-blurb {
  font-size: var(--text-base);
  color: var(--color-text-secondary);
  margin-top: 4px;
}

/* The non-goal statement on #/methodology. Set apart from the surrounding
   blurbs on purpose: it is the one paragraph that says what the eight numbers
   are NOT, and it must not read as fine print. */
.methodology-nongoal {
  color: var(--color-navy);
  border-left: 3px solid var(--color-navy);
  padding-left: 12px;
  margin-top: 14px;
  line-height: 1.55;
}

.aspect-score-badge {
  flex-shrink: 0;
  background: #faf9f6;
  border: 1px solid var(--color-card-border);
  border-radius: var(--radius);
  padding: 10px 18px;
  text-align: center;
}

/* The aspect header used to lead with the 0-100 score at --text-3xl and carry
   the grade as a --text-xs chip beside it. The score is this app's own
   composite; the grade is read off the CITED percentile (or, for finance, off
   the score with that stated in the card below). The sourced statement was the
   small one.
   Inverted: the letter is now the display glyph and the score is secondary
   beneath it. The letter keeps its band colours from .grade-a..f — it does not
   get a bigger version of the chip, because a chip that size reads as a badge
   to collect rather than as a measurement. */
.aspect-grade-glyph {
  display: block;
  font-family: var(--font-serif);
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: 1.05;
}
.aspect-grade-band {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: 6px;
}
.aspect-score-value {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--color-text-secondary);
}

.aspect-score-max {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

/* Percentile gauge */
.gauge-track {
  position: relative;
  height: 12px;
  border-radius: 6px;
  background: var(--color-track);
  border: 1px solid var(--color-card-border);
  overflow: visible;
  /* Bottom margin clears the .gauge-range bracket that hangs below the rail. */
  margin: 8px 0 16px;
}

/* The indicative range, drawn under the rail as a bracket. The range is the
   honest half of a percentile — it says how precise the estimate is — and in
   text form it was the smallest thing in the block.
   IT IS A SEPARATE BAR, NOT AN OVERLAY, and that is the whole design. The
   first version of this drew the band inside the track: .gauge-range is
   positioned and .gauge-fill is not, so per CSS painting order the band landed
   ON TOP of the fill and visually truncated it at range.low — a reader on an
   `estimate` benchmark saw their own bar cut ten percentile points short.
   Hanging it below the rail cannot occlude anything, needs no z-index, and
   shows the WHOLE range rather than only the part above the marker.
   .gauge-track already sets overflow: visible for the marker. */
.gauge-range {
  position: absolute;
  bottom: -8px;
  height: 3px;
  border-radius: 2px;
  background: var(--color-card-border);
}

.gauge-fill {
  height: 100%;
  border-radius: 6px;
  background: var(--color-slate);
}

.gauge-marker {
  position: absolute;
  top: -4px;
  width: 3px;
  height: 20px;
  border-radius: 2px;
  background: var(--color-accent);
  transform: translateX(-50%);
}


.gauge-summary {
  font-size: var(--text-base);
  color: var(--color-text-secondary);
  margin-top: 6px;
}

.gauge-note {
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  margin-top: 4px;
  padding-left: 10px;
  border-left: 2px solid var(--color-card-border);
}

/* Component breakdown rows */
.component-row {
  margin-bottom: 14px;
}

.component-head {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-base);
  font-weight: 500;
  margin-bottom: 2px;
}

.component-detail {
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  margin-top: 3px;
}

.benchmark-method {
  opacity: 0.8;
  font-style: italic;
}

.benchmark-sources {
  margin-top: 14px;
  border-top: 1px dashed var(--color-card-border);
  padding-top: 10px;
  font-size: var(--text-xs);
}

.benchmark-sources summary {
  cursor: pointer;
  color: var(--color-navy);
  font-weight: 600;
}
/* A fold's header is a thumb's 44px on a touchscreen (WCAG 2.5.5). */
@media (pointer: coarse) {
  .benchmark-sources summary { min-height: 44px; padding-block: 10px; box-sizing: border-box; }
}

.benchmark-sources ul {
  margin: 6px 0 0 18px;
  padding: 0;
}

/* 26px, not the 1.4 (16.8px) this used to be, so consecutive citation lines sit
   far enough apart for the 24px hit areas below to tile without overlapping.
   Applied to the line boxes that actually hold links — not to .benchmark-sources
   as a whole, which would also loosen .benchmark-disclaimer, a paragraph of
   prose that wants its 1.45. */
.benchmark-sources li,
.component-detail.benchmark-sources {
  line-height: 26px;
}

.benchmark-sources li {
  margin-bottom: 5px;
}

/* Citation links against WCAG 2.2 SC 2.5.8 (24x24). Measured at 1440px before
   this rule: 14 links on the methodology page at 15px tall, plus the dashboard's
   own source list once its <details> is opened. On a phone most of them wrap to
   two lines and clear 24 by accident, which is not the same as passing.

   These are NOT covered by the criterion's inline exception. That exemption is
   for a target "in a sentence, or whose size is constrained by the line-height
   of non-target text" — but citeLinks() emits nothing except links joined by
   " · ", and the dashboard list is one bare <a> per <li>. There is no sentence
   around them to be exempt inside of.

   padding-block, and NOT display:inline-block, which was considered and
   rejected: inline-block makes each citation an unbreakable box, and the widest
   label here measures 560px against a 375px phone. That trades a 9px shortfall
   for horizontal overflow. Vertical padding on an inline element expands the
   hit area — getBoundingClientRect and hit-testing both include it — while
   leaving line breaking exactly as it was, which is what a citation that has to
   wrap needs. 15px text box + 2x5 = 25px, inside a 26px line. */
.benchmark-sources a {
  color: var(--color-navy);
  word-break: break-word;
  padding-block: 5px;
}

/* Guideline checks (criterion-referenced card on aspect pages).
   Visually quieter than the grade badge on purpose: a guideline check is
   secondary to the cited population comparison, never a competing verdict. */
.criteria-caption {
  margin: 0 0 12px;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  line-height: 1.5;
}

.criteria-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.criterion-row {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 0;
  border-top: 1px solid var(--color-card-border);
}

.criterion-row:first-child {
  border-top: none;
}

.criterion-chip {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.criterion-chip-met {
  background: rgba(31, 122, 90, 0.14);
  color: #1f7a5a;
}

.criterion-chip-unmet {
  background: rgba(176, 106, 22, 0.14);
  color: #8a5312;
}

.criterion-chip-unmeasured {
  background: rgba(32, 50, 76, 0.09);
  color: var(--color-text-secondary);
}

.criterion-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.criterion-name {
  font-weight: 600;
  font-size: var(--text-base);
}

.criterion-detail {
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  line-height: 1.45;
}

/* Narrow screens: the chip drops above the text rather than squeezing it. */
@media (max-width: 420px) {
  .criterion-row {
    flex-direction: column;
    gap: 5px;
  }
}

/* Duty-of-care mental-health support banner (finding #4) */
.care-banner {
  border: 1px solid #e2ded7;
  border-left: 3px solid var(--color-navy);
  background: #f3f1ed;
  border-radius: var(--radius);
  padding: 14px 18px;
  margin-bottom: 18px;
}
.care-banner-title {
  font-family: var(--font-serif);
  font-weight: 700;
  color: var(--color-navy);
  margin-bottom: 4px;
}
.care-banner-text {
  font-size: var(--text-base);
  color: var(--color-text-primary);
  line-height: 1.45;
  margin-bottom: 10px;
}
.care-resources {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.care-resources li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: var(--text-base);
  color: var(--color-text-secondary);
}
.care-resource-tel {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--color-navy);
  text-decoration: none;
  white-space: nowrap;
}
.care-resource-tel:hover { text-decoration: underline; }

.sbs-mark { margin-left: 5px; font-size: var(--text-2xs); }

.friend-remove {
  background: none;
  border: none;
  color: var(--color-crimson);
  font-size: var(--text-sm);
  cursor: pointer;
  margin-left: 6px;
  padding: 2px 4px;
  vertical-align: middle;
}

.friend-remove:hover {
  color: var(--color-crimson-light);
}

/* Helper text colors */
.text-gold { color: var(--color-accent); }

/* Keyboard focus ring.
 *
 * :focus-visible, not :focus — a mouse click should not leave a ring, but Tab
 * always must.
 *
 * outline-offset is the load-bearing line, NOT the colour. This comment used to
 * claim gold was chosen because "a navy ring vanishes on .btn-primary"; gold on
 * navy is 1.26:1, so it would vanish just as completely. What saves it is the
 * 2px offset, which draws the ring entirely outside the element on the paper
 * background — 9.12:1 for gold, and navy would have been 12.55:1 there. Either
 * colour works. Drop the offset to 0 and every filled navy control loses its
 * focus indicator at once, so change that number only with a contrast check.
 *
 * .form-control is untouched: it already replaces the outline with its own
 * border + glow, and its class+pseudo specificity wins here. */
:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 2px;
}

.d-none { display: none !important; }

/* Skip link (WCAG 2.4.1). Seven controls sit before <main> on every route, so
 * a keyboard user re-tabs the whole header on each navigation without one.
 * Parked off-screen with `top` rather than `.sr-only`, because it has to become
 * a real, visible, hit-testable target the moment it takes focus. No transition
 * on `top`: a skip link that slides is a skip link you can miss. */
.skip-link {
  position: absolute;
  top: -60px;
  left: 8px;
  z-index: 1300;
  padding: 11px 16px;
  background: var(--color-navy);
  color: #ffffff;
  border-radius: var(--radius);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
}
.skip-link:focus { top: 8px; }

/* Visually hidden but available to screen readers (finding #12). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- Resumed draft notice (onboarding + check-in) --- */
/* Deliberately quiet. It reports something good that already happened, so it
   is an information strip, not a warning: the eye should land on the question
   the user is being asked, not on this. .btn-sm is defined here rather than as
   a global utility because this is the only small button in the app. */
.onb-resume {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-bottom: 18px;
  border-radius: 8px;
  background: var(--color-track);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
}
.onb-resume .btn-sm {
  padding: 4px 12px;
  font-size: var(--text-sm);
  min-height: 44px; /* touch target stays WCAG-sized even though the text is small */
}

.onb-why {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.45;
  color: var(--color-text-secondary);
  margin: -6px 0 18px;
}
/* Profile & Data page (#/profile) */
.profile-view {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.profile-note,
.onb-note {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--color-text-secondary);
  margin: -6px 0 14px;
}
.profile-data-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* Connected apps — one row per sibling app on this origin. */
.conn-row {
  border: 1px solid var(--color-card-border);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 10px;
}
.conn-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.conn-id {
  display: flex;
  flex-direction: column;
}
.conn-name {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
}
.conn-role,
.conn-fills {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
}
.conn-switch {
  display: flex;
  align-items: center;
  gap: 6px;
  /* 44px keeps the toggle a comfortable tap target on a phone, the same floor
     the rest of the mobile layout was held to. */
  min-height: 44px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.conn-switch input {
  width: 18px;
  height: 18px;
  cursor: pointer;
}
.conn-status {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.45;
  margin: 6px 0 4px;
}
.conn-fills {
  margin: 0;
}

/* Weekly Review: a box filled in by a connected app. The chip sits in the
   label so the source is named before the number is read, and the caption
   under the input says which dates it covers. */
.prefill-chip {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-card-bg);
  background: var(--color-navy);
  border-radius: 999px;
  padding: 2px 8px;
  vertical-align: middle;
}
.field-note {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--color-text-secondary);
  margin: 4px 0 0;
}
.conn-banner {
  border-left: 3px solid var(--color-navy);
  padding: 4px 0 4px 12px;
  margin-bottom: 14px;
}
.conn-banner p {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-secondary);
  margin: 0 0 6px;
}
.conn-banner p:last-child {
  margin-bottom: 0;
}

.onb-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
}
.onb-nav-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* --- Dashboard: quick-start (express baseline) note --- */
.quickstart-note {
  background: #faf9f6;
  border: 1px solid var(--color-card-border);
  border-left: 3px solid var(--color-navy);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 18px;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.45;
  color: var(--color-text-secondary);
}
.quickstart-note strong { color: var(--color-navy); }

/* Completeness note (Phase 2b): estimated-score cue, amber-flagged. */
.completeness-note { border-left-color: var(--color-accent); }
.completeness-note strong { color: var(--color-accent); }
.completeness-note a { color: var(--color-accent); font-weight: 600; }

/* --- Friendlier percentile presentation --- */
.benchmark-plain-lead {
  font-weight: 600;
  color: var(--color-navy);
}
/* This sentence is prose, so it sets its own font rather than inheriting the
   monospace stack from .benchmark-line — monospace is both wrong for prose and
   ~20% wider, which is what pushed the band chip onto its own line (the widest
   chip, "Around average", overflowed the 429px card by exactly 1px). Sans at
   0.86rem leaves ~56px of slack, and also restores the hierarchy: at 0.95rem
   this secondary line was larger than the aspect name above it (0.85rem).

   Inline flow, not flex: as a flex row the sentence is one unbounded item, so
   the chip is the only thing that can wrap and it drops to a line by itself.
   Inline lets the sentence wrap mid-phrase and the chip trail the last line —
   which is what keeps this readable on a phone, where sentence + chip cannot
   share one line at any legible size. */
.benchmark-plain-lead {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.45;
  margin-bottom: 4px;
}
.benchmark-plain-lead .percentile-band {
  margin-left: 6px;
}
/* Matches .benchmark-line, which now sets 0.75rem for this whole block. This
   was held at 0.72rem because 0.75rem made the longest detail
   ("… (vs participation rates)") overshoot the card by 6px; the 12px floor is
   worth more than the saved line, and overshoot is no longer the outcome —
   the nowrap below keeps the parenthetical whole, so a long percentile now
   wraps cleanly inside the card instead of spilling past its edge. Kept as a
   rule rather than deleted because the color still differs from the parent. */
.benchmark-detail {
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
}
/* Keep the method tag whole: "(vs participation rates)" was breaking after
   "participation", leaving an orphaned "rates)". */
.benchmark-detail .benchmark-method {
  white-space: nowrap;
}
.benchmark-verified { color: var(--color-navy); font-weight: 700; }
.percentile-definition {
  margin-top: 6px;
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  line-height: 1.4;
}

/* Band chip: a coarse plain-language label next to the number. nowrap so a
   two-word band ("Around average") can never split, and tight tracking because
   it is a label, not something to read word by word. The size was trimmed to
   0.6rem to keep it on the sentence's line; at 9.6px it was the smallest text
   in the app, and "Bottom 25%" is a verdict about the reader — too important
   to be the hardest thing on the page to read. Back to the 12px floor; the
   nowrap still stops it splitting, it just takes the line it needs. */
.percentile-band {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid currentColor;
  line-height: 1.5;
  white-space: nowrap;
}
.band-top10, .band-top25 { color: var(--color-success); }
.band-above { color: var(--color-accent); }
.band-around { color: var(--color-text-secondary); }
.band-below, .band-bottom { color: var(--color-accent-light); }

/* --- Letter grades (Phase L1) ---
   Same chip geometry as the confidence and percentile chips, one step larger
   because a single letter needs the weight to read at a glance.
   Only A is filled: a filled F would shout, and the app's duty-of-care stance
   is that a low grade is information, not an alarm. C is deliberately neutral
   ink rather than a warning color — the C band is the typical middle, where
   most people honestly are. */
.grade-badge {
  display: inline-block;
  vertical-align: middle;
  min-width: 1.55rem;
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid currentColor;
  line-height: 1.5;
}
.grade-a {
  color: #fff;
  background: var(--color-success);
  border-color: var(--color-success);
}
/* The glyph is not a chip: no fill, no border, no pill. A filled 30px block of
   green would read as a trophy, which is the one thing a population comparison
   must not look like. */
.aspect-grade-glyph.grade-a {
  color: var(--color-success);
  background: none;
  border: none;
}
.grade-b { color: var(--color-success); }
.grade-c { color: var(--color-text-secondary); }
.grade-d { color: var(--color-accent); }
.grade-f { color: var(--color-crimson); }
.grade-none {
  color: var(--color-text-secondary);
  border-style: dashed;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
/* "Not ranked" is a deliberate statement, not missing data, so it reads as a
   solid chip rather than the dashed "not filled in yet" one. */
.grade-unranked {
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.benchmark-unranked-lead {
  color: var(--color-text-secondary);
  font-style: italic;
}

/* Comparison-provenance table (methodology page). Scrolls inside its own box on
   narrow screens so the page body never scrolls sideways. */
.provenance-scroll {
  overflow-x: auto;
  margin: 12px 0;
}
.provenance-table {
  border-collapse: collapse;
  width: 100%;
  min-width: 520px;
  font-size: var(--text-sm);
}
.provenance-table th,
.provenance-table td {
  text-align: left;
  vertical-align: top;
  padding: 7px 10px;
  border-bottom: 1px solid var(--color-card-border);
}
.provenance-table thead th {
  font-weight: 700;
  color: var(--color-text-secondary);
  text-transform: uppercase;
  font-size: var(--text-2xs);
  letter-spacing: 0.04em;
}
.provenance-table tbody th {
  font-weight: 600;
  white-space: nowrap;
}
.provenance-table td {
  color: var(--color-text-secondary);
}
.provenance-unranked th,
.provenance-unranked td {
  color: var(--color-accent);
}

.grade-explainer p {
  font-size: var(--text-base);
  color: var(--color-text-primary);
  margin-bottom: 6px;
}
.grade-explainer p:last-child { margin-bottom: 0; }
.grade-explainer-note {
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

/* --- Balance Index (Phase L1) --- */
.balance-index {
  display: flex;
  align-items: center;
  gap: 18px;
}
.balance-index-figure {
  flex-shrink: 0;
  display: flex;
  align-items: baseline;
  gap: 2px;
}
/* 2.6rem — 41.6px, the top step of the scale, and the largest type anywhere
   in this app — used to belong to this number. It is the ONE figure on the
   page that no published source stands behind: the app's own harmonic mean,
   captioned two lines below as "this app's own summary figure, not a published
   measure". The cited percentiles it is built from were set at --text-xs, 12px,
   grey. The type scale was making the opposite claim to the copy.
   Demoted BELOW the letter grade on the aspect page, which is the sourced
   statement. --text-4xl had no other user and is gone with it. */
.balance-index-value {
  font-family: var(--font-serif);
  font-size: var(--text-2xl);
  font-weight: 700;
  line-height: 1;
  color: var(--color-navy);
}
.balance-index-max {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  color: var(--color-text-secondary);
}
.balance-index-body { min-width: 0; }
.balance-index-title {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-navy);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 4px;
}
/* The one sentence the app exists to say. Sized between the title (1rem) and
   the methodology caption (0.76rem) so the reading order is: what the number
   is -> what it means about you -> how it was built. Navy, not gold: this is a
   statement of fact, not a call to action — the gold line below it is the
   action. */
.balance-index-standing {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-navy);
  line-height: 1.4;
  margin-bottom: 6px;
}
.balance-index-caption {
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  line-height: 1.45;
}
.balance-index-weakest {
  font-size: var(--text-sm);
  color: var(--color-accent);
  font-weight: 600;
  margin-top: 4px;
}
.balance-band {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid currentColor;
  line-height: 1.5;
}
.band-strong { color: var(--color-success); }
.band-steady { color: var(--color-accent); }
.band-uneven { color: var(--color-accent-light); }
.band-strained { color: var(--color-crimson); }

/* Stack the index on narrow screens so the caption keeps a readable measure. */
@media (max-width: 480px) {
  .balance-index {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
}

/* --- Deep (in-depth) assessment --- */

.deep-progress { font-size: var(--text-sm); color: var(--color-text-secondary); margin-top: 4px; }

.deep-done-note {
  font-size: var(--text-sm);
  color: var(--color-navy);
  font-weight: 600;
  margin-bottom: 8px;
}
/* Explains why a full-length scale shows fewer questions than its name implies:
   the shared items were already answered at the baseline assessment. */
.carry-note {
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  margin: -6px 0 12px;
}

/* ==========================================================================
   MOBILE LAYER — phones (max-width: 640px)
   ==========================================================================
   Everything below is scoped to small screens on purpose: the desktop layout
   is deliberately unchanged by the v45 mobile redesign, so no rule here may
   leak upward. 640px is the consolidated phone breakpoint; the 768px tablet
   grid collapse above still stands.

   Written against measurements taken at 375x812 with real data in both
   languages. The defects it answers:
     - banner prose squeezed into a 98px column by justify-content: space-between
     - 917px of prompts before any content; the radar 1800px down
     - static navigation on a 5.6-screen page
     - every form field at 15.2px, which makes iOS Safari zoom on focus
     - tap targets at 19-37px against a 44px standard
     - text down to 9.6px
   ========================================================================== */
@media (max-width: 640px) {

  .card {
    padding: 16px;
    margin-bottom: 12px;
  }

  .card-header {
    font-size: var(--text-lg);
    padding-bottom: 8px;
    margin-bottom: 12px;
  }

  /* --- Forms -------------------------------------------------------------
     THE iOS ZOOM FIX. Safari zooms the viewport whenever a focused field is
     under 16px, and every one of the app's 30 fields was 15.2px — so each tap
     zoomed the page and the reader had to pinch back out. 16px is a hard
     threshold, not a preference: 15.9px still zooms.
     This is the one font-size in the sheet that is deliberately NOT a
     --text-* token. It is a behavioural guard against a Safari rule, so it
     must stay pinned to 16px even if the type scale is later retuned. */
  .form-control,
  input[type="text"],
  input[type="number"],
  input[type="date"],
  input[type="file"],
  select,
  textarea {
    font-size: 16px;
    padding: 11px 12px;
    min-height: 44px;
  }

  .form-group label { font-size: var(--text-base); }
  .form-group { margin-bottom: 14px; }

  /* --- Tap targets -------------------------------------------------------
     Measured before this release: header buttons 25px, inline links 19px,
     tabs 37px. */
  /* inline-flex is load-bearing, not cosmetic: `.btn` sets no display, so an
     <a class="btn"> computes to `display: inline` — and min-height does not
     apply to a non-replaced inline box. The anchor buttons stayed 40px while
     the <button>s obeyed, until this line. */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 11px 18px;
  }

  /* .btn's 11px 18px would now reach the pledge Remove control and widen it —
     it could not before, because that padding was an inline style no media
     query could outrank. Restated here so extracting the inline style changes
     nothing on the phone: the button still reaches 44px through min-height
     alone, exactly as it did. Verified unchanged at 68.1x44 (EN), 36.8x44 (TH). */
  .pledge-remove {
    padding: 2px 10px;
  }

  .radio-option {
    min-height: 44px;
    font-size: var(--text-md);
  }

  .radio-option input[type="radio"] {
    width: 20px;
    height: 20px;
  }

  /* --- Type floor --------------------------------------------------------
     Nothing below 12px on a phone. These are the classes that measured
     9.6-11.5px; the mobile e2e flow guards against new ones appearing. */
  .percentile-band,
.balance-band,
.grade-badge,
.benchmark-detail,
.deep-progress,
.balance-index-caption,
.balance-index-weakest,
.component-detail,
.criterion-chip,
.gauge-note,
.percentile-definition {
    font-size: var(--text-sm);
  }

  /* The provenance tables already scroll inside their card, so making their
     text legible costs width that was never on screen anyway. `thead th` is
     spelled out because the base rule for it carries specificity (0,1,2) and
     would otherwise keep its 0.68rem. */
  .provenance-table th,
  .provenance-table td,
  .provenance-table thead th {
    font-size: var(--text-xs);
  }

  .app-footer { font-size: var(--text-sm); }

  /* --- Prompt stack ------------------------------------------------------
     Two separate fixes.

     First: these banners were display:flex with justify-content:space-between
     and no mobile rule, so the button took its natural width and the prose was
     left a 98px column — about twelve characters a line, which is what turned
     short sentences into 345px-tall blocks. Stacking ends that.

     Second: only the FIRST prompt is the next step. The rest demote to a
     compact title-and-action row. Done in CSS rather than JS so that rotating
     the phone cannot leave a stale layout behind. */
  

  

  

  

  

  

  

  

  

  

  

  
}
