274 lines
14 KiB
CSS
274 lines
14 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;
|
|
}
|
|
|
|
/* ── paper ──────────────────────────────────────────────────────────────
|
|
* Light mode is a remap of the sixteen slots above, not a second stylesheet.
|
|
* Every rule in this file goes through those variables, so the whole board
|
|
* inverts by redefining them and nothing below needs to know.
|
|
*
|
|
* Warm greys on sepia, monochrome: this is not an EGA palette and does not
|
|
* pretend to be one. It is the amber-on-paper end of the same machine. The
|
|
* tones are picked by contrast ratio against the ground rather than by eye,
|
|
* and the ordering of the sixteen is preserved, so what was recessive in the
|
|
* dark theme stays recessive here: --dgrey is quiet, --blue is chrome,
|
|
* --yellow carries the hotkeys, --lred is the loudest thing on the screen.
|
|
*
|
|
* Unlike the dark palette these are allowed to depart from EGA fidelity, so
|
|
* --dgrey is lifted to 4.5:1 rather than left at the 2.7:1 its counterpart
|
|
* sits at on black.
|
|
*/
|
|
[data-theme="light"] {
|
|
color-scheme: light;
|
|
--black: #f4ecdc; /* 1.0 the paper */
|
|
--blue: #a0917c; /* 2.6 frames and rules */
|
|
--brown: #887a64; /* 3.6 section marks */
|
|
--lblue: #a0917c; /* 2.6 */
|
|
--dgrey: #756b5b; /* 4.5 dates, numbers, the sysline */
|
|
--green: #6a6053; /* 5.2 quote rule */
|
|
--cyan: #655c50; /* 5.6 list bullets */
|
|
--magenta: #655c50; /* 5.6 */
|
|
--red: #534d43; /* 7.1 */
|
|
--lgreen: #534d43; /* 7.1 code, counts, quoted text */
|
|
--grey: #47423a; /* 8.5 body */
|
|
--lcyan: #38342e; /* 10.5 links and the lightbar */
|
|
--lmagenta:#38342e; /* 10.5 */
|
|
--yellow: #2b2824; /* 12.5 headings, hotkeys, the address */
|
|
--white: #1e1c19; /* 14.5 emphasis */
|
|
--lred: #121210; /* 16.0 new */
|
|
}
|
|
|
|
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 */
|
|
--cell: clamp(7px, 2.32vw, 17px);
|
|
font-size: var(--cell);
|
|
/* 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; }
|
|
|
|
/* ── mobile: read content is one size, not two ───────────────────────────
|
|
* 80 real characters cannot be legible and fit a phone's width at the same
|
|
* time, so below this breakpoint every piece of actual content (prose,
|
|
* the message header, the listing tables) renders at one fixed size
|
|
* instead of inheriting --cell. Box-drawn tables that packed two columns
|
|
* into 80 characters get a second, narrower rendering built for this
|
|
* width instead: see .desktop-only/.mobile-only below, toggled by the
|
|
* matching (min-width: 601px) block further down. The masthead logo and
|
|
* the plain ──── rule dividers are graphics, not content, and are left on
|
|
* --cell exactly as before.
|
|
*/
|
|
@media (max-width: 600px) {
|
|
.msgbody,
|
|
.cloud,
|
|
.pagehdr,
|
|
.sysline,
|
|
.tagline,
|
|
.prompt,
|
|
.mobile-only { font-size: 1rem; }
|
|
|
|
/* The desktop article header packs two fields per row with hand-counted
|
|
padding; the mobile rows (and a page's single Subj row) are one field
|
|
each, so they just need to wrap like normal text instead of running
|
|
off the edge under white-space: pre. The sysline, tagline and the
|
|
bottom command bar are actual words, not a repeating-character rule,
|
|
so they wrap too. */
|
|
.msghdr.mobile-only,
|
|
.pagehdr,
|
|
.sysline,
|
|
.tagline,
|
|
.prompt { white-space: normal; }
|
|
|
|
/* Box drawing embedded in prose (a pull-quote box, if one is ever used)
|
|
is a rigid character grid and must stay at the chrome size or its
|
|
columns stop lining up against their own border characters. */
|
|
.msgbody .box, .msgbody .gb-rule, .msgbody .rule { font-size: var(--cell); }
|
|
|
|
.desktop-only { display: none; }
|
|
}
|
|
@media (min-width: 601px) {
|
|
.mobile-only { display: none; }
|
|
}
|
|
|
|
/* 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: var(--black); 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); }
|