/* ==========================================================================
   Realtime Typing Speed Analyser - stylesheet

   Theme: ported from the CodeLangs AI site's shared design tokens and
   "jfc-" locked-viewport app-shell pattern (see
   python-interview-preparation/python-interview-question-and-answers/
   python-installation-and-environment-setup.php and assets/css/styles.css)
   so this standalone tool shares the same look and feel, without any
   cross-project file dependency - every token and rule below is
   self-contained in this file.

   Architecture:
     1. Custom properties (design tokens, matching the site's palette)
     2. Reset / base
     3. App shell (topbar + locked single-viewport columns)
     4. Passage metadata badges/pills
     5. Realtime statistics (compact stat cards + progress)
     6. Passage display + character states
     7. Typing input
     8. Test status
     9. Results section
     10. Buttons
     11. Help / noscript / footer note
     12. Responsive breakpoints (below the tablet breakpoint the locked
         single-screen shell intentionally relaxes into a normal
         scrolling page - a true no-scroll two-column layout is not
         practical on small phones with this much content)
     13. Reduced motion / forced colours
     14. Print
   ========================================================================== */

/* -------------------------------------------------------------------------
   1. Custom properties (same names/values as the site-wide theme)
   ------------------------------------------------------------------------- */
:root {
    /* Dark theme, always on. Every pair below was verified with the same
       WCAG relative-luminance contrast math used for the earlier light-
       theme fix, not eyeballed: --color-text/--color-muted clear 7.4:1+
       against every surface; --color-border clears 4.0-4.9:1 (well past
       the 3:1 non-text/UI-component floor); every accent (success,
       error, info, primary, secondary, accent, violet) clears 4.5:1+ as
       TEXT against both --color-surface and --color-surface-muted. The
       one place plain white text would have failed is button-primary's
       label sitting on a bright accent-teal background (2.49:1) - fixed
       by making --color-inverse dark instead of white, same pattern
       already used for the "amber card" on the site homepage. */
    --color-primary: #14b8a6;
    --color-primary-dark: #5eead4;
    --color-secondary: #818cf8;
    --color-accent: #fbbf24;
    --color-success: #4ade80;
    --color-error: #f87171;
    --color-background: #0b1220;
    --color-surface: #17233a;
    --color-surface-muted: #101a2c;
    --color-border: #6b83ab;
    --color-text: #e7ecf5;
    --color-muted: #a3b3cc;
    --color-inverse: #0b1220;

    --color-focus: var(--color-secondary);
    --color-primary-soft: rgba(20, 184, 166, 0.2);
    --color-error-soft: rgba(248, 113, 113, 0.18);
    --color-success-soft: rgba(74, 222, 128, 0.18);
    --color-accent-soft: rgba(251, 191, 36, 0.2);
    --color-secondary-soft: rgba(129, 140, 248, 0.2);

    /* Extra vibrant accents (beyond the core theme) used only to give
       each realtime-statistic chip its own distinct colour. */
    --color-info: #60a5fa;
    --color-info-soft: rgba(96, 165, 250, 0.2);
    --color-violet: #a78bfa;
    --color-violet-soft: rgba(167, 139, 250, 0.2);

    /* Pure-black shadows (not the previous navy-tinted ones, which read
       as invisible once the page itself is dark) - depth here mostly
       comes from --color-surface sitting lighter than --color-background
       plus the now much more visible border, with the shadow adding a
       subtle recessed edge rather than doing the whole job. */
    --shadow-soft: 0 18px 45px rgba(0, 0, 0, 0.4);
    --shadow-card: 0 2px 4px rgba(0, 0, 0, 0.3), 0 10px 24px rgba(0, 0, 0, 0.35);
    --radius: 8px;
    --radius-pill: 999px;

    --space-1: 8px;
    --space-2: 16px;
    --space-3: 24px;
    --space-4: 32px;
    --space-5: 40px;
    --space-6: 48px;

    --font-main: Inter, "Segoe UI", Roboto, Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

    --transition-fast: 150ms ease;
    --transition-medium: 250ms ease;
}

