/* =============================================================================
   os8088.com -- the site, drawn the way the operating system draws itself.

   Every rule here traces back to something in the kernel: 1px black frames,
   white fills, 8px text, a hard 1px drop shadow, XOR inversion for state, and
   a 1px checkerboard desktop. There are no curves, no blur, no gradients, no
   tints and no second font weight. If something here needs a border-radius,
   it is wrong.

   --u is THE pixel unit: 1 OS pixel. Every metric is an integer multiple.

   The page is laid out around the other number the OS gives us: a screen is
   640 pixels across, so a window is 640 pixels across, and a screendump can
   sit in one at 1:1 with nothing scaled and nothing moired. Prose lands on a
   ~72-character measure inside that, which is why the windows are the size
   they are rather than stretched to the viewport with the text stranded in
   the left third of them.
   ========================================================================== */

/* The same face the kernel does: font_init calls int 10h AX=1130h BH=03h and
   copies the VGA BIOS ROM 8x8 character set out of the card. This is that
   bitmap, from the Ultimate Oldschool PC Font Pack (int10h.org), CC BY-SA 4.0.
   Designed at 8px, so use it only at whole multiples of 8. */
@font-face {
  font-family: "Web437 IBM VGA 8x8";
  src: url("/fonts/Web437_IBM_EGA_8x8.woff") format("woff");
  font-display: swap;
  unicode-range: U+0020-007E;   /* the only glyphs os8088 has */
}

:root {
  --u: 2px;

  /* the entire chrome is these two ------------------------------------- */
  --ink: #000000;               /* EGA 0  CBLACK */
  --paper: #ffffff;             /* EGA 15 CWHITE */

  /* desktop dither: 1px checkerboard, (x+y) even = paper (desk.inc) */
  --dither: conic-gradient(var(--ink) 0 25%, var(--paper) 0 50%,
                           var(--ink) 0 75%, var(--paper) 0);
  /* Drawn at 1:1, not at --u: the screendumps on this site are 1:1 too, so the
     desktop behind a window and the desktop inside the picture of a window are
     the same gray and seam into each other invisibly -- which is the whole
     trick the kernel is pulling with desk.inc in the first place. */
  --dither-size: 2px;
  --gray-flat: #808080;

  /* EGA accents -- apps only, never chrome (Minesweeper is the sole user) */
  --c-blue: #0000aa;  --c-green: #00aa00; --c-red: #aa0000;
  --c-lgray: #aaaaaa; --c-dgray: #555555;

  /* typography: the ROM 8x8 face for chrome, system mono for prose ------ */
  --font-px: "Web437 IBM VGA 8x8", ui-monospace, SFMono-Regular, "SF Mono",
             Menlo, Consolas, "DejaVu Sans Mono", monospace;
  --font-body: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "DejaVu Sans Mono", "Liberation Mono", monospace;
  --cell: calc(8 * var(--u));
  --fs: calc(8 * var(--u));      /* chrome: the ROM face at an exact 2x */
  --lh: calc(8 * var(--u));
  /* Prose is set in the system's own mono, which is not a bitmap and so is not
     tied to the 8px grid. It runs a step below the chrome: the chrome is the
     machine talking at its native size, the prose is a person writing about it,
     and the page reads better when the person is not shouting. */
  --fs-body: calc(7 * var(--u));
  --fs-small: calc(6 * var(--u));

  /* chrome metrics, straight from the kernel --------------------------- */
  --hair: var(--u);                      /* every frame and rule is 1px */
  --shadow-off: var(--u);                /* +1/+1, right and bottom, hard */
  --menubar-h: calc(20 * var(--u));      /* MBAR_H */
  --menubar-pad: calc(12 * var(--u));
  --menu-item-h: calc(16 * var(--u));
  --menu-pad-x: calc(8 * var(--u));
  --title-h: calc(16 * var(--u));        /* TITLE_H less the separator */
  --stripe-inset: calc(2 * var(--u));
  --stripe-pitch: calc(2 * var(--u));
  --box: calc(11 * var(--u));            /* close / minimize box, 11x11 */
  --box-inset: calc(7 * var(--u));
  --dock-h: calc(20 * var(--u));
  --icon16: calc(16 * var(--u));

  --gap: calc(12 * var(--u));
  --pad: calc(8 * var(--u));             /* inside a window body */

  /* THE column. A screendump is 640 wide and must never be scaled by anything
     but a whole number, so the page is built around that one measurement: a
     window is exactly one os8088 screen plus its 1px frame, which puts every
     screenshot at 1:1 and lands the prose on a ~72-character measure. Windows
     holding a table or a code listing open wider; a grid of screendumps opens
     to two of them side by side. Nothing is ever full-bleed -- the dither
     around the windows is the desktop, and it is meant to show. */
  --shot: 640px;
  --win: calc(var(--shot) + 2 * var(--hair));
  --win-wide: 800px;             /* the widest code listing, and no wider */
  /* two screendumps abreast, plus the page padding either side */
  --maxw: calc(2 * var(--win) + 3 * var(--gap));

  color-scheme: light;
}

