/* core/static/core/css/reading_fluency.css
   Page-scoped styles for the Oral Reading Fluency Passage Builder
   (core/templates/core/oral_reading_fluency.html), loaded only there via
   extra_css — not folded into the shared style.css, since a large chunk of
   this (print rules, the margin-count layout, the drop zone) is print-only
   or specific to this one page and would bloat every other page's download
   for no benefit. */

.orf-page {
    max-width: 760px;
}

/* ── Intro ──────────────────────────────────────────────────────────────── */

/* Two classes, so this beats .wl-intro's own colour on specificity rather
   than on which stylesheet happened to load last — the .wl-intro.sb-intro
   precedent in spelling_bee.css.

   Bolded on the owner's call (2026-08-22). The two sibling solvers already
   disagree about this, so it is a choice either way rather than a departure:
   jumble.css's .jb-intro is bold, while .wl-intro.sb-intro is deliberately
   left at normal weight on the reasoning that bolding running prose turns it
   into another label. This page now sits with the Jumble page. Kept scoped
   rather than applied to .wl-intro site-wide, so the other thirteen pages
   using the class are untouched.

   --op-ink comes with it, the same lift the Bee's intro takes: --op-dark is
   4.74:1 on the paper texture, ink is 5.62:1. Weight alone thickens the
   strokes; the colour is what separates them from the grain behind. */
.wl-intro.orf-intro {
    color: var(--op-ink);
    font-weight: 700;
}

.orf-noscript-warning {
    margin: 0 0 20px;
    padding: 14px 16px;
    border: 2px solid var(--op-stamp);
    border-radius: 6px;
    background-color: var(--op-olive-soft);
    color: var(--op-text);
    font-size: 14px;
    line-height: 1.6;
}

.orf-msg {
    font-size: 14px;
    line-height: 1.5;
    margin: 0 0 16px;
}

.orf-msg-error {
    color: var(--op-stamp);
    font-weight: 600;
}

/* #3f6b2b, not --op-green-deep — this message now sits inside
   .orf-form-panel (--op-panel background), not directly on the paper
   texture body::before paints, so the dark, contrast-driven trade-off
   --op-green-deep exists for (see style.css's own long comment on it)
   doesn't apply here. That comment names this exact case: "#3f6b2b stays
   in place for .jb-ring-count and .orf-msg-notice, which sit on --op-panel
   rather than the paper and are fine there." */
.orf-msg-notice {
    color: #3f6b2b;
    font-weight: 600;
}

/* --------------------------------------------------------------------------
   Form
   -------------------------------------------------------------------------- */

.orf-form-panel {
    border: 1px solid var(--op-border);
    border-radius: 8px;
    background-color: var(--op-panel);
    padding: 20px;
    margin-bottom: 24px;
}

.orf-field {
    margin-bottom: 18px;
}

.orf-field label {
    display: block;
    font-weight: 600;
    color: var(--op-text);
    margin-bottom: 6px;
}

.orf-field textarea {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 15px;
    padding: 10px 12px;
    border: 1px solid var(--op-border);
    border-radius: 6px;
    background-color: var(--op-panel-alt);
    color: var(--op-text);
}

.orf-drop-zone {
    position: relative;
    border: 2px dashed var(--op-border);
    border-radius: 8px;
    padding: 22px;
    text-align: center;
    cursor: pointer;
    transition: border-color 0.2s, background-color 0.2s;
}

.orf-drop-zone:hover,
.orf-drop-zone.orf-drag-over {
    border-color: var(--op-stamp);
    background-color: var(--op-olive-soft);
}

/* A settled verdict on the selected file — solid rather than dashed reads
   as "done", where the default dashed border reads as "drop something
   here". Kept as its own class rather than :has(input:valid) because a
   file input's own validity has nothing to do with its accept attribute
   until reading_fluency.js's setCustomValidity() call gives it one. */
