/* ============================================================================
   Components. The contract is docs/ui-style.md.

   Loaded LAST, after tokens.css and tailwind.css, so these component rules win
   over Tailwind's utilities and preflight. Tailwind is kept for layout only.

   Two rules run through everything below:
   - Hairlines, not boxes. 1px --rule, no card borders, no shadows, no rounded
     rectangles around content. Whitespace is the layout.
   - The four domain colours appear as a 2px marker beside a rating word and
     NOWHERE else. Everything else is greyscale.
   ============================================================================ */

/* ------------------------------------------------------------------ hiding

   This file is loaded AFTER tailwind.css, so any component rule below that sets
   `display` outranks Tailwind's `.hidden` at equal specificity and puts an
   element on screen that the JavaScript believes it has hidden. That is not a
   cosmetic bug: every <section> ships hidden and boot.js shows exactly one, so
   losing `.hidden` shows all four screens at once, and the sign-in screen's
   reload button appears beside a working sign-in button. Caught in review of the
   first draft of this file, where `.pill { display: inline-flex }` did exactly
   that. Declared first and with !important so no rule below can take it back. */
.hidden { display: none !important; }

body {
    background: var(--ground);
    color: var(--ink);
    font-size: var(--size-body);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* --------------------------------------------------------------------- type */

/* Display is the loudest thing on any screen. If a screen needs a second
   display-sized element, the screen is wrong. Never bold: the size carries it. */
.t-display {
    font-size: var(--size-display);
    font-weight: 400;
    letter-spacing: -0.03em;
    line-height: 1.02;
    color: var(--ink);
}

.t-heading {
    font-size: var(--size-heading);
    font-weight: 500;
    letter-spacing: -0.02em;
    line-height: 1.15;
}

.t-subhead {
    font-size: var(--size-subhead);
    font-weight: 500;
    letter-spacing: -0.01em;
    line-height: 1.35;
}

/* Not a fifth content size. It reads as a different category because of
   uppercase, weight and tracking, which is what lets the type scale stop at
   four steps instead of drifting back to six. */
.t-label {
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.12em;
    line-height: 1.2;
    text-transform: uppercase;
}

/* Ordinals are Label with tabular numerals, so 001 and 004 occupy the same
   width and the left edge of every section lines up. */
.ordinal {
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.12em;
    line-height: 1.2;
    text-transform: uppercase;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1;
}

/* Supporting copy separates from primary copy by colour, not by another size. */
.t-muted { color: var(--ink-muted); }
.t-faint { color: var(--ink-faint); }

/* ---------------------------------------------------------------- structure */

.masthead { border-bottom: 1px solid var(--rule); }

/* Every major block carries an ordinal at its top-left and is separated from
   the one above by a single hairline. No card, no fill, no shadow. */
.numbered-block {
    border-top: 1px solid var(--rule);
    padding-top: var(--gap-section-tight);
    margin-top: var(--gap-section-tight);
}

.numbered-block:first-child {
    border-top: 0;
    padding-top: 0;
    margin-top: 0;
}

@media (min-width: 768px) {
    .numbered-block {
        padding-top: var(--gap-section);
        margin-top: var(--gap-section);
    }
}

.numbered-title {
    font-size: var(--size-heading);
    font-weight: 500;
    letter-spacing: -0.02em;
    line-height: 1.15;
    /* Uppercased visually, per the contract's 001 SUMMARY. The DOM keeps
       sentence case so assistive technology, a copied selection and the
       Playwright locators all read normally. */
    text-transform: uppercase;
    margin-top: 0.5rem;
}

/* Multi-column blocks carry a 1px left rule per column, as in the reference's
   four-card row. The FIRST column carries one too, which is what makes the row
   read as a ruled grid rather than as three dividers. */
.col-rules {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.75rem 0;
    margin-top: 2rem;
}

.col-rules > * {
    border-left: 1px solid var(--rule);
    padding-left: 1rem;
}

@media (min-width: 640px) {
    .col-rules { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
    .col-rules { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* A domain block's opening hairline. This element used to be a 4px bar filled
   with the domain colour; the colour now survives only as the rating marker, so
   this is a rule like every other rule. The class name is unchanged because the
   Playwright suite counts these to assert all four domains rendered. */
.domain-bar {
    height: 1px;
    background: var(--rule);
    border: 0;
}

/* ------------------------------------------------------------------ controls

   Pill button: --ground-raised fill, 1px --rule border, fully rounded, and a
   filled circle carrying the arrow. The circle is the affordance; the pill is
   the container. Primary actions invert.

   The label lives in its own span because upload.js and boot.js swap button
   text ("Analyse" -> "Uploading"), and writing textContent on the button itself
   would delete the arrow. */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    background: var(--ground-raised);
    border: 1px solid var(--rule);
    border-radius: 9999px;
    padding: 0.4375rem 0.4375rem 0.4375rem 1.25rem;
    color: var(--ink);
    font-size: var(--size-body);
    font-weight: 500;
    line-height: 1.2;
    cursor: pointer;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

.pill:hover:not(:disabled) { border-color: var(--ink-muted); }

.pill:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
}

.pill__arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 9999px;
    background: var(--ink);
    color: var(--ink-inverse);
}

.pill--primary {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--ink-inverse);
}

.pill--primary .pill__arrow {
    background: var(--ground-raised);
    color: var(--ink);
}

/* Disabled is legible, not invisible: a participant over the file cap must be
   able to read the button they cannot press. */
.pill:disabled {
    cursor: not-allowed;
    background: transparent;
    border-color: var(--rule);
    color: var(--ink-faint);
}

.pill--primary:disabled { background: transparent; }

.pill:disabled .pill__arrow {
    background: var(--rule);
    color: var(--ground);
}

/* A quieter action that is still a button: a rule, no fill, no circle. */
.link-action {
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    border-bottom: 1px solid var(--rule);
    padding-bottom: 2px;
    cursor: pointer;
}

.link-action:hover { color: var(--ink); border-bottom-color: var(--ink); }

.link-action:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
}