/* A 1-bit OS inverts; it does not re-theme. The OS boots black on white, so
   the site does too -- black on white is the default REGARDLESS of the
   viewer's system dark mode. Dark is opt-in, via the dock's Invert button,
   which stores a choice and stamps data-theme on the root. ------------------ */
:root[data-theme="dark"] {
  --ink: #ffffff; --paper: #000000;
  --c-blue: #5555ff; --c-green: #55ff55; --c-red: #ff5555;
  --c-lgray: #555555; --c-dgray: #aaaaaa;
  color-scheme: dark;
}
:root[data-theme="light"] {
  --ink: #000000; --paper: #ffffff;
  --c-blue: #0000aa; --c-green: #00aa00; --c-red: #aa0000;
  --c-lgray: #aaaaaa; --c-dgray: #555555;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background-color: var(--paper);
  background-image: var(--dither);
  background-size: var(--dither-size) var(--dither-size);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: none;
  text-rendering: optimizeSpeed;
}
@supports not (background: conic-gradient(#000 0 25%, #fff 0)) {
  body { background-image: none; background-color: var(--gray-flat); }
}

img, svg, canvas { max-width: 100%; }
img { height: auto; image-rendering: pixelated; }

a { color: inherit; text-underline-offset: 3px; }
a:focus-visible, button:focus-visible {
  outline: var(--hair) solid var(--ink);
  outline-offset: var(--u);
}

/* =============================================================================
   menu bar -- the site nav is the OS menu bar (menu.inc)
   White field, 1px black rule underneath, 8px titles, pressed = XOR inverted.
   ========================================================================== */
.menubar {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: stretch;
  height: var(--menubar-h);
  background: var(--paper);
  border-bottom: var(--hair) solid var(--ink);
  font-family: var(--font-px);
  font-size: var(--fs);
  user-select: none;
}
.menubar__logo {
  display: grid; place-items: center;
  padding: 0 calc(5 * var(--u));
  color: var(--ink); text-decoration: none;
}
.menubar__logo svg { width: calc(11 * var(--u)); height: calc(11 * var(--u)); display: block; }
.menubar__menu { position: relative; }
.menubar__title {
  height: 100%; padding: 0 var(--menubar-pad);
  border: 0; background: none; color: inherit;
  font: inherit; cursor: default;
}
.menubar__menu:hover > .menubar__title,
.menubar__menu:focus-within > .menubar__title {
  background: var(--ink); color: var(--paper);   /* menu_title_xor */
}

/* pull-down: 1px frame, white fill, 16px item cells, saved-under so no shadow */
.menu {
  position: absolute; top: 100%; left: 0; z-index: 60;
  display: none; min-width: max-content;
  margin: 0; padding: 0; list-style: none;
  background: var(--paper);
  border: var(--hair) solid var(--ink);
  font-family: var(--font-px);
}
.menubar__menu:hover > .menu,
.menubar__menu:focus-within > .menu { display: block; }
.menu a {
  display: block; height: var(--menu-item-h); line-height: var(--menu-item-h);
  padding: 0 var(--menu-pad-x); white-space: nowrap; text-decoration: none;
}
.menu a:hover, .menu a:focus { background: var(--ink); color: var(--paper); }
.menu a[aria-current="page"]::before { content: "* "; }

/* =============================================================================
   .win -- a window: 1px outline + hard 1px drop shadow (wm.inc)
   ========================================================================== */
.win {
  position: relative;
  background: var(--paper);
  color: var(--ink);
  border: var(--hair) solid var(--ink);
  box-shadow: var(--shadow-off) var(--shadow-off) 0 0 var(--ink);
}
/* Title bar: 16 white rows, 6 pinstripes on a 2px pitch inset 3px from the
   frame, a 1px separator below, and a white gap punched through the stripes
   behind the centred title. Only the frontmost window is striped. */
.titlebar {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  height: var(--title-h);
  border-bottom: var(--hair) solid var(--ink);
  background-color: var(--paper);
  background-image: repeating-linear-gradient(to bottom,
      var(--ink) 0 var(--u), transparent var(--u) var(--stripe-pitch));
  background-repeat: no-repeat;
  background-position: var(--stripe-inset) calc(2 * var(--u));
  background-size: calc(100% - 2 * var(--stripe-inset)) calc(11 * var(--u));
  font-family: var(--font-px);
  font-size: var(--fs);
  user-select: none;
}
.titlebar > span {
  background: var(--paper);
  padding: 0 calc(5 * var(--u)) 0 calc(6 * var(--u));
  white-space: nowrap;
  max-width: calc(100% - 2 * (var(--box) + var(--box-inset) + var(--u)));
  overflow: hidden; text-overflow: ellipsis;
}
.closebox, .minbox {
  position: absolute; top: calc(3 * var(--u));
  width: var(--box); height: var(--box);
  border: var(--hair) solid var(--ink);
  background: var(--paper);
  box-shadow: 0 0 0 calc(2 * var(--u)) var(--paper);  /* breaks the stripes */
}
.closebox { left: var(--box-inset); }
.minbox { right: var(--box-inset); }
.minbox::after {                                      /* the 7x1 collapse bar */
  content: ""; position: absolute;
  left: var(--u); top: calc(4 * var(--u));
  width: calc(7 * var(--u)); height: var(--u);
  background: var(--ink);
}
.win__body { padding: var(--pad); }
.win__body > :first-child { margin-top: 0; }
.win__body > :last-child { margin-bottom: 0; }

/* =============================================================================
   layout -- one centred column of windows on the dithered desktop
   ========================================================================== */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: var(--gap); }
