sites/randogoth.com/bbs/static/bbs.css
randogoth c97fcb249b fix: declare color-scheme, so navigation stops flashing white
The mark is fill="currentColor" inside an anchor, so an unstyled paint renders
it in the UA link blue. That is what the flash is: the document painted before
the stylesheet applies, not a theme switch.

Without a color-scheme declaration the browser treats the document as light, so
the canvas it paints between documents, and the scrollbar, are white. Gecko
paints a pending stylesheet's document far more readily than Blink does, which
is why it shows there and not in Chromium.

Dark on flux and bbs, which are dark only; all three branches on raayoni.

The other half of this is a Caddy change in bucur: every response currently
carries no Cache-Control, no ETag and no Last-Modified, so the stylesheet is
refetched on every navigation and the window keeps reopening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-05 22:01:37 +03:00

194 lines
9.7 KiB
CSS

/* randogoth BBS
*
* An ANSI bulletin board, not a DOS application. The distinction is the whole
* point: Turbo Vision is grey chrome, dialogs and a menu bar, which is what the
* previous theme gave. A BBS is 80 columns of CP437 on black, the 16 colour
* EGA palette, numbered areas with hotkeys, and a status line at the bottom.
*
* Everything is sized in `ch` so the screen stays exactly 80 columns wide at
* any font size, the way a terminal does.
*/
@font-face {
font-family: 'Classic Console';
src: url('clacon2.woff2') format('woff2');
font-display: block;
}
/* the IBM EGA/VGA text palette, unaltered */
:root {
/* A board is black by definition; tell the browser so the canvas between
documents and the scrollbar match instead of flashing light. */
color-scheme: dark;
--black: #000000; --blue: #0000aa; --green: #00aa00; --cyan: #00aaaa;
--red: #aa0000; --magenta: #aa00aa; --brown: #aa5500; --grey: #aaaaaa;
--dgrey: #555555; --lblue: #5555ff; --lgreen: #55ff55; --lcyan: #55ffff;
--lred: #ff5555; --lmagenta: #ff55ff; --yellow: #ffff55; --white: #ffffff;
}
html { background: var(--black); }
body {
margin: 0; padding: 0;
background: var(--black);
color: var(--grey);
font-family: 'Classic Console', ui-monospace, monospace;
/* clamp so 80ch always fits the viewport, phone to desktop */
font-size: clamp(7px, 2.32vw, 17px);
/* Exactly one cell per row. Each box row is its own <pre>, so any leading at
all opens a gap between them and the vertical box-drawing characters stop
meeting. Prose gets its leading back on .msgbody below. */
line-height: 1;
-webkit-font-smoothing: none;
font-smooth: never;
}
#screen { width: 80ch; margin: 0 auto; padding: 1.2em 0 2.4em; }
pre, .row { margin: 0; font: inherit; white-space: pre; }
/* ── the masthead ──────────────────────────────────────────────────────── */
/* The masthead is TheDraw's "Syndicate", an ANSI font from the board era, baked
in as static text. It uses exactly three colour levels, so the ramp is three
custom properties and recolouring costs nothing. See bbs.js: the ramp is drawn
at random once per session, which is the one piece of decoration on this site
that is also its subject. */
.logo { margin: 0 0 .2em; }
/* Both planes carry tone. ANSI paints solid areas as a space with a background,
so mapping only the foreground drops most of the mass: the first attempt at
this lost 247 of 355 inked cells and the letters came out hollow. */
.logo .f1 { color: var(--l1, #0000aa); }
.logo .f2 { color: var(--l2, #00aa00); }
.logo .f3 { color: var(--l3, #ffff55); }
.logo .b1 { background: var(--l1, #0000aa); }
.logo .b2 { background: var(--l2, #00aa00); }
.logo .b3 { background: var(--l3, #ffff55); }
.rule { color: var(--blue); }
.sysline { color: var(--dgrey); }
.sysline b { color: var(--grey); font-weight: normal; }
/* ── links behave like a lightbar: inverse on hover, as a BBS selection ─── */
a { color: var(--lcyan); text-decoration: none; }
a:hover, a:focus { background: var(--lcyan); color: var(--black); outline: none; }
/* A lightbar inverts the whole cell. Spans inside a link carry their own colour
(.area is cyan, .key is yellow) and would otherwise keep it against the cyan
highlight, which left the area names cyan on cyan and invisible. */
a:hover *, a:focus * { color: inherit; background: transparent; }
a:visited { color: var(--lcyan); }
/* ── hotkeys ───────────────────────────────────────────────────────────── */
.key { color: var(--yellow); }
.key b { color: var(--white); font-weight: normal; }
/* ── area / message listings ───────────────────────────────────────────── */
.box { color: var(--blue); }
.boxtitle { color: var(--yellow); }
.area { color: var(--lcyan); }
.desc { color: var(--grey); }
.count { color: var(--lgreen); }
.date { color: var(--dgrey); }
.sect { color: var(--brown); }
.num { color: var(--dgrey); }
.new { color: var(--lred); }
/* ── a message ─────────────────────────────────────────────────────────── */
.msghdr { color: var(--dgrey); }
.msghdr b { color: var(--lcyan); font-weight: normal; }
.msghdr .subj { color: var(--yellow); }
.msgbody { color: var(--grey); margin: 1em 0 0; line-height: 1.4; }
.msgbody p { margin: 0 0 1em; }
.msgbody h1, .msgbody h2, .msgbody h3 {
color: var(--yellow); font-size: 1em; font-weight: normal;
text-transform: uppercase; margin: 1.6em 0 .6em; }
.msgbody h1::before, .msgbody h2::before, .msgbody h3::before { content: "── "; color: var(--blue); }
.msgbody ul, .msgbody ol { margin: 0 0 1em; padding-left: 3ch; }
.msgbody li { margin: 0; }
.msgbody ul { list-style: none; }
.msgbody ul > li::before { content: "\00B7 "; color: var(--cyan); }
/* Pictures keep the scanlines the page gave up: on a board, an image was a
monitor showing it. See plugins/crtimages.py for the wrapper. */
.msgbody .crt { position: relative; display: block; margin: 1em 0; line-height: 0;
border: 1px solid var(--blue); }
/* Everything fills the column, including images smaller than it. Upscaling is
nearest-neighbour, so a 320 wide DOS screenshot comes out as bigger pixels
rather than a blurred one, which is the right answer for this material. */
.msgbody .crt img { display: block; width: 100%; height: auto; margin: 0;
border: 0; image-rendering: pixelated; }
/* The scanlines are the monitor it is being shown on. Look straight at it and
they go, which is also the only way to see the picture clean. */
.msgbody .crt::after { content: ""; position: absolute; inset: 0; pointer-events: none;
background: repeating-linear-gradient(to bottom,
rgba(0,0,0,0) 0 2px, rgba(0,0,0,.38) 2px 3px);
opacity: 1; transition: opacity .12s linear; }
.msgbody .crt:hover::after,
.msgbody .crt:focus-within::after { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
.msgbody .crt::after { transition: none; }
}
/* Embeds fill the column like everything else. The ratio is injected per
element by plugins/crtimages.py from each embed's own attributes. */
.msgbody iframe { display: block; width: 100%; height: auto; margin: 1em 0;
border: 1px solid var(--blue); }
.msgbody blockquote { margin: 0 0 1em; padding-left: 2ch; color: var(--lgreen);
border-left: 1px solid var(--green); }
/* Code blocks only. A bare :not() would have been enough until a page put box
drawing in the body: the frames are <pre> too, and were being wrapped, boxed
and turned green as if they were source. */
.msgbody code { color: var(--lgreen); font: inherit; }
.msgbody pre:not([class]), .msgbody pre.highlight {
color: var(--lgreen); font: inherit; white-space: pre-wrap;
border: 1px solid var(--blue); padding: .4em 1ch; margin: 0 0 1em;
overflow-x: auto; }
.msgbody strong { color: var(--white); font-weight: normal; }
.msgbody em { color: var(--lcyan); font-style: normal; }
.msgbody hr { border: 0; border-top: 1px solid var(--blue); margin: 1.4em 0; }
.msgbody a { text-decoration: underline; }
/* the Fidonet tagline that closed every message */
.tagline { color: var(--dgrey); margin-top: 1.6em; }
.tagline .origin { color: var(--green); }
/* ── the prompt. A BBS always leaves you at one. ───────────────────────── */
.prompt { color: var(--grey); margin-top: 1.4em; }
.prompt .cursor {
background: var(--grey); color: var(--grey);
animation: blink 1.06s steps(1) infinite; }
@keyframes blink { 50% { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .prompt .cursor { animation: none; } }
/* the keyword cloud is the one thing that cannot hold an 80 column frame */
.cloud { color: var(--grey); padding: .4em 2ch .6em; line-height: 1.6; }
.cloud a { margin-right: .4ch; }
.cloud .num { margin-right: 1.4ch; }
/* pages */
.bigmail { margin: 0 0 1.4em; }
.bigmail a, .mail { color: var(--yellow); }
.bigmail a:hover { background: var(--yellow); color: var(--black); }
.prompt .hint { color: var(--dgrey); }
/* guestbook */
.gb-rule { margin: 1.5em 0 0; }
/* Box drawing is on the grid wherever it appears, including inside a page body,
which otherwise hands it the 1.4 leading that prose wants. */
.msgbody .box, .msgbody .gb-rule, .msgbody .rule { line-height: 1; }
.gb-body { color: var(--grey); line-height: 1.4; padding: .5em 0 0 4ch; }
/* The form does not try to close each line with a vertical: a text input is not
a character cell and will not line up with one. Top and bottom rules only. */
.gb-form { margin: 0; padding: .8em 0 .4em; }
.gb-row { margin: 0 0 .7em; padding-left: 2ch; line-height: 1.6; }
.gb-row label { display: inline-block; width: 10ch; color: var(--dgrey); }
.gb-row label.second { width: auto; padding: 0 1ch 0 3ch; }
.gb-form input, .gb-form textarea {
font: inherit; background: #000; color: var(--white);
border: 1px solid var(--blue); outline: 0; padding: 0 1ch; }
.gb-form input { width: 22ch; }
.gb-form textarea { width: 58ch; vertical-align: top; resize: vertical; line-height: 1.4; }
.gb-form ::placeholder { color: var(--dgrey); }
.gb-form input:focus, .gb-form textarea:focus { border-color: var(--lcyan); color: var(--lcyan); }
.gb-form button { font: inherit; background: none; border: 0; padding: 0;
color: var(--yellow); cursor: pointer; }
.gb-form button:hover { background: var(--yellow); color: var(--black); }