/* ===========================================================================
   NIGHTBOARD / teletext. CP437 glyphs on a character grid, sixteen ANSI
   colours on black, red chrome. No CSS borders — every rule is a character.
   Font: Px437 IBM VGA 8x16 (Ultimate Oldschool PC Font Pack, VileR,
   CC BY-SA 4.0) — attribution owed in docs/DATA_LICENSES.md.
   Cell is 8x16, so sizes are locked to integer steps or blocks stop tiling.
   =========================================================================== */
@font-face{font-family:"VGA";src:url("../press/Px437_IBM_VGA_8x16.ttf") format("truetype");font-display:block}
:root{
  /* Ground and body text come off pure. #fff on #000 measures 21:1, and a
     WCAG floor is a minimum, not a target: on an emissive panel that pair
     halates, the glyph blooming into the surround so its edge never resolves,
     which is what tires the eye on a screen you work at for an hour. A CRT was
     never pure black either — unlit phosphor is dark grey — so this is the
     more faithful value, not a concession. The Instrument world already knew:
     `--ic-well #0a0c0b`, `--phos-text #e8e6df`.

     Measured on #0a0a0c: grey 8.51:1 (body floor is 7), off-white 16.31:1,
     yellow 18.54:1, bright green 14.91:1, bright red 6.33:1. */
  --k:#0a0a0c; --red:#aa0000; --bred:#ff5555; --brown:#aa5500; --byellow:#ffff55;
  --grey:#aaaaaa; --dgrey:#555555; --white:#ece9e2; --blue:#0000aa; --bgreen:#55ff55;
  --bcyan:#55ffff;
}
/* `--dgrey` is a fill, never a text colour. It measures 2.81:1 on black, and
   the dark half of the ANSI palette is fills by contract (DESIGN.md §3). The
   board used it as a muted-text tier — a habit borrowed from web design, in a
   palette that has no such tier. Sixteen colours, no ramp: emphasis is made by
   promoting a thing to white or to a reversed cell, never by dimming its
   neighbours. Secondary text is grey, and grey reads. */
*{box-sizing:border-box}
html,body{margin:0;background:var(--k);color:var(--grey)}
/* `safe center` centres the board while it fits and falls back to the start
   edge once it does not. Plain `center` pushes the overflow off both sides of
   a narrow screen, and the left half is then unreachable — the page can only
   scroll right. The plain value is declared first so a parser that rejects the
   `safe` keyword keeps today's behaviour instead of dropping the property. */
body{display:flex;flex-direction:column;align-items:center;align-items:safe center;
  padding:clamp(6px,1.5vw,24px) 8px;min-height:100vh}
pre{margin:0;white-space:pre;font-family:"VGA",ui-monospace,monospace;
  -webkit-font-smoothing:none;text-rendering:optimizeSpeed}
#head,#body{font-size:16px;line-height:16px}
/* Teletext double height for the wordmark. Margins are whole cells, and the
   half-height line boxes hold full-height glyphs: each row paints 8px outside
   its box, so the wordmark needs a cell of clearance or its top row lands on
   the status line above it. */
#logo{font-size:16px;line-height:8px;margin:16px 0}
.r{color:var(--red)} .br{color:var(--bred)} .bn{color:var(--brown)}
.y{color:var(--byellow)} .w{color:var(--white)} .gr{color:var(--grey)}
.g{color:var(--bgreen)} .c{color:var(--bcyan)}
/* Reversed cells keep pure white. Halation is an area problem — a run of body
   text blooms, a short reversed label does not — and pure white holds the
   7.75:1 the bar was measured at instead of dropping to 6.40. */