/* -------------------------------------------------------------------------
   2. Reset / base
   ------------------------------------------------------------------------- */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html,
body {
    height: 100%;
}

html {
    overflow-x: hidden;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--color-background);
    color: var(--color-text);
    font-family: var(--font-main);
    font-size: 1rem;
    line-height: 1.5;
    overflow-x: hidden;
}

/* Only when JS actually runs (html gets the "js" class - see the inline
   script at the top of <head>) does the interactive .jfc-app shell exist
   in any meaningful sense, so only then is it safe to lock overflow-y
   outright. Left unset otherwise (falling back to "hidden" for x and the
   browser's spec-mandated "auto" for y), the noscript fallback message
   and read-only passage text stay reachable by a normal page scroll for
   visitors without JavaScript.
   With JS running, leaving overflow-y unset would still compute to
   "auto" per the CSS Overflow spec (since overflow-x is non-visible),
   which lets html/body become a scroll container the instant a sub-pixel
   rounding gap opens up between their own height:100% and .jfc-app's
   height:100dvh (observed in practice at several mobile viewport sizes,
   even though .jfc-app itself measures zero internal overflow) - nothing
   should ever scroll at this level once JS is driving the app, since
   every scrollable area lives inside .jfc-col-scroll/.passage-text. */
html.js,
html.js body {
    overflow: hidden;
}

h1,
h2,
h3 {
    line-height: 1.25;
    color: var(--color-text);
}

p {
    margin: 0 0 var(--space-2);
}

ul {
    margin: 0;
    padding-left: 1.25rem;
}

button {
    font-family: inherit;
}

/* Never remove a focus indicator without providing a clear replacement. */
:focus {
    outline: 3px solid var(--color-focus);
    outline-offset: 2px;
}

:focus:not(:focus-visible) {
    outline: none;
}

:focus-visible {
    outline: 3px solid var(--color-focus);
    outline-offset: 2px;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* -------------------------------------------------------------------------
   3. App shell: locked single-viewport layout (same contract as the
   site's .jfc-app / .jfc-topbar / .jfc-shell pattern) - the shell wrapper,
   not <body> itself, gets the fixed-height/no-scroll treatment, so the
   <noscript> fallback (a sibling, before the shell) always remains fully
   readable and scrollable regardless of JavaScript being enabled.
   ------------------------------------------------------------------------- */
.jfc-app {
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    background: var(--color-background);
}

.jfc-topbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 10px var(--space-3);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    box-shadow: var(--shadow-card);
    z-index: 30;
}

.jfc-topbar-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    color: var(--color-text);
    font-weight: 900;
    text-decoration: none;
}

.jfc-topbar-brand .brand-mark {
    display: inline-grid;
    place-items: center;
    width: 30px;
    height: 30px;
    /* Dark text: the gradient is built from the two brand accent colors,
       both bright enough on this dark theme that white text on top of
       them would fail contrast (2.5-3.0:1) the same way button-primary's
       label would have - see the color-inverse comment in the :root
       block. */
    color: var(--color-inverse);
    font-size: 0.8rem;
    font-weight: 900;
    background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
    border-radius: 8px;
}

.jfc-topbar-brand .jfc-brand-text {
    display: flex;
    flex-direction: column;
    line-height: 1.15;
}

.jfc-topbar-brand .jfc-brand-text strong {
    font-size: 0.82rem;
    font-weight: 900;
}