main { display: block; }

/* Everything is one window wide by default. The exceptions earn it: a window
   with a table or a code listing in it needs the room, and a grid of
   screendumps takes the full two columns. */
main.wrap > * { max-width: var(--win); margin-inline: auto; }
main.wrap > .wide,
main.wrap > .win:has(table),
main.wrap > .win:has(pre) { max-width: var(--win-wide); }
main.wrap > .grid,
main.wrap > :has(> .grid) { max-width: var(--maxw); }
/* inside a section that opened up for a grid, the prose windows stay put */
:has(> .grid) > :not(.grid) { max-width: var(--win); margin-inline: auto; }

/* Vertical rhythm has two steps and only two: a paragraph step inside a
   window, and a window step between them. */
.stack > * + * { margin-top: var(--gap); }
.win__body.stack > * + * { margin-top: var(--pad); }
.win__body.stack > h2 + *, .win__body.stack > h3 + * { margin-top: 0; }

h1, h2, h3 {
  font-family: var(--font-px);
  /* the pixel face advances a full font-size per character and cannot be
     hyphenated, so long words have to be allowed to break */
  overflow-wrap: break-word;
  font-weight: normal;                  /* one weight; emphasis is inversion */
  line-height: 1.25;
  margin: 0 0 var(--pad);
}
h1 { font-size: calc(12 * var(--u)); }
h2 { font-size: calc(10 * var(--u)); margin-top: calc(14 * var(--u)); }
h3 { font-size: calc(8 * var(--u)); margin-top: calc(10 * var(--u)); }
h2:first-child, h3:first-child { margin-top: 0; }
p, ul, ol, dl { margin: 0 0 var(--pad); max-width: 72ch; }
ul, ol { padding-left: calc(10 * var(--u)); }
li { margin-bottom: calc(2 * var(--u)); }
code, kbd, samp { font-family: var(--font-body); }
code { white-space: nowrap; }