.on{background:var(--red);color:#fff}                  /* 7.75:1 */
.sel{background:var(--blue);color:var(--white)}        /* 13.3:1 */
a{color:inherit;text-decoration:none}
a .t{color:var(--bred)}
a:hover .t,a:focus-visible .t{background:var(--bred);color:#000}
a.btn:hover .on,a.btn:focus-visible .on{background:var(--bred);color:#000}
a:focus-visible{outline:none}
@keyframes blink{50%{opacity:0}}
.bl{animation:blink 1.02s steps(1,end) infinite}
[data-blink="off"] .bl{animation:none}
@media (prefers-reduced-motion:reduce){.bl{animation:none}}
.row{display:block}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
/* The skip link exists for a sighted keyboard user, so it has to become
   visible when it takes focus. Clipped at every state, it was reachable by
   tab and invisible to the only person who needs it. */
a.sr:focus{position:fixed;top:0;left:0;width:auto;height:auto;clip:auto;
  background:var(--red);color:#fff;padding:0 2ch;z-index:60;
  outline:2px solid var(--byellow)}
/* The prompt rides the bottom of the viewport rather than the bottom of the
   page. A board you can only type at after scrolling to the end is not a board
   you can type at. Sticky keeps it in the document flow, so it still sits in
   its own 80-cell column and still pours in with the rest of the screen — it
   simply stops leaving the frame. The rule above it separates it from whatever
   it is currently floating over. */
#prompt{font-size:16px;line-height:16px;color:var(--grey);margin-top:8px;
  position:sticky;bottom:0;background:var(--k);padding:0 0 8px;z-index:5}
/* The caret is the browser's own, tinted. It used to be hidden and stood in
   for by a blinking `_` glyph parked at a fixed column, so it sat forty cells
   away from whatever you were typing and the field itself was invisible. */
#cmd{font:inherit;background:transparent;border:0;color:var(--white);outline:none;
  width:76ch;max-width:100%;caret-color:var(--bred);padding:0}
#cmd::placeholder{color:var(--grey);opacity:1}
#say-line{color:var(--y)}
#connect{position:fixed;inset:0;background:#000;z-index:50;padding:24px;
  font-size:16px;line-height:16px;color:var(--bgreen)}