.jfc-topbar-brand .jfc-brand-text span {
    font-size: 0.6rem;
    font-weight: 600;
    color: var(--color-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.jfc-topbar-title {
    margin: 0;
    padding-left: var(--space-2);
    border-left: 1px solid var(--color-border);
    font-size: 1rem;
    font-weight: 800;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jfc-shell {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.jfc-columns {
    height: 100%;
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: var(--space-2);
    padding: var(--space-2);
    box-sizing: border-box;
}

.jfc-col {
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-card);
    overflow: hidden;
}

.jfc-col-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 2px var(--space-2);
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}

.jfc-col-scroll::-webkit-scrollbar {
    width: 8px;
}

.jfc-col-scroll::-webkit-scrollbar-thumb {
    background-color: var(--color-border);
    border-radius: var(--radius-pill);
}

.panel-block + .panel-block {
    margin-top: 3px;
    padding-top: 3px;
    border-top: 1px solid var(--color-border);
}

.panel-block h2 {
    margin: 0 0 6px;
    font-size: 0.78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-muted);
}

/* -------------------------------------------------------------------------
   Shared metric list (passage metadata + results section)
   ------------------------------------------------------------------------- */
.metric-list {
    display: flex;
    flex-direction: column;
    gap: 3px;
    margin: 0;
}

.metric {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-1);
    padding: 0;
}

.metric-label {
    margin: 0;
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--color-muted);
}

.metric-value {
    margin: 0;
    font-size: 0.74rem;
    line-height: 1.2;
    font-weight: 700;
    color: var(--color-text);
    text-align: right;
    word-break: break-word;
}

/* -------------------------------------------------------------------------
   4. Passage metadata badges/pills
   ------------------------------------------------------------------------- */
.badge {
    display: inline-block;
    padding: 3px var(--space-2);
    border-radius: var(--radius-pill);
    font-size: 0.82rem;
    font-weight: 700;
    line-height: 1.4;
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
}

/* Difficulty is never conveyed by colour alone: each variant also gets a
   distinct left border "flag" so the state remains visible without
   relying on hue perception. */
.difficulty-easy {
    background: var(--color-success-soft);
    color: var(--color-success);
    border-color: var(--color-success);
    border-left-width: 4px;
}

.difficulty-medium {
    background: var(--color-accent-soft);
    color: var(--color-accent);
    border-color: var(--color-accent);
    border-left-width: 4px;
}

.difficulty-hard {
    background: var(--color-error-soft);
    color: var(--color-error);
    border-color: var(--color-error);
    border-left-width: 4px;
}

/* ---- Shared control select (Difficulty Level, Test Mode, Duration):
   one consistent dropdown style for every sidebar setting. All three are
   disabled once Start Typing has been clicked (see app.js) and stay
   disabled until Retake or a fresh page load, which is why a clear,
   non-transparent :disabled treatment matters here. ---- */
.difficulty-filter label,
.test-mode label {
    display: block;
    margin-bottom: 5px;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--color-text);
}

.test-mode label:not(:first-child) {
    margin-top: 10px;
}

.control-select {
    display: block;
    width: 100%;
    min-height: 34px;
    padding: 6px 10px;
    font-size: 0.82rem;
    font-family: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.control-select:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}

/* ---- Test action buttons (Start Typing / Retake / New Passage): a
   compact full-width stack in the sidebar rather than the wide
   horizontal buttons used elsewhere. ---- */
.test-actions {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 6px;
}

.test-actions .button {
    width: 100%;
    min-height: 30px;
    padding: 4px var(--space-2);
    font-size: 0.78rem;
}

/* -------------------------------------------------------------------------
   5. Realtime statistics
   ------------------------------------------------------------------------- */
.jfc-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-1);
}