.orf-drop-zone.orf-drop-zone-valid {
    border-style: solid;
    border-color: var(--op-green-deep);
}

.orf-drop-zone.orf-drop-zone-invalid {
    border-style: solid;
    border-color: var(--op-stamp);
}

.orf-drop-zone input[type="file"] {
    position: absolute;
    inset: 0;
    opacity: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
}

.orf-drop-label {
    font-size: 14px;
    color: var(--op-text-muted);
    pointer-events: none;
}

.orf-drop-label-valid {
    color: var(--op-green-deep);
    font-weight: 600;
}

.orf-drop-label-invalid {
    color: var(--op-stamp);
    font-weight: 600;
}

/* Shown only while showSelectedFile (reading_fluency.js) has just kicked
   off an OCR auto-submit — the moment a reader's attention is here, not on
   the Build Passage button, which never changes for this (or any) path.
   Uses the @keyframes orf-spin declared further down this file; keyframe
   rules apply regardless of where in the stylesheet they're declared
   relative to what uses them. */
.orf-drop-spinner {
    display: none;
    width: 14px;
    height: 14px;
    margin-right: 6px;
    vertical-align: -2px;
    border: 2px solid rgba(0, 0, 0, 0.15);
    border-top-color: var(--op-green-deep);
    border-radius: 50%;
    animation: orf-spin 0.7s linear infinite;
}

.orf-drop-zone.orf-drop-zone-reading .orf-drop-spinner {
    display: inline-block;
}

@media (prefers-reduced-motion: reduce) {
    .orf-drop-spinner {
        animation: none;
    }
}

.orf-line-mode {
    border: 1px solid var(--op-border);
    border-radius: 6px;
    padding: 14px;
}

.orf-line-mode legend {
    font-weight: 600;
    padding: 0 6px;
}

.orf-line-mode label {
    display: block;
    font-weight: normal;
    margin-bottom: 10px;
}

.orf-field-hint {
    display: block;
    font-size: 12px;
    font-weight: normal;
    color: var(--op-text-muted);
    margin: 2px 0 0 24px;
}

.orf-form-actions {
    display: flex;
    gap: 10px;
}

.orf-btn-primary {
    font-family: inherit;
    font-size: 15px;
    font-weight: 700;
    padding: 10px 22px;
    border: 2px solid var(--op-card-border);
    border-radius: 6px;
    background-color: var(--op-stamp);
    color: var(--op-cream);
    cursor: pointer;
}

.orf-btn-primary:hover {
    background-color: var(--op-stamp-dark);
}

/* Build Passage starts disabled (see reading_fluency.js's
   initBuildButtonGate) until there's a passage to paste or a file to read
   — this is what a reader actually sees for that state, since :hover
   above never applies to a disabled button anyway. */
.orf-btn-primary:disabled {
    background-color: var(--op-border);
    color: var(--op-text-muted);
    cursor: not-allowed;
}

/* Shared by .orf-drop-spinner (see below) — kept here since Build Passage
   is the first button on the page, not because anything on the button
   itself uses it. */
@keyframes orf-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Deliberately quieter than .orf-btn-primary — Clear is destructive (wipes
   the text, any uploaded file, and the built passage, behind a JS confirm()
   prompt) and shouldn't visually compete with Build Passage as the page's
   one primary action. */
.orf-btn-quiet {
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    padding: 10px 22px;
    border: 1px solid var(--op-border);
    border-radius: 6px;
    background-color: var(--op-panel);
    color: var(--op-olive-dark);
    cursor: pointer;
}

.orf-btn-quiet:hover {
    border-color: var(--op-olive);
}

/* --------------------------------------------------------------------------
   Results: passage with margin word counts
   -------------------------------------------------------------------------- */

.orf-results-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
}

.orf-view-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px;
    margin-left: auto;
}

.orf-filename-control {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
}