/* emphasis is inversion, never weight (font.inc has exactly one face) */
strong, b { font-weight: normal; background: var(--ink); color: var(--paper); padding: 0 2px; }
em, i { font-style: normal; text-decoration: underline; }

pre {
  overflow-x: auto;
  /* a listing is as wide as its longest line and no wider -- a two-line
     command should not draw a box across the whole window */
  width: max-content; max-width: 100%;
  margin: 0 0 var(--pad);
  padding: var(--pad);
  border: var(--hair) solid var(--ink);
  background: var(--paper);
  font-size: var(--fs-small);
  line-height: 1.45;
}
pre code { white-space: pre; }

/* =============================================================================
   hero
   ========================================================================== */
/* On a wide screen the hero and the screendump are two windows open side by
   side, one column each; below that the row folds and they stack, which is the
   order they are in the markup anyway. The two windows are the same height, so
   the specs and the buttons ride the bottom of the hero rather than leaving a
   pocket of white under them. */
.hero .win__body { display: flex; flex-direction: column; height: 100%; }
.hero .specs { margin-top: auto; }
.hero__tag {
  font-family: var(--font-px);
  font-size: var(--fs-small);
  margin: 0 0 calc(4 * var(--u));
}
.hero__sub { max-width: 72ch; }
.specstrip {
  font-family: var(--font-px);
  font-size: var(--fs-small);
  margin: var(--pad) 0;
  padding: calc(4 * var(--u)) 0;
  border-top: var(--hair) solid var(--ink);
  border-bottom: var(--hair) solid var(--ink);
  /* it wraps rather than scrolls: a spec nobody can see is not a spec */
  line-height: 1.6;
}
.specstrip b { background: none; color: inherit; padding: 0; }

/* The spec block -- six numbers off the build, ruled into a grid instead of run
   together on one line. The rules are not borders: the block is filled with ink
   and the cells are filled with paper, so the 1px gaps between them ARE the
   lines, which is how the OS draws a grid too. */
.specs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--hair);
  max-width: none;
  margin: var(--pad) 0 0;
  padding: var(--hair);
  background: var(--ink);
}
.specs > div {
  display: flex; flex-direction: column;
  padding: calc(6 * var(--u)) calc(5 * var(--u));
  background: var(--paper);
}
/* value over label, and the labels sit on the floor of the row so they line up
   across it even when a cell above is taller */
.specs dd {
  order: -1; margin: 0;
  font-family: var(--font-px); font-size: var(--fs); line-height: 1.25;
}
.specs dt { margin-top: auto; padding-top: calc(2 * var(--u)); font-size: var(--fs-small); }

/* The 16 colors, as the 16 colors: mode 12h's default palette in hardware
   order, black through white. These are the values the card puts out, so --
   alone on this site -- they are literal and do not invert with the theme. */