.jfc-stat {
    padding: var(--space-1);
    text-align: center;
    background: var(--color-surface-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.jfc-stat-value {
    display: block;
    font-size: 1.15rem;
    font-weight: 900;
    color: var(--color-primary-dark);
}

.jfc-stat-label {
    font-size: 0.64rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--color-muted);
}

.jfc-progress-block {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

/* ---- Featured variant: Realtime Statistics is the primary on-screen
   focus of the main column, but it must stay compact - the whole app
   has to fit in one screen with no scrolling, so this is a slim single
   bar rather than a tall card. The heading is kept for accessibility
   (aria-labelledby) but hidden visually to avoid spending a text row on
   a label the compact stat chips already make self-evident. ---- */
.live-stats-featured {
    flex: 0 0 auto;
    margin-bottom: var(--space-1);
    padding: 6px var(--space-2);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-card);
}

.jfc-stats-featured {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 6px;
    margin-bottom: 6px;
}

.jfc-stats-featured .jfc-stat {
    padding: 3px 2px;
    border: 1px solid transparent;
    border-radius: var(--radius);
}

.jfc-stats-featured .jfc-stat-value {
    font-size: 1.05rem;
    line-height: 1.2;
}

.jfc-stats-featured .jfc-stat-label {
    font-size: 0.6rem;
}

/* Each realtime statistic gets its own colour so the bar reads as
   colourful, distinct signals at a glance rather than one flat block -
   the label text underneath always remains the primary, colour-
   independent identifier of what each number means. */
.jfc-stat-wpm {
    background: var(--color-primary-soft);
    border-color: rgba(20, 184, 166, 0.5);
}

.jfc-stat-wpm .jfc-stat-value {
    color: var(--color-primary-dark);
}

.jfc-stat-raw-wpm {
    background: var(--color-info-soft);
    border-color: rgba(96, 165, 250, 0.5);
}

.jfc-stat-raw-wpm .jfc-stat-value {
    color: var(--color-info);
}

.jfc-stat-accuracy {
    background: var(--color-success-soft);
    border-color: rgba(74, 222, 128, 0.5);
}

.jfc-stat-accuracy .jfc-stat-value {
    color: var(--color-success);
}

.jfc-stat-cpm {
    background: var(--color-accent-soft);
    border-color: rgba(251, 191, 36, 0.4);
}

.jfc-stat-cpm .jfc-stat-value {
    color: var(--color-accent);
}

.jfc-stat-errors {
    background: var(--color-error-soft);
    border-color: rgba(248, 113, 113, 0.5);
}

.jfc-stat-errors .jfc-stat-value {
    color: var(--color-error);
}

.jfc-stat-elapsed {
    background: var(--color-violet-soft);
    border-color: rgba(167, 139, 250, 0.5);
}

.jfc-stat-elapsed .jfc-stat-value {
    color: var(--color-violet);
}

.live-stats-featured .jfc-progress-block progress {
    height: 6px;
}

progress {
    appearance: none;
    -webkit-appearance: none;
    flex: 1 1 auto;
    height: 10px;
    border: none;
    border-radius: var(--radius-pill);
    background: var(--color-surface-muted);
    overflow: hidden;
}

progress::-webkit-progress-bar {
    background: var(--color-surface-muted);
    border-radius: var(--radius-pill);
}

progress::-webkit-progress-value {
    background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
    border-radius: var(--radius-pill);
    transition: width var(--transition-medium);
}

progress::-moz-progress-bar {
    background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
    border-radius: var(--radius-pill);
    transition: width var(--transition-medium);
}

#stat-progress-value {
    flex: 0 0 auto;
    font-size: 0.85rem;
    font-weight: 800;
    color: var(--color-primary-dark);
}

/* -------------------------------------------------------------------------
   6. Passage display + character states
   ------------------------------------------------------------------------- */
.jfc-card {
    flex: 0 1 auto;
    min-height: 0;
    margin-bottom: var(--space-1);
    padding: var(--space-2);
    background: var(--color-surface-muted);
    border: 1px solid var(--color-border);
    border-left: 4px solid var(--color-border);
    border-radius: calc(var(--radius) + 4px);
}

/* The passage card also carries the current passage's own
   .difficulty-easy/medium/hard class (shared with the metadata badge)
   purely for its coloured left-border accent - ".jfc-card.difficulty-*"
   (two classes) is more specific than the plain badge rule, so this
   correctly overrides the badge's own background/text-colour/border-
   color back to the card's normal look instead of inheriting them. */
.jfc-card.difficulty-easy,
.jfc-card.difficulty-medium,
.jfc-card.difficulty-hard {
    background: var(--color-surface-muted);
    color: inherit;
    border-color: var(--color-border);
}

