/* ============================================================================
   np-text-size.css — Viewer-selectable text size (WCAG 2.2 SC 1.4.8 Visual
   Presentation, Level AAA) + the shared segmented control that drives it.

   PRODUCT CAPABILITY, not a pre-assessment feature. The scale mechanism below
   is host-agnostic: any surface that stamps `data-np-text-size` on <html> and
   links this file gets the scale. The public pre-assessment wizard is simply
   the first consumer (CLAUDE.md §3.14 — build reusable, roll out narrowly).

   ── How the scale works ─────────────────────────────────────────────────────
   The root font-size is set as a PERCENTAGE of the browser's own default, never
   as an absolute pixel value:

       html[data-np-text-size="lg"] { font-size: 125%; }

   Because it is relative, the control COMPOSES with browser zoom (Ctrl +) and
   with the user's OS/browser text-size setting instead of overriding them. A
   hard `font-size: 20px` would silently discard a preference the user had
   already set at platform level — the single most common way this feature is
   built wrong. Everything sized in `rem` then scales for free.

   ── What deliberately does NOT scale ────────────────────────────────────────
   The --np-space-* tokens are px and stay px. If structural padding scaled 1:1
   with text, the "Largest" preset would push the primary action below the fold
   on a phone — a worse outcome than small text. What DOES scale is the
   *typographic* rhythm: line-height, paragraph gaps, and label→input spacing.
   That split is what keeps Largest readable rather than merely big.

   ── SC 1.4.8 clause coverage ────────────────────────────────────────────────
     (1) foreground/background selectable ......... Phase 3 (contrast control)
     (2) line length <= 80 characters ............. §"Measure" below
     (3) text not justified ....................... §"Measure" below
     (4) line spacing >=1.5, paragraph >=2.25 ..... §"Vertical rhythm" below
     (5) resize 200% w/o horizontal scrolling ..... §"Reflow" below + presets
   ========================================================================== */

/* ---------------------------------------------------------------------------
   1. The scale itself.
   Applied to <html> server-side by the layout, before first paint. Never by JS
   on DOMContentLoaded — that produces a flash of small text on every page load,
   which for a low-vision user is a page they briefly cannot read.

   --np-text-scale is published as a custom property so component CSS can opt
   individual values into the scale (see "Vertical rhythm") without every
   stylesheet having to know the preset vocabulary.
   --------------------------------------------------------------------------- */
html {
    --np-text-scale: 1;
}

html[data-np-text-size="lg"] {
    --np-text-scale: 1.25;
    font-size: 125%;
}

html[data-np-text-size="xl"] {
    --np-text-scale: 1.5;
    font-size: 150%;
}

/* Explicit so an unknown/absent token is unambiguous rather than inherited. */
html[data-np-text-size="std"] {
    --np-text-scale: 1;
    font-size: 100%;
}

/* Print: paper has its own size contract and the reader is not the viewer who
   set the preference. Always render print output at the standard scale. */
@media print {
    html[data-np-text-size] {
        --np-text-scale: 1;
        font-size: 100%;
    }

    .np-text-size {
        display: none !important;
    }
}

/* ---------------------------------------------------------------------------
   2. Vertical rhythm — SC 1.4.8 clauses (4).
   Line spacing at least 1.5x within a paragraph, and space between paragraphs
   at least 1.5x the line spacing (i.e. >= 2.25x the font size). These are
   floors, applied only on the enlarged presets so the Standard preset keeps
   the designed rhythm.
   --------------------------------------------------------------------------- */
html[data-np-text-size="lg"] body,
html[data-np-text-size="xl"] body {
    line-height: 1.6;
}

html[data-np-text-size="lg"] p,
html[data-np-text-size="xl"] p,
html[data-np-text-size="lg"] li,
html[data-np-text-size="xl"] li {
    line-height: 1.6;
}

html[data-np-text-size="lg"] p,
html[data-np-text-size="xl"] p {
    /* 1.6 line-height x 1.5 = 2.4em, clearing the 2.25x floor. */
    margin-bottom: 2.4em;
}

/* Headings keep a tighter measure but must not collide once enlarged. */
html[data-np-text-size="lg"] :is(h1, h2, h3, h4, h5, h6),
html[data-np-text-size="xl"] :is(h1, h2, h3, h4, h5, h6) {
    line-height: 1.3;
}

/* Form labels sit closer to their control than body copy does, but the gap
   still has to grow or the label visually merges with the field above it. */
html[data-np-text-size="lg"] :is(.np-label, .npf-form label, .np-pre label),
html[data-np-text-size="xl"] :is(.np-label, .npf-form label, .np-pre label) {
    line-height: 1.5;
}