.field {
    display: block;
    width: 100%;
    background: var(--ground-raised);
    border: 0;
    border-bottom: 1px solid var(--rule);
    padding: 0.625rem 0.75rem;
    color: var(--ink);
    font-size: var(--size-body);
    font-family: inherit;
    border-radius: 0;
}

.field::placeholder { color: var(--ink-faint); }

.field:focus {
    outline: none;
    border-bottom-color: var(--ink);
}

.field--select {
    width: auto;
    padding: 0.375rem 0.5rem;
}

/* The file input is a browser-drawn control, so it is left alone apart from
   type and colour. Replacing it with a custom drop zone would be a new
   participant-facing surface with no coverage a fortnight before the workshop. */
#file-input {
    font-size: var(--size-body);
    color: var(--ink-muted);
    /* A file input has an intrinsic width that `display: block` does not
       stretch, and at that width the styled selector button plus "No file
       chosen" wrapped onto three lines and left a 60px hole in the middle of the
       screen. Give it room to sit on one line. */
    width: 100%;
    max-width: 30rem;
}

#file-input::file-selector-button {
    font-family: inherit;
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    background: var(--ground-raised);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: 9999px;
    padding: 0.375rem 0.875rem;
    margin-right: 0.75rem;
    cursor: pointer;
}

/* One selected file: a hairline row, not a bordered card. */
.file-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    border-bottom: 1px solid var(--rule);
    padding: 0.625rem 0;
}

/* ------------------------------------------------------------ notice states

   A deliberate departure from the otherwise monochrome page, and the reason is
   not aesthetic: the workshop runbook tells a facilitator that a red box means
   it has stopped and an amber box means it is still working, and a screen with
   neither is a defect. That distinction was a Critical fix and it outranks the
   restyle. It is carried by a 2px left rule rather than a filled wash, so the
   hairline discipline survives and the semantics do too.

   Colour is never the only signal: both boxes carry a sentence, and the text
   itself stays --ink so it is legible whether or not the rule is perceived. */