.jfc-card.difficulty-easy {
    border-left-color: var(--color-success);
}

.jfc-card.difficulty-medium {
    border-left-color: var(--color-accent);
}

.jfc-card.difficulty-hard {
    border-left-color: var(--color-error);
}

.passage-text {
    /* Positioned so its character spans' offsetTop is measured relative
       to this box itself (their offsetParent) rather than bubbling up to
       some distant positioned ancestor - the scrollspy logic in app.js
       depends on this to keep the current character in view correctly. */
    position: relative;
    font-family: var(--font-mono);
    font-size: 1.15rem;
    line-height: 1.8;
    letter-spacing: 0.01em;
    max-width: 100%;
    max-height: 18vh;
    overflow-y: auto;
    overflow-wrap: break-word;
    word-break: normal;
    white-space: normal;
    scrollbar-width: thin;
    scrollbar-color: var(--color-border) transparent;
}

.char {
    /* Character state classes only ever change colour, background, and
       border - never font-size, padding, or margin - so toggling state
       while typing can never shift surrounding layout. */
    border-bottom: 2px solid transparent;
    border-radius: 2px;
}

.char-untyped {
    color: var(--color-muted);
}

.char-current {
    color: var(--color-text);
    background: var(--color-primary-soft);
    border-bottom-color: var(--color-primary);
}

.char-correct {
    color: var(--color-success);
}

.char-incorrect {
    color: var(--color-error);
    background: var(--color-error-soft);
    text-decoration: underline;
    text-decoration-style: wavy;
    text-decoration-color: var(--color-error);
}

/* Incorrect spaces have no visible glyph, so the background fill (not
   colour alone) is what makes the error visible. */
.char-incorrect-space {
    background: var(--color-error-soft);
    border-bottom-color: var(--color-error);
    text-decoration: underline;
    text-decoration-style: wavy;
    text-decoration-color: var(--color-error);
}

/* -------------------------------------------------------------------------
   7. Typing input
   ------------------------------------------------------------------------- */
.typing-input-section {
    flex: 0 0 auto;
}

.typing-input-section label {
    display: block;
    margin-bottom: var(--space-1);
    font-weight: 700;
    font-size: 0.9rem;
}

#typing-input {
    display: block;
    width: 100%;
    min-height: 5rem;
    padding: var(--space-2);
    /* 16px minimum keeps iOS Safari from zooming the page on focus. */
    font-size: 1rem;
    font-family: var(--font-mono);
    line-height: 1.6;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    resize: vertical;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

#typing-input:focus {
    border-color: var(--color-focus);
    outline: 3px solid var(--color-focus);
    outline-offset: 2px;
}

#typing-input:disabled {
    background: var(--color-surface-muted);
    color: var(--color-muted);
    cursor: not-allowed;
    opacity: 0.85;
}

/* Applied by the typing logic when input validation needs to flag a
   problem (e.g. paste attempt) without changing the element's size. */
#typing-input.is-error {
    border-color: var(--color-error);
    box-shadow: 0 0 0 3px var(--color-error-soft);
}

/* Reserves its line height whether or not it currently holds text, so a
   blocked-paste/drop notice appearing and clearing never shifts layout. */
.input-notice {
    min-height: 1.2em;
    margin: var(--space-1) 0 0;
    font-size: 0.8rem;
    color: var(--color-accent);
}

/* -------------------------------------------------------------------------
   8. Test status
   ------------------------------------------------------------------------- */
.test-status {
    display: inline-block;
    margin: 0 0 var(--space-1);
    padding: 3px var(--space-2);
    border-radius: var(--radius-pill);
    font-size: 0.85rem;
    font-weight: 700;
    border: 1px solid var(--color-border);
    background: var(--color-surface-muted);
    color: var(--color-muted);
}

.test-status[data-state="typing"] {
    background: var(--color-primary-soft);
    color: var(--color-primary-dark);
    border-color: var(--color-primary);
}

.test-status[data-state="completed"] {
    background: var(--color-success-soft);
    color: var(--color-success);
    border-color: var(--color-success);
}