/* ---------------------------------------------------------------------------
   3. Measure and justification — SC 1.4.8 clauses (2) and (3).
   80 characters is the cap. `ch` is the width of "0" in the current font, so
   this floats correctly with the scale and needs no per-preset arithmetic.
   --------------------------------------------------------------------------- */
html[data-np-text-size="lg"] .npf-form :is(p, li, .np-hint, .safety-note),
html[data-np-text-size="xl"] .npf-form :is(p, li, .np-hint, .safety-note) {
    max-width: 80ch;
}

/* Justified text creates rivers of white space that are actively harmful to
   dyslexic readers. Enforced product-wide, not just on the enlarged presets. */
.npf-form :is(p, li, td, .np-hint),
.np-pre :is(p, li, td, .np-hint) {
    text-align: left;
    text-justify: none;
}

/* ---------------------------------------------------------------------------
   4. Reflow — SC 1.4.8 clause (5) and SC 1.4.10.
   At Largest, multi-column layouts must collapse to a single column regardless
   of viewport width. This is the reason 1.5 was chosen as the top preset: it
   gives one clean, testable breakpoint rather than a spectrum of half-broken
   intermediate states.
   --------------------------------------------------------------------------- */
html[data-np-text-size="xl"] :is(.np-form-grid, .np-form-grid--2, .np-form-grid--3, .np-form-grid--4) {
    grid-template-columns: 1fr;
}

/* At Large, three- and four-column sections drop to two; two-column stays. */
html[data-np-text-size="lg"] :is(.np-form-grid--3, .np-form-grid--4) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Nothing may clip or ellipsis away once enlarged — SC 1.4.4 forbids loss of
   content. Wrapping is always preferable to truncation here. */
html[data-np-text-size="lg"] .npf-form,
html[data-np-text-size="xl"] .npf-form,
html[data-np-text-size="lg"] .np-pre,
html[data-np-text-size="xl"] .np-pre {
    overflow-wrap: break-word;
}