.pal {
  display: block; height: calc(5 * var(--u));
  margin-top: calc(2 * var(--u));
  border: var(--hair) solid var(--ink);
  background: linear-gradient(to right,
    #000000 0 6.25%,  #0000aa 0 12.5%, #00aa00 0 18.75%, #00aaaa 0 25%,
    #aa0000 0 31.25%, #aa00aa 0 37.5%, #aa5500 0 43.75%, #aaaaaa 0 50%,
    #555555 0 56.25%, #5555ff 0 62.5%, #55ff55 0 68.75%, #55ffff 0 75%,
    #ff5555 0 81.25%, #ff55ff 0 87.5%, #ffff55 0 93.75%, #ffffff 0);
}

.cta { display: flex; flex-wrap: wrap; gap: calc(4 * var(--u)); margin: var(--pad) 0 0; }

/* .btn -- the OS has no push button; this is the faithful extrapolation:
   1px frame, white face, inverts while pressed like every other activation. */
.btn {
  display: inline-block;
  min-height: calc(14 * var(--u));
  padding: calc(3 * var(--u)) var(--menu-pad-x);
  border: var(--hair) solid var(--ink);
  background: var(--paper); color: var(--ink);
  font-family: var(--font-px); font-size: var(--fs-small);
  line-height: calc(8 * var(--u));
  text-align: center; text-decoration: none; cursor: default;
  transition: none;                       /* nothing in this OS animates */
}
/* The pixel face advances a full em per character, so a row of buttons is as
   wide as its longest label times its length; let them share the row evenly
   instead of leaving a ragged tail of white. */
.cta .btn { flex: 1 1 auto; }
.cta .btn:only-child { flex: 0 0 auto; }  /* one button is just a button */
.btn:hover, .btn:active, .btn[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.btn--default {                           /* the Mac System 1 default outline */
  box-shadow: 0 0 0 var(--u) var(--paper), 0 0 0 calc(2 * var(--u)) var(--ink);
}

/* =============================================================================
   figures -- screenshots are the product; they get a window frame each
   ========================================================================== */
/* Side by side, two screendump windows are as tall as the taller one; the
   caption takes up the slack so the leftover is inside the ruled box instead
   of an unexplained band of white under it. */
.shot { margin: 0; display: flex; flex-direction: column; }
.shot figcaption { flex: 1 1 auto; }
/* 640x480 sources: only ever scale them by a whole number, or the 1px
   desktop checkerboard moires under nearest-neighbour upscaling. The column is
   sized so that whole number is 1 -- one CSS pixel per OS pixel, and the @2x
   file behind it for anyone on a retina display. */
.shot img { display: block; width: 100%; max-width: var(--shot); background: var(--paper); }
.shot figcaption {
  padding: var(--pad);
  border-top: var(--hair) solid var(--ink);
  font-size: var(--fs-small);
  line-height: 1.5;
}
.shot figcaption b { display: block; background: none; color: inherit; padding: 0;
                     font-family: var(--font-px); margin-bottom: calc(3 * var(--u)); }

/* Screendumps tile at their own size and centre; they never stretch to fill. */
.grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--win)), var(--win)));
  justify-content: center;
}
.grid--wide { grid-template-columns: minmax(0, var(--win)); }

/* =============================================================================
   tables -- the spec table is a real table; Google lifts these into snippets
   ========================================================================== */
table { width: 100%; border-collapse: collapse; font-size: var(--fs-small);
        line-height: 1.45; margin: 0 0 var(--pad); }
caption { text-align: left; font-family: var(--font-px); padding-bottom: calc(4 * var(--u)); }
th, td { text-align: left; padding: calc(3 * var(--u)) var(--pad) calc(3 * var(--u)) 0;
         border-bottom: var(--hair) solid var(--ink); vertical-align: top; }
th:last-child, td:last-child { padding-right: 0; }
thead th { font-family: var(--font-px); font-weight: normal;
           background: var(--ink); color: var(--paper); padding: calc(2 * var(--u)) var(--pad); }
thead th:first-child { padding-left: calc(3 * var(--u)); }
tbody th { font-weight: normal; white-space: nowrap; }

/* =============================================================================
   dock -- the footer is the dock strip (dock.inc, rows 456..479)
   ========================================================================== */