.test-status[data-state="error"] {
    background: var(--color-error-soft);
    color: var(--color-error);
    border-color: var(--color-error);
}

/* -------------------------------------------------------------------------
   9. Results section
   ------------------------------------------------------------------------- */
#results-section {
    flex: 0 0 auto;
    margin-top: var(--space-3);
    padding: var(--space-3);
    border: 1px solid var(--color-primary);
    border-radius: calc(var(--radius) + 4px);
    background: var(--color-surface);
    box-shadow: var(--shadow-card);
}

#results-section h2 {
    margin: 0 0 var(--space-2);
    font-size: 0.78rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-muted);
}

#results-section h3 {
    margin: var(--space-3) 0 var(--space-1);
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--color-muted);
}

#results-section h3:first-of-type {
    margin-top: var(--space-1);
}

/* Focus reliably even when moved here programmatically (not only on
   keyboard-triggered focus, which :focus-visible would sometimes miss). */
#results-section:focus {
    outline: 3px solid var(--color-focus);
    outline-offset: 4px;
}

.result-rating {
    display: inline-block;
    margin: 0 0 var(--space-2);
    padding: 4px var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--color-primary-soft);
    color: var(--color-primary-dark);
    font-size: 1.1rem;
    font-weight: 800;
}

#result-wpm-value,
#result-accuracy-value {
    font-size: 1.15rem;
    color: var(--color-primary-dark);
}

/* -------------------------------------------------------------------------
   10. Buttons
   ------------------------------------------------------------------------- */
.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 44px;
    min-width: 44px;
    padding: 10px var(--space-2);
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-size: 0.9rem;
    font-weight: 700;
    cursor: pointer;
    transition: background-color var(--transition-fast), border-color var(--transition-fast),
        color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
}

.button:hover:not(:disabled) {
    transform: translateY(-2px);
}

.button:active:not(:disabled) {
    transform: translateY(0);
}

.button:disabled {
    cursor: not-allowed;
    opacity: 0.6;
    transform: none;
}

.button-primary {
    color: var(--color-inverse);
    background: var(--color-primary);
    box-shadow: 0 12px 24px rgba(20, 184, 166, 0.25);
}

.button-primary:hover:not(:disabled) {
    background: var(--color-primary-dark);
}

.button-secondary {
    color: var(--color-secondary);
    background: var(--color-surface);
    border-color: var(--color-border);
}

.button-secondary:hover:not(:disabled) {
    border-color: rgba(129, 140, 248, 0.55);
    box-shadow: var(--shadow-card);
}

/* -------------------------------------------------------------------------
   11. Help / noscript / footer note
   ------------------------------------------------------------------------- */
.help-section ul {
    color: var(--color-muted);
    font-size: 0.7rem;
    line-height: 1.15;
}

.help-section li {
    margin-bottom: 0;
}

.sidebar-footer-note {
    margin: 4px 0 0;
    padding-top: 4px;
    border-top: 1px solid var(--color-border);
    color: var(--color-muted);
    font-size: 0.7rem;
}

.noscript-message {
    max-width: 900px;
    margin: var(--space-3) auto;
    padding-inline: var(--space-3);
    color: var(--color-accent);
}

/* -------------------------------------------------------------------------
   12. Responsive breakpoints - the locked, no-page-scroll shell (100dvh,
   overflow hidden) is preserved at every viewport size the app supports,
   down to small phones. Only the shape changes:
     - Medium (<=900px - short landscape phones, small laptop windows):
       still side-by-side, just a narrower sidebar and tighter spacing,
       since a 300px sidebar leaves too little width for the passage/
       textarea on these.
     - Narrow (<=700px - portrait phones/small tablets): side-by-side no
       longer leaves a usable typing column, so the shell stacks
       vertically instead - sidebar above main - but stays height-locked
       exactly like the desktop shell. The sidebar gets a bounded share
       of the height with its own internal scroll (it is setup/reference
       content, not needed while actively typing); main gets the rest
       and is tightened so the live stats, passage, and textarea fit
       without needing to scroll to reach any of them.
     - Extra-small (<=380px) and short (<=560px tall): further
       compaction for the smallest/shortest real devices.
   ------------------------------------------------------------------------- */