html[data-np-text-size="lg"] :is(.npf-form, .np-pre) :is(.np-truncate, [class*="truncate"]),
html[data-np-text-size="xl"] :is(.npf-form, .np-pre) :is(.np-truncate, [class*="truncate"]) {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

/* Wide content that genuinely cannot reflow (data tables) scrolls inside its
   own container so the PAGE never scrolls horizontally. */
html[data-np-text-size="lg"] :is(.npf-form, .np-pre) .np-table-wrap,
html[data-np-text-size="xl"] :is(.npf-form, .np-pre) .np-table-wrap {
    overflow-x: auto;
}

/* ---------------------------------------------------------------------------
   5. Target size — SC 2.5.5 Target Size (AAA).
   A user who needs larger text is very often the same user with reduced motor
   precision on a phone. Growing the text without growing the hit area solves
   the wrong half of the problem.
   --------------------------------------------------------------------------- */
html[data-np-text-size="xl"] :is(.npf-form, .np-pre) :is(input[type="radio"], input[type="checkbox"]) {
    width: 1.5rem;
    height: 1.5rem;
}

html[data-np-text-size="xl"] :is(.npf-form, .np-pre) :is(button, .btn, .np-btn, [role="button"]) {
    min-height: 44px;
}

/* ============================================================================
   6. The control — `np-text-size`.

   A segmented RADIO GROUP, not a stepper and not a toggle:
     • A +/- stepper hides the current state and the boundaries — the user
       cannot tell whether pressing "+" again will do anything.
     • A two-state toggle cannot express three presets.
     • A segmented control shows all three options AND the active one at once.
       For a cognitively-loaded user that is decisive.

   "Off" is simply selecting Standard. There is deliberately NO separate
   disable switch — that would be a fourth state and a second mental model.

   Markup is native <input type="radio"> in <label> (CLAUDE.md §3.11:
   native-first; never a bare <div> click target). The browser therefore gives
   us radiogroup semantics, arrow-key roving and form-post fallback for free.

   Each option renders "Aa" AT ITS OWN SCALE, so the control previews itself
   and the active state is carried by size + fill + check glyph — never by
   colour alone (§6.6, WCAG 1.4.1).
   ========================================================================== */
.np-text-size {
    display: flex;
    align-items: center;
    gap: 8px;
    border: 0;
    margin: 0;
    padding: 0;
    min-width: 0;
}

/* <legend> is the accessible group name. Visible by default because
   discoverability is the whole point of this control existing. */
.np-text-size__legend {
    float: none;
    width: auto;
    margin: 0;
    padding: 0;
    font-size: 0.75rem;
    font-weight: 400;
    line-height: 1.2;
    letter-spacing: 0.01em;
    color: var(--npf-secondary, var(--np-color-text-secondary, #866489));
    white-space: nowrap;
}

.np-text-size--compact .np-text-size__legend {
    /* Visually hidden, still announced. Used where the header is tight. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.np-text-size__options {
    display: flex;
    align-items: stretch;
    gap: 2px;
    padding: 2px;
    background: var(--npf-tint-50, var(--np-color-brand-tint-50, #FBF7FC));
    border: 1px solid var(--npf-input-border, var(--np-color-border, #D8C9DE));
    border-radius: 999px;
}

.np-text-size__option {
    position: relative;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 4px;
    /* 44px min target (SC 2.5.5) even at the Standard preset — the control
       itself must never be the thing that is hard to hit. */
    min-width: 44px;
    min-height: 36px;
    padding: 4px 12px;
    border-radius: 999px;
    cursor: pointer;
    color: var(--npf-secondary, var(--np-color-text-secondary, #866489));
    background: transparent;
    transition: background 150ms ease, color 150ms ease;
    /* The control is fixed-size: it must NOT scale with its own preset, or
       pressing "Largest" makes the control jump and the pointer misses. */
    font-size: 1rem;
}

html[data-np-text-size] .np-text-size__option {
    font-size: calc(1rem / var(--np-text-scale, 1));
}

/* The native radio is the source of truth; it is visually removed but stays
   in the accessibility tree and keeps focus, arrow keys and form posting. */
.np-text-size__input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    margin: 0;
    pointer-events: none;
}

/* "Aa" preview, rendered at the size the option represents. This IS the
   non-colour signal that distinguishes the options from each other. */
.np-text-size__sample {
    font-weight: 400;
    line-height: 1;
}

.np-text-size__option[data-size="std"] .np-text-size__sample { font-size: 0.8125em; }
.np-text-size__option[data-size="lg"]  .np-text-size__sample { font-size: 1em; }
.np-text-size__option[data-size="xl"]  .np-text-size__sample { font-size: 1.25em; }

.np-text-size__text {
    font-size: 0.75em;
    font-weight: 400;
    line-height: 1;
    white-space: nowrap;
}

.np-text-size__option:hover {
    background: var(--npf-tint-100, var(--np-color-brand-tint-100, #F7F0F8));
    color: var(--npf-primary, var(--np-color-brand-primary, #741F7B));
}

/* Active state: filled pill + weight + the size ramp above. Three redundant
   signals, only one of which is colour. */
.np-text-size__input:checked + .np-text-size__body {
    background: var(--npf-primary, var(--np-color-brand-primary, #741F7B));
    color: #FFFFFF;
    font-weight: 400;
}

.np-text-size__body {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 4px;
    width: 100%;
    border-radius: 999px;
    padding: 6px 10px;
    transition: background 150ms ease, color 150ms ease;
}

/* Focus lands on the visually-hidden input, so the ring is drawn on the body.
   :focus-visible only — a mouse click must not leave a ring behind (§3.11). */
.np-text-size__input:focus-visible + .np-text-size__body {
    outline: 3px solid var(--npf-primary, var(--np-color-brand-primary, #741F7B));
    outline-offset: 2px;
}

/* Windows High Contrast / forced-colours: system colours replace ours, so the
   checked state needs a border to survive. */
@media (forced-colors: active) {
    .np-text-size__input:checked + .np-text-size__body {
        border: 2px solid Highlight;
        forced-color-adjust: none;
        background: Highlight;
        color: HighlightText;
    }

    .np-text-size__input:focus-visible + .np-text-size__body {
        outline: 3px solid CanvasText;
    }
}

@media (prefers-reduced-motion: reduce) {
    .np-text-size__option,
    .np-text-size__body {
        transition: none;
    }
}

/* No-JS: the fallback Apply button is the only way to commit the choice.
   np-boot.js sets `.np-js` on <html> before first paint, so this hides for
   the ~100% of users who have JS without a flash of the button. */
.np-text-size__apply {
    padding: 6px 12px;
    min-height: 36px;
    border-radius: 999px;
    border: 1px solid var(--npf-primary, var(--np-color-brand-primary, #741F7B));
    background: transparent;
    color: var(--npf-primary, var(--np-color-brand-primary, #741F7B));
    font-size: 0.75rem;
    font-weight: 400;
    cursor: pointer;
}

.np-js .np-text-size__apply {
    display: none;
}

/* Narrow viewports: drop the word labels, keep the "Aa" ramp. The legend still
   names the group for assistive tech, and the ramp still distinguishes the
   options non-chromatically. */
@media (max-width: 32rem) {
    .np-text-size__legend {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
    }

    .np-text-size__text {
        display: none;
    }

    .np-text-size__option {
        min-width: 44px;
        padding: 4px 6px;
    }
}