.notice {
    border-left: 2px solid var(--ink-muted);
    padding: 0.75rem 0 0.75rem 1rem;
    font-size: var(--size-body);
    color: var(--ink);
    white-space: pre-line;
}

.notice--error { border-left-color: #A3231B; }
.notice--working { border-left-color: #8A5A0B; }

/* Rejection messages are joined with newlines; render each on its own line. */
#upload-error { white-space: pre-line; }

/* -------------------------------------------------------------- the ratings

   Rating marker: a 2px vertical rule in the domain colour immediately left of
   the rating word, with the word itself in --ink. Not a pill, not a badge, no
   background fill.

   The class name `rating-pill` is retained because the Playwright suite counts
   these to assert eighteen sub-competency ratings rendered and reads their text
   to assert it is one of the four words from framework.js. The marker is a
   border rather than a child element for the same reason: textContent must stay
   exactly the rating word.

   --marker is set per domain block below and defaults to --rule. */
.rating-pill {
    display: inline-block;
    border-left: 2px solid var(--marker);
    padding-left: 0.5rem;
    font-size: var(--size-subhead);
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--ink);
}

/* Basic, Intermediate and Advanced deliberately have no colour of their own:
   the rating word carries the level, and giving each a colour was the old pill
   vocabulary. Not Observed takes the faint treatment because it is an absence
   and must not read as a result. It is never carried by colour alone; the word
   is always present. Scoped to .rating-pill so a roll-up figure that happens to
   read Not Observed keeps its label legible. */
.rating-pill.rating-not-observed {
    color: var(--ink-faint);
    border-left-color: var(--rule);
}

.domain-1 { --marker: var(--d1); }
.domain-2 { --marker: var(--d2); }
.domain-3 { --marker: var(--d3); }
.domain-4 { --marker: var(--d4); }

/* A domain roll-up is NOT a sub-competency rating: it is the most common rating
   across the domain, with ties broken downward, so it can read lower than rows
   in the table beneath it. It therefore carries its own label in words, sits
   beside the counts it was derived from, and takes NO domain colour and no 2px
   marker, which is what keeps it unmistakably distinct from the ratings. Do not
   restyle it back into a rating pill. */
.domain-rollup {
    display: block;
    font-size: var(--size-subhead);
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--ink);
    margin-top: 0.375rem;
}

/* --------------------------------------------------------------- the tables

   Hairline row separators only. No vertical rules, no header fill. */
.data-table {
    width: 100%;
    border-collapse: collapse;
    text-align: left;
}

.data-table th {
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    border-bottom: 1px solid var(--rule);
    padding: 0 1rem 0.625rem 0;
    vertical-align: bottom;
}

.data-table td {
    font-size: var(--size-body);
    border-bottom: 1px solid var(--rule);
    padding: 0.875rem 1rem 0.875rem 0;
    vertical-align: top;
}

.data-table th:last-child,
.data-table td:last-child { padding-right: 0; }

/* Locators, document names and refs: supporting copy, so muted, and tabular so
   a column of e1..e14 lines up. */
.cell-locator { color: var(--ink-muted); }

.cell-ref {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Wide tables scroll inside their own container rather than making the page
   scroll sideways. */
.table-scroll {
    overflow-x: auto;
    margin-top: 1.5rem;
}

.cites {
    font-size: var(--size-body);
    color: var(--ink-muted);
    margin-top: 0.25rem;
}

/* --------------------------------------------------------- the progress bar

   Four numbered stages, the current one in ink and the rest faint, with one
   hairline filling beneath. The bar is weighted by measured stage times and
   eases asymptotically toward the current stage's end value without arriving,
   so it cannot claim progress the server has not reported. */
.stage-list {
    margin-top: 2.5rem;
    border-top: 1px solid var(--rule);
}

.stage-row {
    display: grid;
    grid-template-columns: 3rem 1fr auto;
    align-items: baseline;
    gap: 0 1rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--rule);
    color: var(--ink-faint);
}

