/* LocalGate audit instrument — grading-desk design. Chroma is semantic only: verdict inks + the reference yardstick. Everything instrumental speaks mono; everything read speaks serif. */ @font-face { font-family: "Source Serif 4"; src: url("fonts/source-serif-4-latin-400-normal.woff2") format("woff2"); font-weight: 400; font-display: swap; } @font-face { font-family: "Source Serif 4"; src: url("fonts/source-serif-4-latin-600-normal.woff2") format("woff2"); font-weight: 600; font-display: swap; } @font-face { font-family: "IBM Plex Mono"; src: url("fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2"); font-weight: 400; font-display: swap; } @font-face { font-family: "IBM Plex Mono"; src: url("fonts/ibm-plex-mono-latin-600-normal.woff2") format("woff2"); font-weight: 600; font-display: swap; } :root { --ground: #f3f5f4; /* cool exam paper, not cream */ --field: #ffffff; --ink: #1c2430; /* blue-black */ --dim: #5c6672; --line: #d4dad8; --line-strong: #aeb8b4; --ref-ground: #fbf7e6; /* annotation-yellow: the yardstick, nothing else */ --ref-line: #e3d9ae; --match: #2f7a4d; --nomatch: #a63d2a; --noanswer: #58657a; --unsure: #7d6418; --yes: #2f7a4d; --no: #a63d2a; --serif: "Source Serif 4", Charter, "Iowan Old Style", Georgia, serif; --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace; } @media (prefers-color-scheme: dark) { :root { --ground: #171b1f; --field: #1f242a; --ink: #dde3e8; --dim: #8b95a0; --line: #323a41; --line-strong: #49535c; --ref-ground: #2b2618; --ref-line: #4a3f1e; --match: #5cb885; --nomatch: #d9765f; --noanswer: #93a2b8; --unsure: #cfa93e; --yes: #5cb885; --no: #d9765f; } } * { box-sizing: border-box; } html { height: 100%; } body { margin: 0; min-height: 100%; background: var(--ground); color: var(--ink); font: 16.5px/1.62 var(--serif); } /* ── header: instrument panel ── */ header { display: flex; gap: 1.2rem; align-items: baseline; flex-wrap: wrap; padding: .65rem 1.2rem; background: var(--field); border-bottom: 2px solid var(--ink); font-family: var(--mono); } header h1 { margin: 0; font-size: .8rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; } #whoami { color: var(--dim); font-size: .75rem; } #progress { margin-left: auto; color: var(--dim); font-size: .75rem; font-variant-numeric: tabular-nums; } /* gauge with ticks every 10% */ #bar { height: 6px; background: repeating-linear-gradient(to right, transparent 0, transparent calc(10% - 1px), var(--line-strong) calc(10% - 1px), var(--line-strong) 10%), var(--line); } #bar-fill { height: 100%; width: 0; background: var(--ink); transition: width .25s ease; } #banner { padding: .55rem 1.2rem; font-family: var(--mono); font-size: .78rem; color: var(--unsure); background: color-mix(in srgb, var(--unsure) 9%, var(--field)); border-bottom: 1px solid var(--line); } #banner .banner-act { margin-left: .6rem; padding: .15rem .55rem; font: inherit; color: inherit; background: none; cursor: pointer; border: 1px solid currentColor; border-radius: 3px; } /* ── layout: reading column + rubric rail on wide screens ── */ #layout { max-width: 100rem; margin: 0 auto; } main { max-width: 46rem; margin: 1.1rem auto 12rem; padding: 0 1.2rem; } @media (min-width: 1240px) { #layout { display: grid; grid-template-columns: 1fr 21rem; gap: 0; } main { grid-column: 1; margin-left: auto; margin-right: 3vw; } details#rubric { grid-column: 2; position: sticky; top: 0; align-self: start; max-height: calc(100vh - 8rem); border-left: 1px solid var(--line); border-top: none; background: transparent; } details#rubric[open] summary { border-bottom: 1px solid var(--line); } } /* ── fields ── */ .field { background: var(--field); border: 1px solid var(--line); border-left: 3px solid var(--line-strong); padding: .8rem 1.1rem; margin: .8rem 0; } .field b { display: block; font-family: var(--mono); font-weight: 600; font-size: .66rem; letter-spacing: .12em; text-transform: uppercase; color: var(--dim); margin-bottom: .4rem; } .field .content { white-space: pre-wrap; overflow-wrap: anywhere; } /* the yardstick: only the reference wears the annotation highlight */ .field.reference { background: var(--ref-ground); border-color: var(--ref-line); border-left-color: var(--unsure); } .response { max-height: 26rem; overflow-y: auto; border-left-color: var(--ink); } .endmark { text-align: center; color: var(--dim); font-family: var(--mono); font-size: .68rem; letter-spacing: .18em; padding-top: .6rem; } .warmup-note { font-family: var(--mono); font-size: .78rem; padding: .55rem .9rem; border: 1px dashed var(--line-strong); color: var(--dim); } .independence { color: var(--dim); font-style: italic; } button.signin { font: 600 .95rem var(--mono); padding: .7rem 1.5rem; cursor: pointer; border: 2px solid var(--ink); background: var(--ink); color: var(--ground); } button.signin:hover { background: transparent; color: var(--ink); } /* ── rubric ── */ details#rubric { background: var(--field); border-top: 1px solid var(--line); font-family: var(--mono); font-size: .74rem; line-height: 1.55; overflow-y: auto; } details#rubric summary { cursor: pointer; padding: .55rem 1.1rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; font-size: .66rem; color: var(--dim); list-style: none; } details#rubric summary::before { content: "▸ "; } details#rubric[open] summary::before { content: "▾ "; } details#rubric pre { white-space: pre-wrap; font: inherit; margin: 0; padding: .3rem 1.1rem 1rem; } @media (max-width: 1239px) { details#rubric { position: fixed; bottom: 5.9rem; left: 0; right: 0; max-height: 38vh; border-top: 2px solid var(--line-strong); } } /* ── controls: the bench ── */ #controls { position: fixed; bottom: 0; left: 0; right: 0; background: var(--field); border-top: 2px solid var(--ink); padding: .65rem 1.2rem; display: flex; gap: .7rem; align-items: center; flex-wrap: wrap; font-family: var(--mono); } #controls .grp { display: flex; gap: .4rem; align-items: center; } #controls .lbl { font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); } #controls button { font: 600 .82rem var(--mono); padding: .48rem .8rem; cursor: pointer; background: var(--field); color: var(--ink); border: 2px solid var(--line-strong); } #controls button:hover { border-color: var(--ink); } /* verdict identity: a colored stamp edge; full ink when committed */ #controls button[data-v] { border-left-width: 5px; } #controls button[data-v="match"], #controls button[data-v="yes"] { border-left-color: var(--match); } #controls button[data-v="no_match"], #controls button[data-v="no"] { border-left-color: var(--nomatch); } #controls button[data-v="no_answer"] { border-left-color: var(--noanswer); } #controls button[data-v].sel { color: var(--field); } #controls button[data-v="match"].sel, #controls button[data-v="yes"].sel { background: var(--match); border-color: var(--match); } #controls button[data-v="no_match"].sel, #controls button[data-v="no"].sel { background: var(--nomatch); border-color: var(--nomatch); } #controls button[data-v="no_answer"].sel { background: var(--noanswer); border-color: var(--noanswer); } #controls button.unsure { border-style: dashed; color: var(--dim); } #controls button.unsure.sel { background: var(--unsure); border-color: var(--unsure); border-style: solid; color: var(--field); } #controls kbd { display: inline-block; font-size: .62rem; font-weight: 400; padding: 0 .3em; margin-left: .45em; border: 1px solid currentColor; border-bottom-width: 2px; border-radius: 3px; opacity: .65; } #controls #note { font: .8rem var(--mono); padding: .45rem .6rem; width: 13rem; border: 1px solid var(--line); background: var(--ground); color: var(--ink); } #controls #note::placeholder { color: var(--dim); } #controls .nav { margin-left: auto; display: flex; gap: .4rem; } /* ── the signature: the verdict stamp ── */ #controls .last { width: 100%; color: var(--dim); font-size: .72rem; display: flex; align-items: center; gap: .5rem; } #controls .last .stamp { display: inline-block; font-weight: 600; font-size: .7rem; letter-spacing: .06em; padding: .12rem .5rem; border: 2px solid var(--stamp, var(--dim)); color: var(--stamp, var(--dim)); transform: rotate(-1.2deg); /* confirm, then get out of the way: the chip fades ~2s after each grade; the dim "last · Axxx" line stays as the j-to-revisit reference */ animation: stamp .14s ease-out, stampfade .5s ease-in 2.2s forwards; } @keyframes stampfade { to { opacity: 0; visibility: hidden; } } #controls .last .stamp.v-match { --stamp: var(--match); } #controls .last .stamp.v-no_match { --stamp: var(--nomatch); } #controls .last .stamp.v-no_answer { --stamp: var(--noanswer); } @keyframes stamp { from { transform: rotate(-1.2deg) scale(1.12); opacity: .4; } to { transform: rotate(-1.2deg) scale(1); opacity: 1; } } @media (prefers-reduced-motion: reduce) { .last .stamp { animation: none; } #bar-fill { transition: none; } } :focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; } /* ── hardening + review fixes (2026-08-15 UX batch) ── */ /* The UA [hidden] rule loses to `display:flex` above — without this, the completion screen kept a live, invisible grading bench. */ #controls[hidden] { display: none; } #banner { color: var(--ink); border-left: 3px solid var(--unsure); } #banner.err { border-left-color: var(--no); } .item-id { font-family: var(--mono); font-size: .72rem; letter-spacing: .04em; color: color-mix(in srgb, var(--ink) 55%, var(--field)); margin: 0 0 .35rem; } /* long responses: keep the label pinned while scrolling — negative margin + padding so its background also masks the container's padding band (text used to smear behind the label at every scroll position) */ .field.response > b { position: sticky; top: 0; display: block; background: var(--field); z-index: 1; margin: -.8rem -1.1rem .4rem; padding: .8rem 1.1rem .4rem; } /* key hints are noise on touch screens (either signal suffices) — must match #controls kbd's specificity, which otherwise wins */ @media (hover: none), (pointer: coarse) { kbd, #controls kbd { display: none; } } /* KaTeX inherits our reading size; display math gets breathing room */ .content .katex { font-size: 1.06em; } .content .katex-display { margin: .5em 0; overflow-x: auto; } /* borderline wears the same slate as no_answer (both are "the amber path") */ #controls button[data-v="borderline"] { border-left-color: var(--noanswer); } #controls button[data-v="borderline"].sel { background: var(--noanswer); border-color: var(--noanswer); } /* mobile: the rubric must never hide beneath the bench — pin it to the bench's real measured height (set from JS each render) */ @media (max-width: 1239px) { details#rubric { bottom: var(--bench-h, 5.9rem); } #controls .nav button, #controls button.unsure { padding: .6rem .9rem; } #banner .banner-act { padding: .55rem .8rem; } } /* rubric rail: one size up for a 2-3h read, and an honest scroll cue */ details#rubric pre { font-size: .78rem; } details#rubric { scrollbar-gutter: stable; } details#rubric[open]::after { content: ""; position: sticky; display: block; bottom: 0; height: 1.6rem; margin-top: -1.6rem; pointer-events: none; background: linear-gradient(to bottom, transparent, var(--field)); } /* note grows into free bench space instead of truncating at 13rem */ #controls #note { flex: 1 1 13rem; min-width: 9rem; max-width: 24rem; } /* ── mobile compatibility batch (2026-08-15) ── */ /* Fixed-layer stack: bench over rubric over banner — the sticky response label used to paint on top of all three (it has z-index:1; they had none). */ #controls { z-index: 30; } details#rubric { z-index: 20; } #banner { position: sticky; top: 0; z-index: 10; max-height: 40vh; overflow-y: auto; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; } #banner[hidden] { display: none; } #banner .banner-act { order: -1; margin-left: 0; } /* recovery actions first, above the fold */ /* Content must always clear the bench's real height. PADDING, not margin: a bottom margin collapses through body (html/body are height-pinned) and never creates the scroll room — the item's tail then hides under the fixed bench, unreachable. Found on a real phone, item A022. */ main { margin-bottom: 0; padding-bottom: calc(var(--bench-h, 8rem) + 4rem); } #controls button { white-space: nowrap; } #controls .grp { flex-wrap: wrap; } /* screens without a bench (landing, locked, done) also need no rubric strip */ body:has(#controls[hidden]) details#rubric { display: none; } @media (max-width: 1239px) { #controls button { min-height: 44px; } /* the 250-500x buttons were 36px */ #controls #note { min-height: 44px; } #banner .banner-act { min-height: 44px; } details#rubric { max-height: min(38vh, calc(100dvh - var(--bench-h, 5.9rem) - 9rem)); } } /* small phones / landscape: compact the bench to give the item room to breathe */ @media (max-width: 430px), (max-height: 420px) { #controls { padding: .4rem .6rem; gap: .35rem; } #controls .lbl { flex-basis: 100%; margin: 0; } #controls #note { order: 9; flex: 1 1 100%; } #controls .last { display: none; } } /* onward links (unlock, completion, locked gate) — buttons, not bare anchors */ a.open-out { display: inline-block; margin-top: .8rem; padding: .7rem 1.3rem; font: 600 .9rem var(--mono); text-decoration: none; color: var(--ground); background: var(--ink); border: 2px solid var(--ink); } a.open-out:hover { background: transparent; color: var(--ink); } /* the signed-out "your work is safe" reassurance */ .resume-note { font-family: var(--mono); font-size: .8rem; padding: .6rem .9rem; border: 1px solid var(--match); border-left-width: 4px; color: var(--ink); background: color-mix(in srgb, var(--match) 7%, var(--field)); }