@media (max-width: 900px) {
    .jfc-columns {
        grid-template-columns: 230px minmax(0, 1fr);
        gap: 10px;
        padding: 10px;
    }

    .passage-text {
        font-size: 1.05rem;
        max-height: 16vh;
    }
}

@media (max-width: 700px) {
    .jfc-topbar {
        padding: 8px var(--space-2);
    }

    .jfc-topbar-brand .jfc-brand-text {
        display: none;
    }

    .jfc-topbar-title {
        padding-left: var(--space-1);
        font-size: 0.88rem;
    }

    .jfc-columns {
        display: flex;
        flex-direction: column;
        grid-template-columns: none;
        gap: 6px;
        padding: 6px;
    }

    .jfc-col-left {
        flex: 0 1 auto;
        max-height: 42%;
    }

    .jfc-col-main {
        flex: 1 1 auto;
        min-height: 0;
    }

    /* The sidebar can scroll internally for its secondary content (Help,
       footer) without violating "operate without scroll" - but the
       essential controls (Difficulty/Mode/Duration and the three action
       buttons) are part of normal operation and must fit without needing
       that scroll, so they compact here even though the desktop sidebar
       keeps its more generous control padding. */
    .difficulty-filter label,
    .test-mode label {
        margin-bottom: 2px;
        font-size: 0.72rem;
    }

    .test-mode label:not(:first-child) {
        margin-top: 4px;
    }

    .control-select {
        min-height: 28px;
        padding: 3px 8px;
    }

    .test-actions {
        gap: 3px;
        margin-top: 3px;
    }

    .test-actions .button {
        min-height: 28px;
        padding: 2px var(--space-2);
    }

    .live-stats-featured {
        padding: 4px 8px;
    }

    .jfc-stats-featured {
        gap: 4px;
        margin-bottom: 4px;
    }

    .jfc-stats-featured .jfc-stat {
        padding: 2px;
    }

    .jfc-stats-featured .jfc-stat-value {
        font-size: 0.92rem;
    }

    .jfc-stats-featured .jfc-stat-label {
        font-size: 0.56rem;
    }

    .test-status {
        margin-bottom: 4px;
    }

    .jfc-card {
        padding: 10px;
        margin-bottom: 4px;
    }

    .passage-text {
        font-size: 0.98rem;
        line-height: 1.55;
        max-height: 13vh;
    }

    .typing-input-section label {
        margin-bottom: 3px;
        font-size: 0.78rem;
    }

    #typing-input {
        min-height: 3.6rem;
        padding: 10px;
    }

    .input-notice {
        min-height: 1em;
        margin-top: 2px;
    }
}

/* Height-based compaction, independent of the width tiers above - a
   short landscape phone (ample width, little height) needs this just as
   much as a short portrait one, and at landscape widths over 700-900px
   it would otherwise get none of the width-triggered tightening at all. */
@media (max-height: 700px) {
    .jfc-topbar {
        padding: 6px var(--space-2);
    }

    .jfc-topbar-brand .brand-mark {
        width: 26px;
        height: 26px;
    }

    .live-stats-featured {
        padding: 4px 8px;
        margin-bottom: 4px;
    }

    .jfc-stats-featured {
        gap: 4px;
        margin-bottom: 4px;
    }

    .jfc-stats-featured .jfc-stat {
        padding: 2px;
    }

    .jfc-stats-featured .jfc-stat-value {
        font-size: 0.95rem;
    }

    .jfc-stats-featured .jfc-stat-label {
        font-size: 0.58rem;
    }

    .test-status {
        margin-bottom: 4px;
        padding: 2px 10px;
    }

    .jfc-card {
        padding: 8px;
        margin-bottom: 4px;
    }

    .passage-text {
        max-height: 12vh;
    }

    .typing-input-section label {
        margin-bottom: 3px;
        font-size: 0.78rem;
    }

    #typing-input {
        min-height: 3.2rem;
        padding: 8px;
    }

    .input-notice {
        min-height: 1em;
        margin-top: 2px;
    }
}