.orf-filename-control label {
    font-weight: 700;
    color: var(--op-text);
    white-space: nowrap;
}

.orf-filename-control input {
    font-family: inherit;
    font-size: 14px;
    padding: 5px 8px;
    border: 1px solid var(--op-border);
    border-radius: 6px;
    background-color: var(--op-panel-alt);
    color: var(--op-text);
    width: 11em;
}

.orf-filename-control input.orf-filename-invalid {
    border-color: var(--op-stamp-dark);
    box-shadow: 0 0 0 1px var(--op-stamp-dark);
}

/* Out of flow on purpose: an error appearing must not shift the controls
   or the content below. It floats under the field instead. */
.orf-filename-error {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 2;
    width: max-content;
    max-width: min(26em, calc(100vw - 32px));
    margin: 0;
    font-size: 14px;
    font-weight: 700;
    color: var(--op-stamp-dark);
}

.orf-font-size-control {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.orf-font-btn {
    padding: 4px 10px;
    font-size: 14px;
    line-height: 1;
}

.orf-font-size-value {
    min-width: 3.5em;
    text-align: center;
    font-weight: 600;
    color: var(--op-text);
}

.orf-header-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    font-weight: 700;
    color: var(--op-text);
    cursor: pointer;
}

/* Lives as the first child of #orf-passage now (see the template), so it
   shares the passage's own border/background rather than being a separate
   boxed panel — the border-bottom below is what still marks it as its own
   section. Shown on screen by default (not just in print) so a teacher can
   preview exactly what will print. .orf-header-hidden — toggled by the
   "Include header" checkbox, name="include_header" on the form — governs
   print and the downloaded PDF too, not just the on-screen preview; see
   the @media print block below and build_passage_pdf's include_header
   parameter in core/reading_fluency_pdf.py.

   font-family/font-size/line-height are all fixed here rather than left to
   inherit from #orf-passage — that element's own font-size is the text-size
   stepper's target (reading_fluency.js's initFontSizeControl sets it as an
   inline style) and its font-family is the passage's monospace Courier New.
   The header's blank fields are handwriting space, not passage text, so
   neither should move when a reader resizes the passage. */
.orf-print-header {
    display: block;
    margin: 0 0 16px;
    padding-bottom: 14px;
    border-bottom: 1px dashed var(--op-border);
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 14px;
    line-height: 1.5;
}

.orf-print-header.orf-header-hidden {
    display: none;
}

.orf-print-field,
.orf-print-calc {
    margin: 0 0 10px;
    color: var(--op-text);
}

.orf-print-field:last-child,
.orf-print-calc:last-child {
    margin-bottom: 0;
}

.orf-print-calc {
    font-weight: 600;
}

.orf-edit-hint {
    font-size: 13px;
    font-style: italic;
    font-weight: 700;
    color: var(--op-text-muted);
    margin: -6px 0 10px;
}

.orf-passage {
    border: 1px solid var(--op-border);
    border-radius: 8px;
    background-color: var(--op-panel);
    padding: 20px 16px;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    line-height: 2;
}

.orf-line {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    margin: 0;
    padding: 2px 0;
}

.orf-line-blank {
    height: 1em;
}

.orf-line-text {
    flex: 1;
}

.orf-line-count {
    flex-shrink: 0;
    min-width: 2.5em;
    text-align: right;
    color: var(--op-text-muted);
    font-size: 0.8em;
}

/* Editing affordance — reading_fluency.js adds contenteditable to
   .orf-line-text (respect mode) and .orf-paragraph (auto-wrap mode) only
   when it runs, so there's nothing to style here in the no-JS case (the
   <noscript> notice covers that). Hover previews the target before the
   reader commits to clicking in; focus marks where they're actively typing. */
.orf-line-text[contenteditable="true"]:hover,
.orf-paragraph[contenteditable="true"]:hover {
    background-color: var(--op-olive-soft);
    border-radius: 3px;
}