.dock {
  display: flex; align-items: center; gap: calc(4 * var(--u));
  min-height: var(--dock-h);
  margin-top: calc(16 * var(--u));
  padding: calc(3 * var(--u)) calc(8 * var(--u));
  background: var(--paper);
  border-top: var(--hair) solid var(--ink);
  font-family: var(--font-px);
  font-size: var(--fs-small);
  flex-wrap: wrap;
}
.dock a { text-decoration: none; }
.dock a:hover { background: var(--ink); color: var(--paper); }
.dock__spacer { flex: 1 1 auto; }

/* =============================================================================
   utilities
   ========================================================================== */
.inv { background: var(--ink); color: var(--paper); }
.mono { font-family: var(--font-px); }
.small { font-size: var(--fs-small); }
.note {
  padding: var(--pad);
  border: var(--hair) solid var(--ink);
  font-size: var(--fs-small);
}
.note > :last-child { margin-bottom: 0; }
.skip {
  position: absolute; left: -9999px;
  padding: var(--cell); background: var(--paper); border: var(--hair) solid var(--ink);
}
.skip:focus { left: var(--gap); top: var(--gap); z-index: 100; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

@media (max-width: 720px) {
  :root { --u: 1.5px; --gap: calc(8 * var(--u)); }
  .wrap { padding: calc(8 * var(--u)); }
  /* At one full font-size of advance per character the display sizes stop
     fitting; step them down rather than letting the page scroll sideways. */
  h1 { font-size: calc(9 * var(--u)); }
  h2 { font-size: calc(8 * var(--u)); }
  h3 { font-size: calc(7 * var(--u)); }
  body { font-size: calc(7 * var(--u)); }
  .specs { grid-template-columns: repeat(2, 1fr); }
  .menubar { font-size: calc(7 * var(--u)); }
  .cta .btn { width: 100%; }
}

/* Wide content scrolls inside its own box; the page body never does. */
.win__body table, .shot table { display: block; overflow-x: auto; }
html, body { overflow-x: clip; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* the dock's inversion button: same 1px frame as everything else */
.dock__btn {
  border: var(--hair) solid var(--ink);
  background: var(--paper); color: var(--ink);
  font: inherit; padding: 0 calc(4 * var(--u)); cursor: default;
}
.dock__btn:hover, .dock__btn[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* =============================================================================
   the browser demo
   ========================================================================== */
/* The demo runs at the guest's own 640x480, not blown up to 2x like the
   screenshots: at 1:1 the whole emulated machine -- screen, status line and
   controls -- fits on a laptop without scrolling, and the poster below is
   held to the same box so booting does not resize the page under you. */
.emu__poster { position: relative; margin: 0 auto; width: 640px; max-width: 100%; }
.emu__poster img { display: block; width: 100%; }
.emu__overlay {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--paper) 70%, transparent);
}
.emu__stage { display: flex; justify-content: center; background: var(--paper); }
/* v86 renders into a canvas sized by the guest's video mode; hold it at a
   whole-number scale so the OS's 1px checkerboard does not moire. 640px is
   1:1 with mode 12h -- one CSS pixel per OS pixel, the sharpest it gets. */
.emu__screen { line-height: 0; }
.emu__screen canvas {
  display: block; image-rendering: pixelated;
  /* v86 stamps width/height INLINE every time the guest changes video mode
     (it picks 1280x960 for mode 12h), so these have to be !important or the
     stylesheet loses -- and clamping only the width leaves the inline height
     behind, which stretches the picture. 640px is 1:1 with mode 12h. */
  width: 640px !important;
  height: auto !important;
  max-width: 100%;
}
.emu__screen:focus-visible { outline: calc(2 * var(--u)) solid var(--ink); outline-offset: 0; }
.emu__status {
  margin: 0; padding: var(--cell);
  border-top: var(--hair) solid var(--ink);
  font-family: var(--font-px); font-size: var(--fs-small);
}
.emu__controls {
  display: flex; flex-wrap: wrap; align-items: center; gap: calc(6 * var(--u));
  padding: var(--cell);
  border-top: var(--hair) solid var(--ink);
}
