1/* Chrome only. Everything that looks like art is composed into the canvas and2 shaded there; this file keeps small type crisp and stops the browser behaving3 like a browser.45 The Macintosh was black on white, so the page is paper with ink on it. A6 "night" palette is just the two values swapped — nothing here needs to know7 which way round they are. */89/* Two faces, and the pair is the hierarchy: Tiny5 is the display face, cut on10 the same eight-pixel em Silkscreen was, and DM Mono is everything that is11 read rather than announced. Tiny5 ships one weight, so the contrast between12 a title and a caption is a change of *face* rather than of weight — which is13 the stronger signal anyway, and the one a bitmap face can actually hold. */14@font-face { font-family: Tiny5; src: url(./fonts/tiny5.woff2) format('woff2'); font-display: block; }15@font-face { font-family: DMMono; src: url(./fonts/dm-mono.woff2) format('woff2'); font-display: block; }1617:root {18 --ink: #2B2B22;19 --paper: #E8F4F2;20 --ground: #E8F4F2; /* the backdrop's field; varies per board */21 --s: 2; /* the upscale from the picture buffer */22}2324* { margin: 0; padding: 0; box-sizing: border-box; }2526html, body {27 height: 100%;28 /* The picture covers the whole safe area, so what is left of the page is the29 strip the hardware took — a notch, a rounded corner, a home bar. It carries30 the backdrop rather than the paper, or the edge of the canvas becomes a31 visible seam the moment the two stop being the same colour. */32 background: var(--ground);33 color: var(--ink);34 font-family: DMMono, ui-monospace, monospace;35 overflow: hidden;36 overscroll-behavior: none; /* no pull-to-refresh mid-drag */37 touch-action: none; /* the board owns every gesture */38 -webkit-user-select: none; user-select: none;39 -webkit-touch-callout: none;40 -webkit-tap-highlight-color: transparent;41}42body {43 position: fixed; inset: 0;44 width: 100%; height: 100dvh;45 padding: env(safe-area-inset-top) env(safe-area-inset-right)46 env(safe-area-inset-bottom) env(safe-area-inset-left);47}4849#app { position: absolute; inset: 0; }50#stage { position: absolute; left: 0; top: 0; }5152#board {53 display: block;54 image-rendering: pixelated; /* the upscale lands on whole device pixels */55 image-rendering: crisp-edges;56}5758/* Type layer: same box, full device resolution, never pixelated. */59#type { position: absolute; left: 0; top: 0; pointer-events: none; }6061#ui { position: absolute; inset: 0; pointer-events: none; }62#ui button { pointer-events: auto; }6364/* Left and width are written by the view in picture units: a zone belongs to the65 panel it sits in, and only the menu bar still spans the screen. */66.zone {67 position: absolute;68 display: flex; align-items: center; justify-content: center;69 gap: calc(13px * var(--s));70}71/* Beside the board rather than under it, the panel is a third of the width it72 had, and the spacing that set three readouts across a phone has to come in73 with it. The type does not change size — it is the same panel at the same74 scale, just narrower. */75[data-mode="split"] .zone { gap: calc(6px * var(--s)); }76[data-mode="split"] #acts { gap: calc(5px * var(--s)); }77/* The menu bar is already white; a button background would punch a hole in its78 rule. */79#top { justify-content: flex-start; gap: calc(5px * var(--s)); padding: 0 calc(8px * var(--s)); }80/* A unit of air above and below the word, not two. Six units of type on a 1.2581 line, a unit of padding each side and the hairline each side is eleven and a82 half — which puts the lettered buttons back under the boxed icons, and leaves83 the icons the tallest thing the bar carries. At two they were thirteen and a84 half and the tallest, so the bar's floor was derived from a box that was not85 in fact the one setting it, and BACK and INSTALL hung out of the strip at the86 upscales where the floor bites. BACK is a ghost with no border and never87 looked wrong; INSTALL carries a real one and only appears when the app can be88 installed, which is why it stayed hidden. */89#top button { background: none; padding: calc(1px * var(--s)) calc(4px * var(--s)); }90#top .spacer { flex: 1; }9192/* A boxed icon: square, hard-ruled, with the glyph struck inside it. The box is93 the point — an unboxed glyph on a menu bar reads as a picture of something94 rather than as a thing to press, which is why systems of this vintage drew the95 rectangle every time. */96.iconbtn {97 display: flex; align-items: center; justify-content: center;98 /* Half a unit of air inside the frame, and no more. The glyph is drawn on a99 9-unit grid and only stays on whole pixels while its box is a whole100 multiple of 9, so the padding is the only place a change can come from —101 and it is not free: nine, a half each side and the hairline each side is102 twelve, and twelve is the number the menu bar's floor is derived from, so103 every unit added here is a unit the shortest bar has to grow to keep its104 rule clear of the box. Half a unit is enough to stop the glyph fusing with105 the frame; the clearance below is bought in the bar's height instead, which106 is where it always belonged. */107 padding: calc(0.5px * var(--s)) ;108 border-color: var(--ink) ;109}110.iconbtn svg {111 display: block;112 width: calc(9px * var(--s)); height: calc(9px * var(--s));113 fill: currentColor;114 shape-rendering: crispEdges; /* whole pixels, like the rest of the picture */115}116/* Inverts on press. The plain menu-bar buttons deliberately do not take a117 background, but these already carry a box, so filling it is the QuickDraw118 answer rather than a hole in the rule. */119#top .iconbtn:active { background: var(--ink); color: var(--paper); }120#acts { gap: calc(6px * var(--s)); }121122.stat { display: flex; flex-direction: column; align-items: center; gap: calc(2px * var(--s)); min-width: 0; }123.val { font-family: Tiny5, monospace; font-size: calc(10px * var(--s)); }124.lbl { font-size: calc(5px * var(--s)); letter-spacing: calc(1px * var(--s)); opacity: .72; }125#gauge { justify-content: space-between; gap: 0; }126#gauge .spacer { flex: 1; }127128/* Buttons are QuickDraw buttons: a hard rectangle, inverted while pressed. */129button {130 font-family: DMMono, monospace;131 font-size: calc(6px * var(--s));132 letter-spacing: calc(1.2px * var(--s));133 color: var(--ink);134 background: var(--paper);135 border: calc(1px * var(--s)) solid var(--ink);136 border-radius: 0;137 padding: calc(3.5px * var(--s)) calc(6.5px * var(--s));138 cursor: pointer;139}140button:active, button.armed { background: var(--ink); color: var(--paper); }141button:disabled { opacity: .35; border-color: transparent; }142button.ghost { border-color: transparent; }143144/* Discrete steps, never a smooth fade: an opacity ramp would put in-between145 frames in the middle of a one-bit game. */146@keyframes beat { 0%, 55% { opacity: 1 } 56%, 100% { opacity: 0 } }147.beat { animation: beat 1.4s steps(1, end) infinite; }148/* The one animation the canvas does not own. Told to stop by the same preference149 that stops all the others — and left legible rather than left mid-blink, since150 the prompt is the only thing telling anyone what to do. */151@media (prefers-reduced-motion: reduce) {152 .beat { animation: none; opacity: 1; }153}154155#sheet {156 position: absolute; inset: 0;157 display: flex; flex-direction: column;158 align-items: center; justify-content: center;159 gap: calc(11px * var(--s));160 padding: calc(16px * var(--s));161 background: var(--paper);162 pointer-events: auto;163}164/* On a short screen the stacked groups are taller than the screen is. Two165 columns rather than a scroll: a settings sheet you have to scroll to reach166 CLOSE is a dialog that has stopped being a dialog. */167[data-mode="split"] #sheet {168 display: grid;169 grid-template-columns: auto auto;170 justify-content: center; align-content: center;171 gap: calc(7px * var(--s)) calc(16px * var(--s));172 padding: calc(10px * var(--s));173}174[data-mode="split"] #sheet .credit,175[data-mode="split"] #sheet > button { grid-column: 1 / -1; justify-self: center; }176.group { display: flex; flex-direction: column; align-items: center; }177.grouplbl {178 font-size: calc(5px * var(--s)); letter-spacing: calc(1.6px * var(--s));179 opacity: .6; margin-bottom: calc(5px * var(--s));180}181.row { display: flex; gap: calc(4px * var(--s)); flex-wrap: wrap; justify-content: center; }182183/* A pull-down, drawn the way the rest of the chrome is drawn.184185 A native <select> is the one control the platform will not let you style past186 a point, and one rounded system-blue menu in a screen of hard rectangles187 undoes the whole conceit. This is the QuickDraw menu instead: a hard box, a188 hairline frame, and inversion to show the highlight. */189.menu { position: relative; }190/* An open pull-down dims the rest of the sheet. It lifts the menu off the thing191 it is standing on, and it is the only hint anyone gets that clicking away is a192 thing you can do.193194 A veil of black at less than half alpha, and black in both polarities. On a195 light theme it takes the lightness out of the field and everything on it; on a196 dark one there is no lightness left in the field to take, so it works on the197 light letters instead and knocks them back. Anything tinted to the theme goes198 the wrong way on one side or the other — ink brightens a dark screen, paper199 washes out a light one — and the point is not to hide what is behind but to200 stop it being the thing you are reading. */201.scrim {202 position: absolute; inset: 0;203 z-index: 4;204 background: rgb(0 0 0 / 45%);205}206.menu.open { z-index: 5; }207.menutitle { min-width: calc(84px * var(--s)); text-align: left; }208.menutitle::after { content: ' \25BE'; float: right; } /* the little triangle */209.menulist {210 display: none;211 position: absolute; left: 0; top: calc(100% - 1px * var(--s));212 z-index: 5; min-width: 100%;213 background: var(--paper);214 border: calc(1px * var(--s)) solid var(--ink);215 /* The drop shadow QuickDraw menus cast: solid ink, offset, never blurred. */216 box-shadow: calc(2px * var(--s)) calc(2px * var(--s)) 0 var(--ink);217}218.menu.open .menulist { display: block; }219.menulist button {220 display: block; width: 100%; text-align: left;221 border: 0; background: none;222 padding: calc(3px * var(--s)) calc(7px * var(--s));223 white-space: nowrap;224}225.menulist button:hover, .menulist button.armed { background: var(--ink); color: var(--paper); }226/* A rule under AUTO, because it is a different kind of answer from the rest:227 everything below it is a screen, AUTO is "let the board decide". */228.menulist button[data-auto] {229 border-bottom: calc(1px * var(--s)) solid var(--ink);230}231.credit {232 font-size: calc(4.4px * var(--s)); line-height: 1.9;233 letter-spacing: calc(.5px * var(--s));234 opacity: .5; text-align: center;235 max-width: calc(180px * var(--s));236}