.orf-line-text[contenteditable="true"]:focus,
.orf-paragraph[contenteditable="true"]:focus {
    outline: 2px dashed var(--op-stamp);
    outline-offset: 2px;
    background-color: var(--op-olive-soft);
    border-radius: 3px;
}

/* --------------------------------------------------------------------------
   Auto-wrap passage — real paragraph flow, margin counts measured and
   placed by reading_fluency.js (see PassageParagraph's docstring: nothing
   in CSS can select "the Nth visual line", so this can't be done in CSS
   alone, the same reason the word pill accordion panel is JS-positioned).
   -------------------------------------------------------------------------- */

.orf-passage-auto-wrap {
    position: relative;
}

/* This box is deliberately fluid (fills its container) rather than a fixed
   physical width — there is no dedicated Print button (removed: printing
   the live page and printing Save PDF's server-rendered PDF converged
   on the same reader workflow — open a PDF, then print it — so keeping
   both was redundant), and Save PDF (core/reading_fluency_pdf.py, pure
   Python/fpdf2) never depended on this box's on-screen width at all. The
   @page/@media print rules below (and .orf-line-row's per-line margin
   counts) still apply if a reader prints this page directly (Ctrl+P) —
   that path is a best-effort fallback, not something print correctness
   depends on, which is why the box is not constrained to a physical width
   for it. See git history on this rule for the four rounds of chasing
   print-pagination correctness through JS/CSS alone that led here, before
   settling on Save PDF as the one reliable path. */
.orf-paragraph {
    margin: 0 0 0.4em;
}

.orf-word {
    /* A literal space text node between spans in the template provides the
       word gap — nothing added here, so JS's offsetTop grouping sees each
       span's own box, not an inflated one. */
}

/* One per VISUAL LINE, not per paragraph and not per passage — the scope
   kept needing to shrink, each time in response to a real, measured
   defect rather than a hypothetical one. A position:absolute count needs
   a containing block that can never itself straddle a printed page
   break; first the whole (possibly multi-page) passage was the
   containing block, then one paragraph — and a single ordinary paragraph
   turned out to be able to span a full printed page too, in real prose,
   reproducing the identical bug at a narrower scope. break-inside: avoid
   below on something this small (one line tall) only ever costs at most
   one line of blank space if a break would have fallen inside it —
   unlike the same rule at paragraph granularity, measured to leave a
   large blank gap when a longer paragraph didn't fit the remaining page. */
.orf-line-row {
    position: relative;
}

/* padding-right is what reserves room for this line's own
   .orf-margin-count, an absolutely-positioned sibling — must live on
   .orf-line-content specifically, not .orf-line-row: the count is
   positioned right:0 relative to the ROW, so the row itself must have no
   padding of its own (right:0 needs to reach the row's true right edge,
   which is where the padding-right zone starts), while the CONTENT needs
   the padding to keep its own text from running into that zone. Splitting
   this one level higher (giving the row itself the padding) was a real,
   measured bug: it put the numbers on top of the last word of text
   instead of beside it, since right:0 then landed at the start of the
   reserved zone rather than inside it. */
.orf-line-content {
    display: block;
    padding-right: 4em;
    box-sizing: border-box;
}

.orf-margin-count {
    /* right: 0.6em, not 0 — flush against the box's own border read as the
       numbers "falling off the page" (measured: right edge landed exactly
       on the border pixel, zero gap). This is inside .orf-line-content's
       own padding-right, so it doesn't collide with the text. */
    position: absolute;
    top: 0;
    right: 0.6em;
    color: var(--op-text-muted);
    font-size: 0.8em;
}

.orf-total-words {
    font-weight: 600;
    margin: 10px 0 24px;
}

/* --------------------------------------------------------------------------
   Print layout
   -------------------------------------------------------------------------- */

