Spaces:
Running
Running
| /* 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)); } | |