@media (max-height: 600px) {
    .jfc-topbar {
        padding: 4px var(--space-2);
    }

    .jfc-topbar-title {
        font-size: 0.8rem;
    }

    .live-stats-featured {
        padding: 3px 6px;
        margin-bottom: 3px;
    }

    .jfc-stats-featured {
        gap: 3px;
        margin-bottom: 3px;
    }

    .jfc-stats-featured .jfc-stat {
        padding: 1px;
    }

    .jfc-stats-featured .jfc-stat-value {
        font-size: 0.85rem;
    }

    .jfc-stats-featured .jfc-stat-label {
        font-size: 0.52rem;
    }

    .test-status {
        font-size: 0.74rem;
        padding: 1px 8px;
    }

    .jfc-card {
        padding: 4px 6px;
        margin-bottom: 3px;
    }

    .passage-text {
        max-height: 9vh;
        font-size: 0.92rem;
        line-height: 1.4;
    }

    /* The placeholder text and the visible passage above it already
       convey what the textarea is for, so at the shortest viewports the
       label is kept for screen readers only (still programmatically
       associated via for/id) rather than spending a text row on it. */
    .typing-input-section label {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    #typing-input {
        min-height: 2rem;
        padding: 4px 8px;
    }

    .input-notice {
        min-height: 0.9em;
        margin-top: 1px;
        font-size: 0.72rem;
    }
}

/* Only meaningful once .jfc-columns is actually stacked (guaranteed by
   the width condition here matching the <=700px width tier above) - the
   sidebar is setup/reference content, not needed while actively typing,
   so it gives up more of its share to main specifically in the corner
   where both width and height are this constrained (the smallest real
   phones, e.g. older 4-inch iPhones in either orientation). */
@media (max-width: 700px) and (max-height: 600px) {
    .jfc-col-left {
        max-height: 26%;
    }
}

/* Laptop/desktop only: the mobile-oriented height tiers above (<=700px
   and <=600px tall) key off height alone, with no width condition, so a
   wide-but-short laptop window (e.g. 1366x700) was matching them too and
   getting the same cramped passage box meant for phones in landscape.
   There is genuinely a lot of spare vertical room once the sidebar
   columns are side by side (measured 300-480px unused even at the
   smallest laptop height tested), so the passage container gets
   noticeably more room here - this deliberately comes after, and wins
   over, every rule above. */
@media (min-width: 901px) {
    .passage-text {
        max-height: 36vh;
    }
}

/* -------------------------------------------------------------------------
   13. Reduced motion / forced colours
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }

    progress::-webkit-progress-value,
    progress::-moz-progress-bar {
        transition: none;
    }
}

@media (forced-colors: active) {
    .button,
    #typing-input,
    .control-select,
    .badge,
    .jfc-stat,
    .test-status,
    .jfc-col,
    .jfc-topbar,
    #results-section {
        border: 1px solid ButtonText;
    }

    :focus,
    :focus-visible,
    #results-section:focus {
        outline: 2px solid Highlight;
    }

    .char-current {
        border-bottom: 2px solid Highlight;
    }
}

/* -------------------------------------------------------------------------
   14. Print
   ------------------------------------------------------------------------- */
@media print {
    .jfc-topbar,
    .typing-input-section,
    .jfc-col-left,
    .actions,
    .noscript-message {
        display: none !important;
    }

    .jfc-app,
    .jfc-shell,
    .jfc-columns,
    .jfc-col-scroll {
        height: auto !important;
        overflow: visible !important;
    }

    #results-section {
        box-shadow: none !important;
        border: 1px solid #000 !important;
    }

    body {
        background: #ffffff;
    }
}