/* @page rather than a body/html margin: this is what actually reaches the
   browser's own print pagination, which a normal box-model margin cannot
   touch. Only margin-top is set, matching the PDF's TOP_MARGIN (0.25in) —
   left/right/bottom are left alone, deliberately, so the browser's own
   print-dialog margin setting still governs everything but the one side
   this page has an opinion about. Not nested inside @media print: @page
   only ever applies during pagination anyway, and this file is loaded on
   this page alone, so nothing scopes it further. */
/* Left/right/bottom match core/reading_fluency_pdf.py's own MARGIN (54pt =
   0.75in), so the browser print dialog opens on a default that visually
   matches the downloaded PDF. This is a DEFAULT, not a guarantee — the
   reader can still widen it interactively in the print preview UI, which is
   exactly why .orf-passage-auto's width above is fixed conservatively
   enough to survive that rather than relying on this value being honoured. */
@page {
    margin: 0.25in 0.75in 0.75in 0.75in;
}

@media print {
    body:has(.orf-page) .main-header,
    body:has(.orf-page) .main-footer,
    body:has(.orf-page) .orf-form-panel,
    body:has(.orf-page) .orf-results-actions,
    body:has(.orf-page) .wl-intro,
    body:has(.orf-page) .wl-credits-link,
    body:has(.orf-page) .orf-total-words,
    body:has(.orf-page) .orf-edit-hint,
    body:has(.orf-page) .orf-page > h1,
    body:has(.orf-page) .orf-results > h2 {
        display: none !important;
    }

    body:has(.orf-page) .page-row {
        border: none;
        margin: 0;
    }

    /* One line, not a whole paragraph — see .orf-line-row's own comment
       above for why the granularity matters: avoiding a split at
       paragraph scope was measured to leave a large blank gap when a
       longer paragraph didn't fit the remaining page (worse with the
       header included, since it eats into that remaining space). At one
       line, the same protection costs at most one line's worth of blank
       space instead. */
    .orf-line-row {
        break-inside: avoid;
    }

    /* No !important on display here (unlike the rest of this block): the
       "Include header" checkbox now governs print and download too, not
       just the on-screen preview, so .orf-header-hidden (added by that
       checkbox, see reading_fluency.js) must still be able to win here —
       its two-class selector already outranks this one-class rule on
       specificity alone, with no !important tug-of-war needed. The
       screen-only divider (border-bottom/padding-bottom) is undone here —
       the printed sheet keeps its original plain-text look, and the fixed
       font stays fixed since .orf-print-field/.orf-print-calc below still
       set their own size directly rather than relying on inheritance. */
    .orf-print-header {
        margin: 0 0 12px;
        padding-bottom: 0;
        border-bottom: none;
    }

    /* These fields are written on by hand, so they need far more room than
       set text — the same reason HEADER_LINE_HEIGHT_FACTOR exists in
       core/reading_fluency_pdf.py, kept in step with it so the browser's
       Print and the Save PDF button produce the same sheet. 13px x 2.6
       = 33.8px between rules. The page's own top margin is set by the
       @page rule above, kept in step with the PDF's TOP_MARGIN. */
    .orf-print-field,
    .orf-print-calc {
        font-size: 13px;
        line-height: 2.6;
        margin: 0;
    }

    .orf-passage {
        border: none;
    }

    /* The editing affordance is a screen-only hint — never printed, and
       harmless even if a reader somehow prints mid-edit. */
    .orf-line-text[contenteditable],
    .orf-paragraph[contenteditable] {
        outline: none !important;
        background: none !important;
    }

    /* Auto-wrap keeps its right padding in print — that reserved space is
       where the JS-measured margin counts sit. .orf-passage-auto's own
       fixed width (see that rule, well above the media query) already
       applies here unchanged — it's set unconditionally, on purpose, so
       print never depends on `beforeprint` re-measuring anything. */
    .orf-passage:not(.orf-passage-auto) {
        padding: 0;
    }
}
