On mobile, tapping any text field zoomed the page in and made the game awkward to use. That's iOS Safari's built-in behavior, not a layout bug: focusing a text control whose font-size is under 16px zooms the viewport to it.
Revisionist's text controls land at 15px — and not from an explicit size, but by inheritance. The body sets the editorial 15px:
The page's 15px base (line 61).
assets/css/main.css · 368 lines assets/css/main.css 368 lines · CSS expand all
⋯ 56 lines hidden (lines 1–56) 2 * Main CSS — Revisionist. 3 * Tailwind + Nuxt UI, then the "Spine Console" design tokens: an editorial-minimal 4 * read-model aesthetic. Structure comes from hairlines + whitespace + type, never 5 * from cards / shadows / gradient fills. Semantic color lives in 9px dots + signed 6 * numbers, not box fills. 12 /* ── Design tokens ─────────────────────────────────────────────────────── */ 13 /* Warm "ledger / letterpress" palette: aged paper, sepia ink, hairlines like faint 14 rules, and a single terracotta accent (sealing-wax) for everything interactive. 15 Semantic color is warmed too so a swing reads as ink, never an error state — the 16 accent (bright terracotta) stays distinct from negative valence (deep oxblood). */ 18 --rv-fg : #2b241c ; /* sepia ink — ~12:1 on the cream ground */ 19 --rv-mut : #685c49 ; /* readable quiet — ~5.6:1 */ 20 --rv-faint : #7d6f57 ; /* the contrast floor for readable text — ~4.6:1 */ 21 --rv-hair : #c9b99c ; /* decorative-only (dot rings, ticks); never load-bearing */ 22 --rv-line : #e6dcc8 ; /* sepia hairline */ 23 --rv-bg : #faf6ee ; /* warm paper */ 24 --rv-tint : #f1ebdd ; /* a shade deeper paper */ 25 --rv-accent : #b1532e ; /* terracotta / sealing wax — the one interactive accent */ 26 --rv-ok : #466632 ; /* muted olive-forest */ 27 --rv-warn : #8a5a16 ; /* gold-ochre */ 28 --rv-bad : #7c2d26 ; /* deep oxblood (distinct from the brighter accent) */ 29 --rv-mono : ui-monospace , SFMono-Regular , Menlo , Consolas , monospace ; 30 --rv-serif : "Iowan Old Style" , "Palatino Linotype" , Palatino , Georgia , ui-serif , serif ; 32 /* ── Motion vocabulary ─────────────────────────────────────────────── */ 33 /* One timing language so every animation feels cut from the same cloth: 34 three durations (micro / standard / expressive) and two curves — a gentle 35 settle and the struck-token overshoot the dice & seal already use. */ 36 --rv-dur-1 : 140 ms ; /* micro — hovers, presses, color/position shifts */ 37 --rv-dur-2 : 280 ms ; /* standard — reveals, fades, fills */ 38 --rv-dur-3 : 520 ms ; /* expressive — the dice, the chronicle, node arrivals */ 39 --rv-ease : cubic-bezier ( .2 , .7 , .3 , 1 ) ; /* settle (ease-out) */ 40 --rv-ease-spring : cubic-bezier ( .2 , 1.4 , .4 , 1 ) ; /* the struck-token overshoot */ 44 --rv-fg : #ece4d6 ; /* warm off-white ink */ 46 --rv-faint : #978c77 ; /* ~6:1 on the warm near-black */ 49 --rv-bg : #14110d ; /* warm near-black (brown undertone) */ 51 --rv-accent : #db7a4f ; /* lifted terracotta */ 58 background : var ( --rv-bg ) ; 60 font-family : system-ui , - apple-system , "Segoe UI" , Roboto , sans-serif ; ⋯ 305 lines hidden (lines 64–368) 65 /* Browser-level chrome, warmed to match the page: text selection + scrollbars. */ 66 :: selection { background : color-mix ( in srgb , var ( --rv-accent ) 24 % , transparent ) ; } 67 * { scrollbar-width : thin ; scrollbar-color : var ( --rv-hair ) transparent ; } 68 :: -webkit-scrollbar { width : 11 px ; height : 11 px ; } 69 :: -webkit-scrollbar-track { background : transparent ; } 70 :: -webkit-scrollbar-thumb { background : var ( --rv-hair ) ; border-radius : 999 px ; border : 3 px solid var ( --rv-bg ) ; } 71 :: -webkit-scrollbar-thumb : hover { background : var ( --rv-faint ) ; } 73 /* ── Token helper classes (pair with Tailwind layout utilities) ─────────── */ 74 . rv-fg { color : var ( --rv-fg ) ; } 75 . rv-muted { color : var ( --rv-mut ) ; } 76 . rv-hair-c { color : var ( --rv-hair ) ; } 77 . rv-hover : hover { background : var ( --rv-tint ) ; } 78 . rv-hover-fg { color : var ( --rv-mut ) ; } 79 . rv-hover-fg : hover { color : var ( --rv-fg ) ; } 80 /* First-turn quieting of the empty read-model, so the eye lands on the dock. */ 81 . rv-dim { opacity : 0.5 ; transition : opacity var ( --rv-dur- 2 ) var ( --rv-ease ) ; } 82 @ media ( prefers-reduced-motion : reduce ) { . rv-dim { transition : none ; } } 83 . rv-faint { color : var ( --rv-faint ) ; } 84 . rv-accent { color : var ( --rv-accent ) ; } 85 . rv-bg { background : var ( --rv-bg ) ; } 86 . rv-tint { background : var ( --rv-tint ) ; } 87 . rv-mono { font-family : var ( --rv-mono ) ; } 89 . rv-ok { color : var ( --rv-ok ) ; } 90 . rv-warn { color : var ( --rv-warn ) ; } 91 . rv-bad { color : var ( --rv-bad ) ; } 93 /* Pair with Tailwind border / border-t / border-b / border-r / border-l. */ 94 . rv-line { border-color : var ( --rv-line ) ; } 96 /* A "section" header = an 11px uppercase muted label + a 1px underline. The card. */ 97 /* A text-style modifier only — does NOT force display, so `flex`/`hidden`/`md:inline` 98 utilities on the same element still win (a prior `display:block` here silently 99 broke the spine header's flex layout and the objective era-tag's responsive hide). */ 102 text-transform : uppercase ; 103 letter-spacing : 0.1 em ; 105 color : var ( --rv-mut ) ; 110 border-bottom : 1 px solid var ( --rv-line ) ; 114 /* 9px status / valence dots — the symbol that replaces every colored fill. */ 115 . rv-dot { display : inline-block ; width : 9 px ; height : 9 px ; border-radius : 50 % ; flex : none ; } 116 . rv-dot--ok { background : var ( --rv-ok ) ; } 117 . rv-dot--warn { background : var ( --rv-warn ) ; } 118 . rv-dot--bad { background : var ( --rv-bad ) ; } 119 . rv-dot--mut { background : var ( --rv-faint ) ; } 120 . rv-dot--accent { background : var ( --rv-accent ) ; } 121 . rv-dot--hollow { background : transparent ; border : 1.5 px solid var ( --rv-hair ) ; } 123 /* One serif voice — titles + Chronicle prose ONLY (never UI labels or numerals). */ 124 . rv-serif { font-family : var ( --rv-serif ) ; } 126 /* Inputs: 1px hairline, ~2px radius, accent focus outline. No shadow, no chrome. */ 129 background : var ( --rv-bg ) ; 131 border : 1 px solid var ( --rv-line ) ; 135 /* Engraved double-rule — the composer reads as a framed note (echoes the die). */ 136 box-shadow : inset 0 0 0 3 px var ( --rv-bg ) , inset 0 0 0 4 px var ( --rv-line ) ; 138 . rv-input :: placeholder { color : var ( --rv-faint ) ; } 140 . rv-input : focus-visible { 141 outline : 2 px solid var ( --rv-accent ) ; 142 outline-offset : -1 px ; 143 border-color : transparent ; 146 /* Buttons: text-forward, hairline; primary fills with the one accent. */ 151 background : transparent ; 152 border : 1 px solid var ( --rv-line ) ; 156 transition : border-color var ( --rv-dur- 1 ) var ( --rv-ease ) , color var ( --rv-dur- 1 ) var ( --rv-ease ) , 157 background var ( --rv-dur- 1 ) var ( --rv-ease ) , opacity var ( --rv-dur- 1 ) var ( --rv-ease ) , 158 transform var ( --rv-dur- 1 ) var ( --rv-ease ) ; 160 . rv-btn : hover : not ( : disabled ) { border-color : var ( --rv-fg ) ; } 161 . rv-btn : active : not ( : disabled ) { transform : translateY ( 0.5 px ) scale ( 0.975 ) ; } 162 . rv-btn : focus-visible { outline : 2 px solid var ( --rv-accent ) ; outline-offset : 1 px ; } 163 . rv-btn : disabled { opacity : 0.5 ; cursor : not-allowed ; } 166 background : var ( --rv-accent ) ; 167 border-color : var ( --rv-accent ) ; 169 . dark . rv-btn--primary { color : #0d0d0d ; } 170 . rv-btn--primary : hover : not ( : disabled ) { filter : brightness ( 1.08 ) ; border-color : var ( --rv-accent ) ; } 171 /* Disabled primary reads as a flat, inert neutral — NOT a dimmed accent — so the 172 enabled→disabled jump is unmistakable and the resting CTA never looks faded. */ 173 . rv-btn--primary : disabled { 174 color : var ( --rv-faint ) ; 175 background : transparent ; 176 border-color : var ( --rv-line ) ; 180 /* A real, on-brand focus ring on every bespoke interactive element (the .rv-input 181 handles its own). One accent; never the suppressed default. */ 184 summary : focus-visible , 185 [ role = "button" ] : focus-visible , 186 [ tabindex ] : focus-visible { 187 outline : 2 px solid var ( --rv-accent ) ; 192 /* Invisible 44px hit area for the editorial-tiny icon controls (padding, not size). */ 194 display : inline-flex ; 196 justify-content : center ; 201 /* Ruled "fill-in" field — a single rule like a blank on a ledger form / letter. 202 Used for the short text inputs; the multi-line composer keeps a boxed surface. */ 205 background : transparent ; 208 border-bottom : 2 px solid var ( --rv-hair ) ; 212 transition : border-color var ( --rv-dur- 1 ) var ( --rv-ease ) , background-color var ( --rv-dur- 1 ) var ( --rv-ease ) ; 214 . rv-field :: placeholder { color : var ( --rv-faint ) ; } 216 . rv-field : focus-visible { 218 border-bottom-color : var ( --rv-accent ) ; 219 background : var ( --rv-tint ) ; 222 /* Mobile Safari zooms the viewport when a focused text control's font-size is 223 under 16px (both .rv-field and .rv-input inherit the body's 15px). Pin them to 224 16px on touch devices so tapping a field no longer zooms the page in — without 225 touching the viewport meta (disabling pinch-zoom would be an a11y regression). 226 Desktop keeps the smaller editorial size. */ 227 @ media ( pointer : coarse ) { 234 /* A mounted card-stock surface — a filing card with an engraved inner rule. */ 236 background : var ( --rv-tint ) ; 237 border : 1 px solid var ( --rv-line ) ; 239 box-shadow : inset 0 0 0 3 px var ( --rv-tint ) , inset 0 0 0 4 px var ( --rv-line ) ; 242 /* An illuminated initial for the Chronicle prose — the one ornament a hand-written 243 history book earns. Applies only to the first paragraph of a telling. */ 244 [ data-testid = "chronicle-body" ] p : first-of-type :: first-letter , 245 [ data-testid = "end-chronicle-body" ] p : first-of-type :: first-letter { 246 font-family : var ( --rv-serif ) ; 250 margin : 0.04 em 0.09 em 0 0 ; 251 color : var ( --rv-accent ) ; 255 /* The lifetime slider as a timeline playhead: a thin ruled track + a terracotta 256 marker bar (not a default round handle). */ 258 -webkit- appearance : none ; 262 background : transparent ; 265 input [ type = "range" ] :: -webkit-slider-runnable-track { height : 2 px ; background : var ( --rv-line ) ; } 266 input [ type = "range" ] :: -moz-range-track { height : 2 px ; background : var ( --rv-line ) ; } 267 input [ type = "range" ] :: -webkit-slider-thumb { 268 -webkit- appearance : none ; appearance : none ; 269 width : 5 px ; height : 16 px ; margin-top : -7 px ; 270 background : var ( --rv-accent ) ; border : 0 ; border-radius : 1.5 px ; 272 input [ type = "range" ] :: -moz-range-thumb { 273 width : 5 px ; height : 16 px ; background : var ( --rv-accent ) ; border : 0 ; border-radius : 1.5 px ; 275 input [ type = "range" ] : focus-visible { outline : 2 px solid var ( --rv-accent ) ; outline-offset : 4 px ; } 277 /* Thin tracks: the only "bars" (progress, char ammo). Hairline, never a pill. */ 278 . rv-track { height : 6 px ; background : var ( --rv-line ) ; border-radius : 999 px ; overflow : hidden ; } 279 . rv-track > . rv-fill { height : 100 % ; background : var ( --rv-fg ) ; border-radius : 999 px ; transition : width 0.4 s var ( --rv-ease ) ; } 281 /* Collapsible rails (progressive disclosure) — ▸/▾ markers, hairline-topped. */ 282 . rv-rail { border-top : 1 px solid var ( --rv-line ) ; padding-top : 10 px ; } 283 . rv-summary { cursor : pointer ; list-style : none ; display : flex ; align-items : center ; gap : 8 px ; } 284 . rv-summary :: -webkit-details-marker { display : none ; } 285 . rv-summary :: before { content : "\25B8" ; color : var ( --rv-faint ) ; font-size : 11 px ; line-height : 1 ; } 286 details [ open ] > . rv-summary :: before { content : "\25BE" ; } 287 /* A forced-open rail (desktop Chronicle): the summary is a static label, not a toggle. */ 288 . rv-summary--static { cursor : default ; pointer-events : none ; } 289 . rv-summary--static :: before { display : none ; } 291 /* ── Motion & state primitives ─────────────────────────────────────────── */ 293 /* Tactile press — non-button interactive surfaces (chips, nodes, tabs) give a 294 little under the finger, the same as .rv-btn. Pair with the press target. */ 295 . rv-press { transition : transform var ( --rv-dur- 1 ) var ( --rv-ease ) , background var ( --rv-dur- 1 ) var ( --rv-ease ) , border-color var ( --rv-dur- 1 ) var ( --rv-ease ) , color var ( --rv-dur- 1 ) var ( --rv-ease ) ; } 296 . rv-press : active { transform : scale ( 0.97 ) ; } 298 /* The one spinner — a struck ring, for every "consulting the record…" wait. 299 Sizes to its font (em), inherits ink, so it drops into any text run. */ 301 display : inline-block ; 302 width : 0.9 em ; height : 0.9 em ; 303 border : 2 px solid currentColor ; 304 border-bottom-color : transparent ; 306 animation : rv-spin 0.7 s linear infinite ; 307 vertical-align : -0.12 em ; 309 @ keyframes rv-spin { to { transform : rotate ( 360 deg ) ; } } 311 /* Pending shimmer — a slow ink wash over a block the system is still composing 312 (skeleton lines while the chronicler writes). */ 314 background : linear-gradient ( 90 deg , var ( --rv-line ) 20 % , var ( --rv-hair ) 40 % , var ( --rv-line ) 60 % ) ; 315 background-size : 200 % 100 % ; 316 animation : rv-shimmer 1.5 s ease-in-out infinite ; 319 @ keyframes rv-shimmer { from { background-position : 200 % 0 ; } to { background-position : -200 % 0 ; } } 321 /* Waiting mode — a thin indeterminate sweep under the masthead while a turn is 322 being resolved: the whole board reads as "history is being rewritten." */ 323 . rv-loading-bar { position : relative ; height : 2 px ; overflow : hidden ; background : var ( --rv-line ) ; } 324 . rv-loading-bar :: after { 326 position : absolute ; inset : 0 ; 327 background : var ( --rv-accent ) ; 328 transform-origin : left ; 329 animation : rv-load-sweep 1.15 s var ( --rv-ease ) infinite ; 331 @ keyframes rv-load-sweep { 332 0 % { transform : translateX ( -100 % ) scaleX ( 0.35 ) ; } 333 55 % { transform : translateX ( 40 % ) scaleX ( 0.6 ) ; } 334 100 % { transform : translateX ( 160 % ) scaleX ( 0.35 ) ; } 337 /* A quiet inline hint — teaches without nagging; a faint aside led by a small 339 . rv-hint { font-size : 11 px ; line-height : 1.4 ; color : var ( --rv-faint ) ; } 340 . rv-hint :: before { content : "\203A\00a0\00a0" ; color : var ( --rv-accent ) ; font-weight : 700 ; } 342 /* Entrances. panel-in is transform-only so it never fights .rv-dim's opacity, and 343 it replays on display:none→block (the phone tab switch). fade-in is the soft 344 reveal for whole views (board, end screen). */ 345 . rv-panel { animation : rv-panel-in var ( --rv-dur- 2 ) var ( --rv-ease ) ; } 346 @ keyframes rv-panel-in { from { transform : translateY ( 8 px ) ; } to { transform : translateY ( 0 ) ; } } 347 . rv-fade-in { animation : rv-fade-in var ( --rv-dur- 3 ) var ( --rv-ease ) both ; } 348 @ keyframes rv-fade-in { from { opacity : 0 ; transform : translateY ( 6 px ) ; } to { opacity : 1 ; transform : none ; } } 350 /* Value-change pop — the headline % gives a quick struck-token bump when it moves 351 (the text stays in lockstep with the store; this is purely the flourish). */ 352 . pct-pop { animation : rv-pct-pop var ( --rv-dur- 2 ) var ( --rv-ease-spring ) ; } 353 @ keyframes rv-pct-pop { 0 % { transform : scale ( 1 ) ; } 35 % { transform : scale ( 1.14 ) ; } 100 % { transform : scale ( 1 ) ; } } 355 /* One place that honors reduced-motion for every shared primitive above. */ 356 @ media ( prefers-reduced-motion : reduce ) { 357 . rv-spinner { animation-duration : 1.4 s ; } 359 . rv-loading-bar :: after , 362 . pct-pop { animation : none ; } 363 . rv-fade-in { opacity : 1 ; transform : none ; } 365 . rv-btn : active : not ( : disabled ) { transform : none ; } 366 /* The bar fills jump rather than slide (the width tween is motion too). */ 367 . rv-track > . rv-fill { transition : none ; }
…and the field classes opt into it with font: inherit, whose shorthand carries font-size through. So .rv-field (the figure and Archive-lookup inputs) and .rv-input (the message composer) both render at 15px — one pixel under the threshold, which is all iOS needs.
font: inherit (line 211) pulls the body's 15px onto the field; .rv-input does the same.
assets/css/main.css · 368 lines assets/css/main.css 368 lines · CSS expand all
⋯ 202 lines hidden (lines 1–202) 2 * Main CSS — Revisionist. 3 * Tailwind + Nuxt UI, then the "Spine Console" design tokens: an editorial-minimal 4 * read-model aesthetic. Structure comes from hairlines + whitespace + type, never 5 * from cards / shadows / gradient fills. Semantic color lives in 9px dots + signed 6 * numbers, not box fills. 12 /* ── Design tokens ─────────────────────────────────────────────────────── */ 13 /* Warm "ledger / letterpress" palette: aged paper, sepia ink, hairlines like faint 14 rules, and a single terracotta accent (sealing-wax) for everything interactive. 15 Semantic color is warmed too so a swing reads as ink, never an error state — the 16 accent (bright terracotta) stays distinct from negative valence (deep oxblood). */ 18 --rv-fg : #2b241c ; /* sepia ink — ~12:1 on the cream ground */ 19 --rv-mut : #685c49 ; /* readable quiet — ~5.6:1 */ 20 --rv-faint : #7d6f57 ; /* the contrast floor for readable text — ~4.6:1 */ 21 --rv-hair : #c9b99c ; /* decorative-only (dot rings, ticks); never load-bearing */ 22 --rv-line : #e6dcc8 ; /* sepia hairline */ 23 --rv-bg : #faf6ee ; /* warm paper */ 24 --rv-tint : #f1ebdd ; /* a shade deeper paper */ 25 --rv-accent : #b1532e ; /* terracotta / sealing wax — the one interactive accent */ 26 --rv-ok : #466632 ; /* muted olive-forest */ 27 --rv-warn : #8a5a16 ; /* gold-ochre */ 28 --rv-bad : #7c2d26 ; /* deep oxblood (distinct from the brighter accent) */ 29 --rv-mono : ui-monospace , SFMono-Regular , Menlo , Consolas , monospace ; 30 --rv-serif : "Iowan Old Style" , "Palatino Linotype" , Palatino , Georgia , ui-serif , serif ; 32 /* ── Motion vocabulary ─────────────────────────────────────────────── */ 33 /* One timing language so every animation feels cut from the same cloth: 34 three durations (micro / standard / expressive) and two curves — a gentle 35 settle and the struck-token overshoot the dice & seal already use. */ 36 --rv-dur-1 : 140 ms ; /* micro — hovers, presses, color/position shifts */ 37 --rv-dur-2 : 280 ms ; /* standard — reveals, fades, fills */ 38 --rv-dur-3 : 520 ms ; /* expressive — the dice, the chronicle, node arrivals */ 39 --rv-ease : cubic-bezier ( .2 , .7 , .3 , 1 ) ; /* settle (ease-out) */ 40 --rv-ease-spring : cubic-bezier ( .2 , 1.4 , .4 , 1 ) ; /* the struck-token overshoot */ 44 --rv-fg : #ece4d6 ; /* warm off-white ink */ 46 --rv-faint : #978c77 ; /* ~6:1 on the warm near-black */ 49 --rv-bg : #14110d ; /* warm near-black (brown undertone) */ 51 --rv-accent : #db7a4f ; /* lifted terracotta */ 58 background : var ( --rv-bg ) ; 60 font-family : system-ui , - apple-system , "Segoe UI" , Roboto , sans-serif ; 65 /* Browser-level chrome, warmed to match the page: text selection + scrollbars. */ 66 :: selection { background : color-mix ( in srgb , var ( --rv-accent ) 24 % , transparent ) ; } 67 * { scrollbar-width : thin ; scrollbar-color : var ( --rv-hair ) transparent ; } 68 :: -webkit-scrollbar { width : 11 px ; height : 11 px ; } 69 :: -webkit-scrollbar-track { background : transparent ; } 70 :: -webkit-scrollbar-thumb { background : var ( --rv-hair ) ; border-radius : 999 px ; border : 3 px solid var ( --rv-bg ) ; } 71 :: -webkit-scrollbar-thumb : hover { background : var ( --rv-faint ) ; } 73 /* ── Token helper classes (pair with Tailwind layout utilities) ─────────── */ 74 . rv-fg { color : var ( --rv-fg ) ; } 75 . rv-muted { color : var ( --rv-mut ) ; } 76 . rv-hair-c { color : var ( --rv-hair ) ; } 77 . rv-hover : hover { background : var ( --rv-tint ) ; } 78 . rv-hover-fg { color : var ( --rv-mut ) ; } 79 . rv-hover-fg : hover { color : var ( --rv-fg ) ; } 80 /* First-turn quieting of the empty read-model, so the eye lands on the dock. */ 81 . rv-dim { opacity : 0.5 ; transition : opacity var ( --rv-dur- 2 ) var ( --rv-ease ) ; } 82 @ media ( prefers-reduced-motion : reduce ) { . rv-dim { transition : none ; } } 83 . rv-faint { color : var ( --rv-faint ) ; } 84 . rv-accent { color : var ( --rv-accent ) ; } 85 . rv-bg { background : var ( --rv-bg ) ; } 86 . rv-tint { background : var ( --rv-tint ) ; } 87 . rv-mono { font-family : var ( --rv-mono ) ; } 89 . rv-ok { color : var ( --rv-ok ) ; } 90 . rv-warn { color : var ( --rv-warn ) ; } 91 . rv-bad { color : var ( --rv-bad ) ; } 93 /* Pair with Tailwind border / border-t / border-b / border-r / border-l. */ 94 . rv-line { border-color : var ( --rv-line ) ; } 96 /* A "section" header = an 11px uppercase muted label + a 1px underline. The card. */ 97 /* A text-style modifier only — does NOT force display, so `flex`/`hidden`/`md:inline` 98 utilities on the same element still win (a prior `display:block` here silently 99 broke the spine header's flex layout and the objective era-tag's responsive hide). */ 102 text-transform : uppercase ; 103 letter-spacing : 0.1 em ; 105 color : var ( --rv-mut ) ; 110 border-bottom : 1 px solid var ( --rv-line ) ; 114 /* 9px status / valence dots — the symbol that replaces every colored fill. */ 115 . rv-dot { display : inline-block ; width : 9 px ; height : 9 px ; border-radius : 50 % ; flex : none ; } 116 . rv-dot--ok { background : var ( --rv-ok ) ; } 117 . rv-dot--warn { background : var ( --rv-warn ) ; } 118 . rv-dot--bad { background : var ( --rv-bad ) ; } 119 . rv-dot--mut { background : var ( --rv-faint ) ; } 120 . rv-dot--accent { background : var ( --rv-accent ) ; } 121 . rv-dot--hollow { background : transparent ; border : 1.5 px solid var ( --rv-hair ) ; } 123 /* One serif voice — titles + Chronicle prose ONLY (never UI labels or numerals). */ 124 . rv-serif { font-family : var ( --rv-serif ) ; } 126 /* Inputs: 1px hairline, ~2px radius, accent focus outline. No shadow, no chrome. */ 129 background : var ( --rv-bg ) ; 131 border : 1 px solid var ( --rv-line ) ; 135 /* Engraved double-rule — the composer reads as a framed note (echoes the die). */ 136 box-shadow : inset 0 0 0 3 px var ( --rv-bg ) , inset 0 0 0 4 px var ( --rv-line ) ; 138 . rv-input :: placeholder { color : var ( --rv-faint ) ; } 140 . rv-input : focus-visible { 141 outline : 2 px solid var ( --rv-accent ) ; 142 outline-offset : -1 px ; 143 border-color : transparent ; 146 /* Buttons: text-forward, hairline; primary fills with the one accent. */ 151 background : transparent ; 152 border : 1 px solid var ( --rv-line ) ; 156 transition : border-color var ( --rv-dur- 1 ) var ( --rv-ease ) , color var ( --rv-dur- 1 ) var ( --rv-ease ) , 157 background var ( --rv-dur- 1 ) var ( --rv-ease ) , opacity var ( --rv-dur- 1 ) var ( --rv-ease ) , 158 transform var ( --rv-dur- 1 ) var ( --rv-ease ) ; 160 . rv-btn : hover : not ( : disabled ) { border-color : var ( --rv-fg ) ; } 161 . rv-btn : active : not ( : disabled ) { transform : translateY ( 0.5 px ) scale ( 0.975 ) ; } 162 . rv-btn : focus-visible { outline : 2 px solid var ( --rv-accent ) ; outline-offset : 1 px ; } 163 . rv-btn : disabled { opacity : 0.5 ; cursor : not-allowed ; } 166 background : var ( --rv-accent ) ; 167 border-color : var ( --rv-accent ) ; 169 . dark . rv-btn--primary { color : #0d0d0d ; } 170 . rv-btn--primary : hover : not ( : disabled ) { filter : brightness ( 1.08 ) ; border-color : var ( --rv-accent ) ; } 171 /* Disabled primary reads as a flat, inert neutral — NOT a dimmed accent — so the 172 enabled→disabled jump is unmistakable and the resting CTA never looks faded. */ 173 . rv-btn--primary : disabled { 174 color : var ( --rv-faint ) ; 175 background : transparent ; 176 border-color : var ( --rv-line ) ; 180 /* A real, on-brand focus ring on every bespoke interactive element (the .rv-input 181 handles its own). One accent; never the suppressed default. */ 184 summary : focus-visible , 185 [ role = "button" ] : focus-visible , 186 [ tabindex ] : focus-visible { 187 outline : 2 px solid var ( --rv-accent ) ; 192 /* Invisible 44px hit area for the editorial-tiny icon controls (padding, not size). */ 194 display : inline-flex ; 196 justify-content : center ; 201 /* Ruled "fill-in" field — a single rule like a blank on a ledger form / letter. 202 Used for the short text inputs; the multi-line composer keeps a boxed surface. */ 205 background : transparent ; 208 border-bottom : 2 px solid var ( --rv-hair ) ; 212 transition : border-color var ( --rv-dur- 1 ) var ( --rv-ease ) , background-color var ( --rv-dur- 1 ) var ( --rv-ease ) ; ⋯ 155 lines hidden (lines 214–368) 214 . rv-field :: placeholder { color : var ( --rv-faint ) ; } 216 . rv-field : focus-visible { 218 border-bottom-color : var ( --rv-accent ) ; 219 background : var ( --rv-tint ) ; 222 /* Mobile Safari zooms the viewport when a focused text control's font-size is 223 under 16px (both .rv-field and .rv-input inherit the body's 15px). Pin them to 224 16px on touch devices so tapping a field no longer zooms the page in — without 225 touching the viewport meta (disabling pinch-zoom would be an a11y regression). 226 Desktop keeps the smaller editorial size. */ 227 @ media ( pointer : coarse ) { 234 /* A mounted card-stock surface — a filing card with an engraved inner rule. */ 236 background : var ( --rv-tint ) ; 237 border : 1 px solid var ( --rv-line ) ; 239 box-shadow : inset 0 0 0 3 px var ( --rv-tint ) , inset 0 0 0 4 px var ( --rv-line ) ; 242 /* An illuminated initial for the Chronicle prose — the one ornament a hand-written 243 history book earns. Applies only to the first paragraph of a telling. */ 244 [ data-testid = "chronicle-body" ] p : first-of-type :: first-letter , 245 [ data-testid = "end-chronicle-body" ] p : first-of-type :: first-letter { 246 font-family : var ( --rv-serif ) ; 250 margin : 0.04 em 0.09 em 0 0 ; 251 color : var ( --rv-accent ) ; 255 /* The lifetime slider as a timeline playhead: a thin ruled track + a terracotta 256 marker bar (not a default round handle). */ 258 -webkit- appearance : none ; 262 background : transparent ; 265 input [ type = "range" ] :: -webkit-slider-runnable-track { height : 2 px ; background : var ( --rv-line ) ; } 266 input [ type = "range" ] :: -moz-range-track { height : 2 px ; background : var ( --rv-line ) ; } 267 input [ type = "range" ] :: -webkit-slider-thumb { 268 -webkit- appearance : none ; appearance : none ; 269 width : 5 px ; height : 16 px ; margin-top : -7 px ; 270 background : var ( --rv-accent ) ; border : 0 ; border-radius : 1.5 px ; 272 input [ type = "range" ] :: -moz-range-thumb { 273 width : 5 px ; height : 16 px ; background : var ( --rv-accent ) ; border : 0 ; border-radius : 1.5 px ; 275 input [ type = "range" ] : focus-visible { outline : 2 px solid var ( --rv-accent ) ; outline-offset : 4 px ; } 277 /* Thin tracks: the only "bars" (progress, char ammo). Hairline, never a pill. */ 278 . rv-track { height : 6 px ; background : var ( --rv-line ) ; border-radius : 999 px ; overflow : hidden ; } 279 . rv-track > . rv-fill { height : 100 % ; background : var ( --rv-fg ) ; border-radius : 999 px ; transition : width 0.4 s var ( --rv-ease ) ; } 281 /* Collapsible rails (progressive disclosure) — ▸/▾ markers, hairline-topped. */ 282 . rv-rail { border-top : 1 px solid var ( --rv-line ) ; padding-top : 10 px ; } 283 . rv-summary { cursor : pointer ; list-style : none ; display : flex ; align-items : center ; gap : 8 px ; } 284 . rv-summary :: -webkit-details-marker { display : none ; } 285 . rv-summary :: before { content : "\25B8" ; color : var ( --rv-faint ) ; font-size : 11 px ; line-height : 1 ; } 286 details [ open ] > . rv-summary :: before { content : "\25BE" ; } 287 /* A forced-open rail (desktop Chronicle): the summary is a static label, not a toggle. */ 288 . rv-summary--static { cursor : default ; pointer-events : none ; } 289 . rv-summary--static :: before { display : none ; } 291 /* ── Motion & state primitives ─────────────────────────────────────────── */ 293 /* Tactile press — non-button interactive surfaces (chips, nodes, tabs) give a 294 little under the finger, the same as .rv-btn. Pair with the press target. */ 295 . rv-press { transition : transform var ( --rv-dur- 1 ) var ( --rv-ease ) , background var ( --rv-dur- 1 ) var ( --rv-ease ) , border-color var ( --rv-dur- 1 ) var ( --rv-ease ) , color var ( --rv-dur- 1 ) var ( --rv-ease ) ; } 296 . rv-press : active { transform : scale ( 0.97 ) ; } 298 /* The one spinner — a struck ring, for every "consulting the record…" wait. 299 Sizes to its font (em), inherits ink, so it drops into any text run. */ 301 display : inline-block ; 302 width : 0.9 em ; height : 0.9 em ; 303 border : 2 px solid currentColor ; 304 border-bottom-color : transparent ; 306 animation : rv-spin 0.7 s linear infinite ; 307 vertical-align : -0.12 em ; 309 @ keyframes rv-spin { to { transform : rotate ( 360 deg ) ; } } 311 /* Pending shimmer — a slow ink wash over a block the system is still composing 312 (skeleton lines while the chronicler writes). */ 314 background : linear-gradient ( 90 deg , var ( --rv-line ) 20 % , var ( --rv-hair ) 40 % , var ( --rv-line ) 60 % ) ; 315 background-size : 200 % 100 % ; 316 animation : rv-shimmer 1.5 s ease-in-out infinite ; 319 @ keyframes rv-shimmer { from { background-position : 200 % 0 ; } to { background-position : -200 % 0 ; } } 321 /* Waiting mode — a thin indeterminate sweep under the masthead while a turn is 322 being resolved: the whole board reads as "history is being rewritten." */ 323 . rv-loading-bar { position : relative ; height : 2 px ; overflow : hidden ; background : var ( --rv-line ) ; } 324 . rv-loading-bar :: after { 326 position : absolute ; inset : 0 ; 327 background : var ( --rv-accent ) ; 328 transform-origin : left ; 329 animation : rv-load-sweep 1.15 s var ( --rv-ease ) infinite ; 331 @ keyframes rv-load-sweep { 332 0 % { transform : translateX ( -100 % ) scaleX ( 0.35 ) ; } 333 55 % { transform : translateX ( 40 % ) scaleX ( 0.6 ) ; } 334 100 % { transform : translateX ( 160 % ) scaleX ( 0.35 ) ; } 337 /* A quiet inline hint — teaches without nagging; a faint aside led by a small 339 . rv-hint { font-size : 11 px ; line-height : 1.4 ; color : var ( --rv-faint ) ; } 340 . rv-hint :: before { content : "\203A\00a0\00a0" ; color : var ( --rv-accent ) ; font-weight : 700 ; } 342 /* Entrances. panel-in is transform-only so it never fights .rv-dim's opacity, and 343 it replays on display:none→block (the phone tab switch). fade-in is the soft 344 reveal for whole views (board, end screen). */ 345 . rv-panel { animation : rv-panel-in var ( --rv-dur- 2 ) var ( --rv-ease ) ; } 346 @ keyframes rv-panel-in { from { transform : translateY ( 8 px ) ; } to { transform : translateY ( 0 ) ; } } 347 . rv-fade-in { animation : rv-fade-in var ( --rv-dur- 3 ) var ( --rv-ease ) both ; } 348 @ keyframes rv-fade-in { from { opacity : 0 ; transform : translateY ( 6 px ) ; } to { opacity : 1 ; transform : none ; } } 350 /* Value-change pop — the headline % gives a quick struck-token bump when it moves 351 (the text stays in lockstep with the store; this is purely the flourish). */ 352 . pct-pop { animation : rv-pct-pop var ( --rv-dur- 2 ) var ( --rv-ease-spring ) ; } 353 @ keyframes rv-pct-pop { 0 % { transform : scale ( 1 ) ; } 35 % { transform : scale ( 1.14 ) ; } 100 % { transform : scale ( 1 ) ; } } 355 /* One place that honors reduced-motion for every shared primitive above. */ 356 @ media ( prefers-reduced-motion : reduce ) { 357 . rv-spinner { animation-duration : 1.4 s ; } 359 . rv-loading-bar :: after , 362 . pct-pop { animation : none ; } 363 . rv-fade-in { opacity : 1 ; transform : none ; } 365 . rv-btn : active : not ( : disabled ) { transform : none ; } 366 /* The bar fills jump rather than slide (the width tween is motion too). */ 367 . rv-track > . rv-fill { transition : none ; }