#connect[hidden]{display:none}
button.tog{font:inherit;background:none;border:0;color:var(--bred);cursor:pointer;padding:0}
button.tog:hover,button.tog:focus-visible{background:var(--bred);color:#000;outline:none}
button.wall-key{font:inherit;line-height:24px;background:var(--red);color:#fff;
  border:0;padding:0 1ch;margin:-4px 0;cursor:pointer}
button.wall-key:hover,button.wall-key:focus-visible{background:var(--bred);color:#000;
  outline:none}

/* --- shared screens (board_screen.html) ---------------------------------- */
#foot{font-size:16px;line-height:16px;color:var(--grey);margin-top:16px}
#body a,#foot a,#head a{color:inherit;text-decoration:none}
#body a .t,#foot a .t{color:var(--bred)}
#body a:hover .t,#body a:focus-visible .t,
#foot a:hover .t,#foot a:focus-visible .t{background:var(--bred);color:#000}
#body a:focus-visible,#head a:focus-visible,#foot a:focus-visible{outline:none}

/* rows: the teletext list primitive, as real markup */
.brow{display:block}
.brow:hover .w,.brow:focus-visible .w{background:var(--red);color:#fff}

/* A field is a run of reversed cells, which is what a field was on a board.
   The 1px hairline these used to carry is the one thing a character grid
   cannot draw, and it is what made the sign-in screen read as a web form in
   costume. White on the dark blue measures 13.29:1. */
.bform label{display:block;color:var(--grey)}
.bform input[type=text],.bform input[type=email],.bform input[type=password],
.bform input[type=search],.bform textarea,.bform select{
  font:inherit;background:var(--blue);color:var(--white);border:0;outline:none;
  padding:0;width:40ch;max-width:100%}
.bform input::placeholder,.bform textarea::placeholder{color:#ccd;opacity:1}
.bform input:focus,.bform textarea:focus,.bform select:focus{
  outline:2px solid var(--byellow);outline-offset:0}
.bform .err{color:var(--bred)}
.bform .help{color:var(--grey)}
/* Two cells, not one. A 16px control is on the grid and under the 24px target
   floor in DESIGN.md §8; 32px is the next legal step, because a fractional
   line-height would take every row beneath the button off the cell. */
.bbtn{display:inline-block;background:var(--red);color:#fff;padding:0 2ch;cursor:pointer;
  font:inherit;border:0;line-height:32px}
.bbtn:hover,.bbtn:focus-visible{background:var(--bred);color:#000;outline:none}

/* --- screens: the grid is 80 cells; nothing may exceed it ---------------- */
/* One width, no per-block scroller and no auto margins. A block that scrolls
   on its own leaves the header, body and footer at three different scroll
   offsets, so the columns stop lining up — on a character grid that is the
   whole design. When 80 cells do not fit, the page scrolls as one piece and
   every row stays in register. Auto margins would beat `align-items` above
   and re-introduce the unreachable overflow, so they are gone too. */
#head,#logo,#body,#foot,#prompt{width:80ch}

/* --- 132: the screen you work in ----------------------------------------- */
/* The other of the board's two widths. 132ch is 1056px, which fits 1280 and up
   without the page scrolling; below that it scrolls as one piece like every
   other board screen. Set by the shell, read by the tags through
   `board_width`, so the rules and the frame agree without every call site
   repeating the number. */
[data-width="132"] #head,[data-width="132"] #body,
[data-width="132"] #foot,[data-width="132"] #prompt{width:132ch}
[data-width="132"] #cmd{width:128ch}

/* --- leading: one cell for art, one and a half for reading --------------- */
/* `line-height` equal to `font-size` is required only where block glyphs tile
   vertically — the masthead's ▄▄▄/▀▀▀, the wordmark, block art. Text rows have
   nothing to tile, and a 1.0 leading ratio on a screen read for an hour is the
   single largest thing making it hard work. 24px is 1.5 cells: still an
   integer number of half-cells, so a following block still lands square.
   `#head` keeps its cell, because it does tile. */
[data-rows="airy"] #body{line-height:24px}
[data-rows="airy"] #body .on{line-height:24px}

/* --- notes screen (native grid) ------------------------------------------ */
.notes-page{display:block}
.write-panel,.notes-panel{display:block;margin:0 0 16px}
.note-form label{display:inline}
.note-form select,.note-form input[type=text],.note-form textarea{
  font:inherit;background:var(--blue);color:var(--white);border:0;
  padding:0;width:44ch;max-width:100%}
.note-form textarea{width:70ch;max-width:100%;white-space:pre-wrap;line-height:1.35;
  border:0;padding:0 1ch}
.note-form select:focus,.note-form input:focus,.note-form textarea:focus{
  outline:2px solid var(--byellow);outline-offset:0}
.md-editor-tabs{display:inline}
.md-tab{font:inherit;background:none;border:0;color:var(--grey);cursor:pointer;padding:0 1ch;
  line-height:32px}
.md-tab.active{background:var(--red);color:#fff}
.md-tab:focus-visible{outline:none;background:var(--bred);color:#000}
.md-preview{display:none;white-space:normal;line-height:1.4;color:var(--white);
  padding-left:1ch;margin:0}
.previewing .md-preview{display:block}
.previewing textarea{display:none}

/* filter row and category tabs */
.note-filter{display:block;margin:0 0 8px}
.note-filter{white-space:normal}
.note-filter input{font:inherit;background:var(--blue);color:var(--white);border:0;
  padding:0;width:18ch;margin-bottom:4px}
.note-filter input[type=date]{width:15ch;color-scheme:dark}
.note-filter input:focus{outline:2px solid var(--byellow);outline-offset:0}
.cat-tabs{display:block;margin:0 0 8px}
/* Two cells, for the same reason `.bbtn` takes two: a one-cell control is 16px
   and DESIGN.md §8 sets the target floor at 24. */
.cat-tab{font:inherit;background:none;border:0;color:var(--grey);cursor:pointer;
  padding:0 1ch;margin-right:1ch;line-height:32px}
.cat-tab .tab-n{color:var(--byellow)}
.cat-tab.active{background:var(--red);color:#fff}
.cat-tab.active .tab-n{color:#fff}
.cat-tab:hover,.cat-tab:focus-visible{outline:none;background:var(--bred);color:#000}

/* the index rows: one line each, body opens beneath */
.notes-section{display:none}
.notes-section.visible{display:block}
.note-card{display:block}
.note-card-head{display:block;cursor:pointer}
.note-card-head:hover .w,.note-card-head:focus-visible .w{background:var(--red);color:#fff}
.note-card-head:focus-visible{outline:none}
.note-card-body{display:none;white-space:normal;line-height:1.45;color:var(--grey);
  padding:4px 0 8px 2ch;margin:0 0 4px 2ch}
.note-card.open .note-card-body{display:block}
.note-card-body p{margin:0 0 .8em}
.note-card-body code{background:#1a0000;color:var(--byellow);padding:0 .3em}
.note-card-body a{color:var(--bcyan);text-decoration:underline}

/* --- list rows with an inline action ------------------------------------- */
.brow{display:inline-block;width:auto}
.brow-act{display:inline;margin-left:1ch}
/* A delete control is not secondary text — it is the one irreversible thing on
   the row, and it was the palette's least visible element at 2.81:1. */
.bdel{font:inherit;background:none;border:0;color:var(--bred);cursor:pointer;padding:0}
.bdel:hover,.bdel:focus-visible{background:var(--bred);color:#000;outline:none}
.bfilter{font:inherit;background:var(--blue);color:var(--white);border:0;
  padding:0;width:24ch}
.bfilter:focus{outline:2px solid var(--byellow);outline-offset:0}

/* --- staff records (native grid) ----------------------------------------- */
/* A queue record is a block of rows that htmx replaces as one piece, so it
   needs to be an element without being a box: no padding, no border, nothing
   that could take it off the cell. */
.brec{display:block}
/* Free text that arrived from a player or a traceback is the one thing on the
   board whose width nobody controls. It folds at the grid instead of widening
   the page, which would take the header and footer out of register with it. */
/* It is a block, so it takes its own lines and its indent has to come from the
   grid rather than from spaces typed in front of it — leading spaces before a
   block box land on a line of their own. 4 + 74 keeps it inside the 80. */
.bwrap{display:block;white-space:pre-wrap;overflow-wrap:break-word;
  margin-left:4ch;width:74ch;max-width:100%}
/* Buttons carry the two decisions on the queue. They read apart by word first
   — the colour only seconds it — because a staff caller working a list at
   speed is reading the verb, not the fill. White on dark red measures 7.75:1,
   yellow on the same ground 5.83:1. */
.bbtn-warn{color:var(--byellow)}
/* A disabled control is exempt from the contrast floor, which is not a reason
   to make it unreadable — the caller still has to read what the guard is
   guarding. Grey on the dark grey fill measures 3.21:1 against black's 2.82. */
.bbtn:disabled{background:var(--dgrey);color:var(--grey);cursor:not-allowed}
.bbtn:disabled:hover{background:var(--dgrey);color:var(--grey)}
/* The guard on an irreversible action. The control stays dead until the caller
   types the record's own id, which is a thing you cannot do by reflex on the
   wrong row — the failure mode `window.confirm` has, since one Enter answers
   it whichever record raised it. */
.bguard{font:inherit;background:var(--blue);color:var(--white);border:0;padding:0;
  width:10ch}
.bguard:focus{outline:2px solid var(--byellow);outline-offset:0}
.bguard.armed{background:var(--red)}
.bnote{font:inherit;background:var(--blue);color:var(--white);border:0;padding:0;
  width:30ch;max-width:100%}
.bnote:focus{outline:2px solid var(--byellow);outline-offset:0}
.bnote::placeholder{color:#ccd;opacity:1}
/* A textarea on a character grid keeps the cell's line-height. A fractional
   one reads slightly better as prose and takes every row beneath it off the
   grid, which on this surface is the whole design. */
.bnote-lg{display:block;width:70ch;max-width:100%;line-height:16px;
  white-space:pre-wrap;margin:0 0 8px}
/* A form is a block box, and a block box on a character grid starts a new line
   whether or not the design asked for one. The decision controls belong on the
   row they decide, so the form itself takes no space. */
.bfrm{display:inline}

/* --- notes workbench (132) ----------------------------------------------- */
/* Rail, list, body: 22 + 48 + 60 cells and a 2-cell gutter. Each pane is its
   own run of pre text, so the cell grid holds inside each and none of them has
   to know how tall the others are.

   This replaces an accordion. An accordion was fine at three notes: click a
   row, the body opens under it, everything below jumps down. At forty it is
   the wrong instrument — you lose your place on every open, and you cannot
   read one note while looking at its neighbour. */
.wb{display:block}
.wb-pane{display:inline-block;vertical-align:top;white-space:pre}
.wb-rail{width:22ch}
.wb-list{width:48ch}
.wb-body-pane{width:60ch;margin-left:2ch}

/* Rail: one category per row, count right-aligned, whole row is the target. */
.wb-cat{display:block;width:100%;font:inherit;background:none;border:0;
  color:var(--grey);cursor:pointer;padding:0;text-align:left;line-height:24px}
.wb-cat:hover,.wb-cat:focus-visible{outline:none;background:var(--bred);color:#000}
.wb-cat.active{background:var(--red);color:#fff}
.wb-cat.active .wb-cat-n{color:#fff}
.wb-cat-n{color:var(--byellow)}

/* List: one note per row. The selected row is a reversed cell, which is how
   the masthead already says "you are here". White on the dark blue is 13.29:1. */
.wb-row{display:block;width:100%;font:inherit;background:none;border:0;
  color:var(--grey);cursor:pointer;padding:0;text-align:left;line-height:24px}
.wb-row:hover .w,.wb-row:focus-visible .w{background:var(--red);color:#fff}
.wb-row:focus-visible{outline:none}
.wb-row.sel{background:var(--blue)}
.wb-row.sel .w,.wb-row.sel .y,.wb-row.sel .gr{background:none;color:#fff}
.wb-row[hidden]{display:none}
.wb-listing{display:block}
.wb-listing.hidden{display:none}

/* Body: the one pane that is prose rather than structure. It aligns with
   nothing, so it comes off the grid — real leading, real measure, wrapped
   text. Everything else on the screen stays on the cell. */
.wb-body{display:none;white-space:normal;line-height:1.55;color:var(--grey)}
.wb-body.visible{display:block}
.wb-body h1,.wb-body h2,.wb-body h3{font:inherit;color:var(--white);margin:0 0 .4em}
.wb-body p{margin:0 0 .9em}
.wb-body ul,.wb-body ol{margin:0 0 .9em 2ch;padding:0}
.wb-body code{background:#1a0000;color:var(--byellow);padding:0 .3em}
.wb-body a{color:var(--bcyan);text-decoration:underline}
.wb-body blockquote{margin:0 0 .9em 2ch;color:var(--grey)}
.wb-meta{display:block;white-space:pre;margin:0 0 12px}

/* --- transcript rows (channel log) --------------------------------------- */
/* A channel log is a timestamp and a line somebody typed, and the second of
   those is a width nobody controls. The timestamp holds its column and the
   message folds inside the cells that are left rather than pushing the screen
   past 80 and taking the header and footer out of register with it.
   2 indent + 20 stamp + 58 message = 80. */
.btrans-m{display:inline-block;vertical-align:top;white-space:pre-wrap;
  overflow-wrap:break-word;width:58ch;max-width:100%}

/* --- guide prose (the /about/ sections) ---------------------------------- */
/* The second place on an OOC surface where the grid does not apply, and for
   the same reason as the first: the notes body pane (DESIGN.md §9b). Structure
   stays on the cell; a paragraph does not. A guide section set at 1.0 leading
   in 80 rigid columns is a guide section nobody finishes.
   66ch is the measure, inside the 80 the screen is drawn at. */
.bprose{display:block;white-space:normal;line-height:1.55;color:var(--grey);
  width:66ch;max-width:100%;margin:8px 0 0}
.bprose h1,.bprose h2,.bprose h3,.bprose h4{font:inherit;color:var(--white);
  margin:1.6em 0 .5em}
.bprose h1:first-child,.bprose h2:first-child{margin-top:0}
/* Sub-headings inside a section are named by a glyph rather than by size: the
   face has one size on this screen, so weight and scale are not available to
   separate them and the board's own answer is a mark in the margin. */
.bprose h2::before,.bprose h3::before{content:"── ";color:var(--red)}
.bprose p{margin:0 0 .9em}
.bprose ul,.bprose ol{margin:0 0 .9em 2ch;padding:0}
.bprose li{margin:0 0 .25em}
.bprose code{background:#1a0000;color:var(--byellow);padding:0 .3em}
.bprose pre{white-space:pre-wrap;color:var(--white);margin:0 0 .9em}
.bprose a{color:var(--bcyan);text-decoration:underline}
.bprose blockquote{margin:0 0 .9em 2ch;color:var(--grey)}
.bprose blockquote p:last-child{margin-bottom:0}
.bprose hr{border:0;color:var(--red);margin:1.2em 0}
.bprose hr::before{content:"────────────────────────────────────────"}
.bprose strong{color:var(--white)}
/* The face is an 8x16 bitmap outline with no italic, so `font-style:italic`
   gets a synthesized oblique: every glyph sheared off its own cell, which on a
   character grid reads as damage rather than as emphasis. Underline is what a
   terminal has always used for the same job. */
.bprose em{color:var(--white);font-style:normal;text-decoration:underline}
.bprose table{border-collapse:collapse;margin:0 0 .9em}
.bprose th{color:var(--brown);text-align:left;font-weight:inherit;padding:0 2ch 0 0}
.bprose td{padding:0 2ch 0 0;vertical-align:top}

/* --- touch: two cells per hit row --------------------------------------- */
/* A 16px row is a comfortable target for a mouse and a coin-toss for a thumb.
   Under a coarse pointer the interactive rows take two cells instead of one,
   which is 32px of hit height and still lands on the grid — a fractional
   line-height would push every following row off the cell and stripe the
   block glyphs. Rows that are only read keep their single cell. */
@media (pointer:coarse){
  .brow,.note-card-head{line-height:32px}
  .cat-tab,.md-tab,.bdel,button.tog,.bbtn{line-height:32px}
  .bguard,.bnote{line-height:32px}
  .wb-cat,.wb-row{line-height:32px}
}