.stage-row .stage-name,
.stage-row .stage-state {
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.stage-row .stage-state { color: var(--ink-faint); }

/* Differentiated by colour and weight, not by another size. */
.stage-row.is-current { color: var(--ink); }
.stage-row.is-current .stage-name { font-weight: 600; }
.stage-row.is-current .stage-state { color: var(--ink); }

.stage-row.is-done { color: var(--ink-muted); }
.stage-row.is-done .stage-state { color: var(--ink-muted); }

.progress-rail {
    position: relative;
    height: 1px;
    background: var(--rule);
    margin-top: 2.5rem;
}

.progress-fill {
    position: absolute;
    left: 0;
    top: 0;
    height: 1px;
    width: 0;
    background: var(--ink);
    /* Linear, and slower than the recompute interval, so the fill glides rather
       than stepping. It is never animated to 100%: see progress.js. */
    transition: width 300ms linear;
}

/* Label size and tracking, but NOT uppercased: "48s elapsed" is a reading, and
   "13S ELAPSED" makes the unit look like part of a code. */
.progress-elapsed {
    display: block;
    text-align: right;
    font-size: var(--size-label);
    font-weight: 500;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
    margin-top: 0.75rem;
}

/* ------------------------------------------------------------- illustration

   1px-stroke geometric line art in --ink-muted, no fill, roughly 80px. Never an
   emoji, never a filled icon. Inline SVG, so no request leaves the page. */
.mark {
    width: 80px;
    height: 80px;
    color: var(--ink-muted);
}

.mark circle,
.mark rect,
.mark line,
.mark path,
.mark polyline {
    fill: none;
    stroke: currentColor;
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

/* --------------------------------------------------------------------- print

   The report is the deliverable and it is exported by printing to PDF.

   Hiding the other stages is not the same as showing the report: every stage
   carries .hidden until it is the current one, so a native Ctrl+P produced a
   blank page rather than the rendered report. #report-stage is therefore forced
   visible, and prints whenever a report has been rendered at all. */
@media print {
    /* The greige ground is a screen colour. On paper it is a grey wash over
       every page, so the ground goes white and the hairline and the muted ink
       are darkened: 1px of #D6D2D2 disappears on most office printers, and
       --ink-muted at 3.4:1 on screen is worse again in greyscale. The TOKENS are
       overridden rather than the rules, so every hairline on the page moves
       together and nothing needs a print-specific selector. */
    :root {
        --ground: #FFFFFF;
        --ground-raised: #FFFFFF;
        --rule: #8E8A88;
        --ink-muted: #4A4746;
        --ink-faint: #6E6A68;
    }

    /* `body >`, NOT a bare `header`. The page masthead is a direct child of body;
       the report's own <header>, carrying the course name, the evaluation date and
       the document count, is inside #report-root. A bare `header` selector hid
       both, so every PDF a participant has ever exported opened at "001 SUMMARY"
       with nothing saying which course it was about. Found by printing the page
       rather than by reading the stylesheet. */
    body > header, #upload-stage, #progress-stage, #signin-stage { display: none !important; }
    #report-stage { display: block !important; }
    body { background: #fff; }
    .page-break { break-before: page; }
    a[href]::after { content: ""; }

    /* A section heading must not be the last thing on a page. */
    .numbered-title { break-after: avoid; }

    /* The 2px domain marker is the only colour in the report and it is what
       makes eighteen ratings scannable, so it must survive the browser's
       "do not print backgrounds" default. */
    .rating-pill {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* A row must not be split across a page, and a table that runs over one
       must repeat its column heads. */
    .data-table tr { break-inside: avoid; }
    .data-table thead { display: table-header-group; }

    /* Nothing on paper scrolls. */
    .table-scroll { overflow-x: visible; }

    /* Section padding sized for a screen wastes a third of a printed page. */
    .numbered-block {
        padding-top: 1.5rem;
        margin-top: 1.5rem;
    }
}
