/* bios.css for darkovski.dev
 *
 * The typeface is "Px437 IBM VGA 8x16" from The Ultimate Oldschool PC Font
 * Pack v2.2 by VileR (int10h.org/oldschool-pc-fonts), licensed CC BY-SA 4.0
 * <https://creativecommons.org/licenses/by-sa/4.0/>. It is self-hosted, and
 * the full license text lives in fonts/FONT-LICENSE.TXT.
 */

@font-face {
  font-family: "Px437 IBM VGA 8x16";
  src: url("../fonts/Px437_IBM_VGA_8x16.woff2") format("woff2");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

:root {
  --bios-bg: #0000AA;
  --bios-panel: #0000AA;
  --bios-frame: #BFBFBF;
  --bios-text: #BFBFBF;
  --bios-bright: #FFFFFF;
  --bios-highlight-bg: #BFBFBF;
  --bios-highlight-text: #0000AA;
  --bios-accent: #FFFF55;
  --bios-help-bg: #00AAAA;
  /* One text cell of the 8x16 VGA font. Everything is sized off this. */
  --cell: 16px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  /* Blue, not black. The canvas shows through below the content whenever
     a mobile browser collapses its address bar and the viewport grows,
     which read as a black bar at the bottom of the page. */
  background: var(--bios-bg);
  /* Any anti-aliasing smears a bitmap font's pixels, so it stays off on purpose. */
  -webkit-font-smoothing: none;
  font-smooth: never;
}

body {
  font-family: "Px437 IBM VGA 8x16", "Perfect DOS VGA 437", monospace;
  font-size: var(--cell);
  line-height: var(--cell);
  background: var(--bios-bg);
  color: var(--bios-text);
  /* svh, not dvh. dvh grows as a mobile browser collapses its address
     bar, which pushes the layout taller than the visible area and leaves
     a stretch of empty scroll below the page. svh sizes to the smallest
     viewport state, so the shell never exceeds the screen, and the blue
     canvas covers anything revealed when the bar hides. */
  min-height: 100vh;
  min-height: 100svh;
}

/* The native cell is 8x16. Render 2x on screens with room for it. */
@media (min-width: 1100px) and (min-height: 700px) {
  :root { --cell: 32px; }
}

.screen {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
}

a { color: inherit; text-decoration: none; }

/* ---- Title bar ---- */

.title-bar {
  background: var(--bios-highlight-bg);
  color: var(--bios-highlight-text);
  text-align: center;
  padding: calc(var(--cell) / 4) 0;
}
.title-bar h1 { font-size: inherit; font-weight: normal; }

/* ---- Tab strip ---- */

.tab-strip ul {
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  padding: calc(var(--cell) / 4) var(--cell);
  gap: 0 calc(var(--cell) * 1.5);
}
.tab-strip a {
  display: block;
  padding: calc(var(--cell) / 8) calc(var(--cell) / 2);
}
.tab-strip a.active {
  color: var(--bios-accent);
  background: var(--bios-highlight-text);
}
.tab-strip a:hover,
.tab-strip a:focus-visible {
  color: var(--bios-bright);
  outline: none;
}
.tab-strip a.active:hover,
.tab-strip a.active:focus-visible { color: var(--bios-accent); text-decoration: underline; }

/* ---- Content area, settings left, help right ---- */

.content {
  flex: 1;
  display: flex;
  gap: calc(var(--cell) / 2);
  border-top: 2px solid var(--bios-frame);
  border-bottom: 2px solid var(--bios-frame);
  padding: calc(var(--cell) / 2);
  align-items: stretch;
}
.panes { flex: 0 1 65%; min-width: 0; }
.help-panel { flex: 0 1 35%; min-width: 0; }

/* Panes are all visible without JS so the page reads top to bottom as a
   document. JS collapses them into tab panels. */
.js .pane[hidden] { display: none; }
.pane + .pane { margin-top: var(--cell); }
.js .pane + .pane { margin-top: 0; }

/* Section headings exist for document structure and no-CSS readers. The
   tab strip plays their role visually. Keep them for screen readers. */
.js .pane-title {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.pane-title { font-size: inherit; font-weight: normal; color: var(--bios-bright); margin-bottom: calc(var(--cell) / 2); }

/* ---- Rows shaped like Label ....... [Value] ---- */

.rows { list-style: none; }

/* Group separators inside a settings list, like the section headers a
   real setup screen draws between item blocks. Not selectable. */
.rows .group {
  color: var(--bios-bright);
  margin-top: calc(var(--cell) / 2);
  padding: 0 calc(var(--cell) / 4);
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  padding: 0 calc(var(--cell) / 4);
  cursor: default;
}
.row > a {
  display: flex;
  align-items: baseline;
  /* Full-width basis so the inline help text always wraps to its own line.
     With flex-basis 0 the two would share a line and overlap. */
  flex: 1 1 100%;
  min-width: 0;
  cursor: pointer;
}
/* Help text and sub-menus sit below the label/value line. min-width lets
   them shrink below the dot leader's intrinsic width instead of overflowing. */
.row > .help, .row > .submenu { flex-basis: 100%; min-width: 0; }

.label { color: var(--bios-text); }
.value { color: var(--bios-bright); white-space: nowrap; }

.leader {
  flex: 1;
  overflow: hidden;
  white-space: nowrap;
  min-width: calc(var(--cell) / 2);
  color: var(--bios-text);
}
/* Long enough to fill a full-width row on any screen at any zoom.
   Overflow is clipped, so only running short ever shows. */
.leader::after { content: " ........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ "; }

/* The selection bar. A focused or selected row inverts, exactly like the real thing. */
.js .row { outline: none; }
.js .row.selected,
.js .row:focus-visible {
  background: var(--bios-highlight-bg);
}
/* Child combinators keep the inversion on the row's own line. A plain
   descendant selector would also repaint submenu content blue on blue. */
.js .row.selected > .label, .js .row.selected > .value, .js .row.selected > .leader,
.js .row.selected > a > .label, .js .row.selected > a > .value, .js .row.selected > a > .leader,
.js .row:focus-visible > .label, .js .row:focus-visible > .value, .js .row:focus-visible > .leader,
.js .row:focus-visible > a > .label, .js .row:focus-visible > a > .value, .js .row:focus-visible > a > .leader {
  color: var(--bios-highlight-text);
}

/* Per-row help lives inline in the HTML so it survives with JS off.
   When JS runs it is mirrored into the help panel and hidden here. */
.row .help { color: var(--bios-text); padding: 0 var(--cell) calc(var(--cell) / 2); }
.js .row .help { display: none; }

/* ---- Boot sub-menus ---- */

.submenu {
  border: 2px solid var(--bios-frame);
  padding: calc(var(--cell) / 2);
  margin: calc(var(--cell) / 4) var(--cell) calc(var(--cell) / 2);
  /* Own background so the text stays readable when the parent row is
     selected. Without it the selection bar paints grey under grey text. */
  background: var(--bios-bg);
  color: var(--bios-text);
}
.js .submenu { display: none; }
.js .submenu.open { display: block; }
.submenu h3 { font-size: inherit; font-weight: normal; color: var(--bios-accent); margin-bottom: calc(var(--cell) / 2); }

/* Attached-device rows inside a submenu, shaped like the settings rows.
   Selectors carry .submenu so they outrank the underlined .submenu a rule. */
.devices { list-style: none; margin-top: calc(var(--cell) / 2); }
.submenu .devices a {
  display: flex;
  align-items: baseline;
  min-width: 0;
  color: var(--bios-text);
  text-decoration: none;
  padding: 0 calc(var(--cell) / 4);
}
.submenu .devices .value { color: var(--bios-bright); }
.submenu .devices a:hover, .submenu .devices a:focus-visible {
  background: var(--bios-highlight-bg);
  outline: none;
}
.submenu .devices a:hover .label, .submenu .devices a:hover .value, .submenu .devices a:hover .leader,
.submenu .devices a:focus-visible .label, .submenu .devices a:focus-visible .value, .submenu .devices a:focus-visible .leader {
  color: var(--bios-highlight-text);
}
.submenu p + p { margin-top: calc(var(--cell) / 2); }
.submenu a { color: var(--bios-bright); text-decoration: underline; }
.submenu a:focus-visible, .submenu a:hover {
  background: var(--bios-highlight-bg);
  color: var(--bios-highlight-text);
  outline: none;
}

/* ---- Item Specific Help panel ---- */

.help-panel {
  border: 2px solid var(--bios-frame);
  padding: var(--cell) calc(var(--cell) / 2) calc(var(--cell) / 2);
  position: relative;
  align-self: stretch;
}
.help-panel h2 {
  font-size: inherit;
  font-weight: normal;
  color: var(--bios-bright);
  position: absolute;
  top: calc(var(--cell) / -2);
  left: 50%;
  transform: translateX(-50%);
  background: var(--bios-bg);
  padding: 0 calc(var(--cell) / 2);
  white-space: nowrap;
}
#help-body p + p { margin-top: calc(var(--cell) / 2); }
#help-body a { color: var(--bios-bright); text-decoration: underline; }

/* ---- Key legend ---- */

.key-legend ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  /* Gaps in em so they track the legend's own size rather than the cell,
     which lets the whole strip scale as one unit. */
  gap: 0.25em 0.6em;
  padding: calc(var(--cell) / 4) calc(var(--cell) / 2);
}

/* The full legend needs about 1827px at the doubled cell size, so every
   laptop between 1100 and 1900 wide was orphaning the last hint onto a
   second line. Sizing it against the viewport keeps it to one row at any
   width, and it still caps at the cell size once there is genuinely room.
   Only applied where the cell doubles, since vw units would render it
   microscopic on a phone. */
@media (min-width: 1100px) and (min-height: 700px) {
  .key-legend ul { font-size: min(var(--cell), 1.7vw); }
}
.key-legend kbd { font-family: inherit; color: var(--bios-accent); }

/* ---- Confirmation dialog (Exit Discarding Changes) ---- */

.dialog {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.dialog[hidden] { display: none; }
.dialog-box {
  background: var(--bios-bg);
  border: 2px solid var(--bios-frame);
  box-shadow: calc(var(--cell) / 2) calc(var(--cell) / 2) 0 #000055;
  padding: var(--cell);
  text-align: center;
  color: var(--bios-bright);
}
.dialog-keys { margin-top: var(--cell); }
.dialog-entry { margin-top: var(--cell); }
.dialog-entry input {
  font: inherit;
  background: var(--bios-highlight-bg);
  color: var(--bios-highlight-text);
  border: none;
  outline: none;
  padding: 0 calc(var(--cell) / 4);
  width: 16ch;
}
.dialog-box button {
  font: inherit;
  background: none;
  border: none;
  color: var(--bios-accent);
  cursor: pointer;
  padding: 0 calc(var(--cell) / 2);
}
.dialog-box button:hover, .dialog-box button:focus-visible {
  background: var(--bios-highlight-bg);
  color: var(--bios-highlight-text);
  outline: none;
}

/* ---- Touch. One text cell is too thin a tap target, so pad rows and
   tabs out to about 3 cells on coarse pointers. ---- */

@media (pointer: coarse) {
  .row > a, .row > .label, .row > .leader, .row > .value {
    padding-top: calc(var(--cell) / 2);
    padding-bottom: calc(var(--cell) / 2);
  }
  .tab-strip a {
    padding-top: calc(var(--cell) / 2);
    padding-bottom: calc(var(--cell) / 2);
  }
}

/* Five tabs plus a generous gap need about 432px, so on a phone Exit was
   being orphaned onto a second line. Spreading them across the full width
   with tight padding keeps the strip to one row down to roughly 320px,
   and reads closer to a real setup screen than a wrapped list. */
@media (max-width: 700px) {
  .tab-strip ul {
    justify-content: space-between;
    gap: 0;
    padding-left: calc(var(--cell) / 2);
    padding-right: calc(var(--cell) / 2);
  }
  .tab-strip a {
    padding-left: calc(var(--cell) / 4);
    padding-right: calc(var(--cell) / 4);
  }
}

/* Narrow screens cannot fit a domain, a run of dots and a bracketed
   value on one line. Dropping the leader and letting the value fall to
   its own right aligned line keeps the column tidy instead of ragged. */
@media (max-width: 700px) {
  .submenu .devices a { flex-wrap: wrap; }
  .submenu .devices .leader { display: none; }
  .submenu .devices .value { margin-left: auto; }
}

/* ---- Small screens show help inline under the selected row ---- */

/* With many rows the bottom help panel ends up below the fold, so on
   phones the selected row expands accordion style instead. The inline
   help is the same markup no-JS visitors read, just unhidden. */
@media (max-width: 700px) {
  .content { flex-direction: column; }
  .value { white-space: normal; }
  .js .help-panel { display: none; }
  .js .row.selected > .help {
    display: block;
    /* The whole selected item inverts as a single block, help included,
       the way a real setup screen highlights a row. Painting the help
       blue instead left a sliver of the bar visible beside it. */
    background: transparent;
    color: var(--bios-highlight-text);
    /* Its own background, since the selection bar paints the row grey.
       Negative margins bleed it over the row padding so no grey rim
       shows around the help block. */
    margin: 0;
    padding: calc(var(--cell) / 4) calc(var(--cell) / 2) calc(var(--cell) / 4);
  }
}

/* ---- Boot failure screen, served as 404. Lives here rather than
   inline so the page needs no inline style exception in the CSP. ---- */

/* The boot failure screen keeps its black, canvas included. */
html:has(body.fail-screen) { background: #000; }
body.fail-screen { background: #000; padding: var(--cell); }
.fail { white-space: pre-wrap; }
.fail h1 { font-size: inherit; font-weight: normal; color: var(--bios-bright); }
.fail .err { color: var(--bios-accent); }
.fail a { color: var(--bios-accent); text-decoration: underline; }

/* ---- Flourishes. POST screen, scanlines, cursor blink ---- */

/* POST/boot screen. Built entirely by JS, so no-JS visitors never see it. */
.post {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: #000;
  color: var(--bios-frame);
  padding: var(--cell);
  white-space: pre-wrap;
  cursor: pointer; /* any click skips, mirroring "any key" */
}
.post .post-accent { color: var(--bios-accent); }

@media (prefers-reduced-motion: no-preference) {
  /* Subtle CRT scanlines over everything, including the POST screen. */
  body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 20;
    pointer-events: none;
    background: repeating-linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0.18) 0px,
      rgba(0, 0, 0, 0.18) 1px,
      transparent 1px,
      transparent 3px
    );
  }

  .cursor {
    animation: cursor-blink 1.06s steps(1) infinite; /* ~DOS blink rate */
  }
  @keyframes cursor-blink {
    50% { opacity: 0; }
  }
}

/* ---- Print ----------------------------------------------------------
   This is a resume, so Ctrl+P has to produce something a person can hand
   to someone else. Before this it printed the Main tab alone, because the
   other four panes are hidden by the .js layer, and one help paragraph of
   thirty seven, on a full bleed blue background.

   The fix is mostly to undo .js rather than restyle anything. The no-JS
   fallback is already a complete linear document, which is exactly what
   belongs on paper. ---------------------------------------------------- */

@media print {
  /* Every pane, every help paragraph and every sub-menu, the way a
     visitor without JavaScript already reads the page. */
  .js .pane[hidden],
  .js .row .help,
  .js .submenu {
    display: block !important;
  }

  html, body, .screen {
    background: #fff !important;
    color: #000;
    min-height: 0;
    display: block;
  }

  /* Screen furniture that means nothing on paper. The scanline overlay
     is a fixed body::after, which would otherwise tile onto every sheet. */
  body::after,
  .tab-strip,
  .key-legend,
  .help-panel,
  .post,
  .cursor {
    display: none !important;
  }

  .title-bar {
    background: none;
    color: #000;
    border-bottom: 1pt solid #000;
    text-align: left;
    padding: 0 0 4pt;
  }

  .content { display: block; }
  .panes { flex: none; width: auto; }

  .pane + .pane { margin-top: 12pt; }
  .pane-title {
    color: #000;
    font-weight: bold;
    border-bottom: 0.5pt solid #666;
    margin-top: 10pt;
  }

  .label, .value, .row .help, .submenu, .rows .group {
    color: #000;
    background: none;
  }
  .value { font-weight: bold; }
  .rows .group { font-weight: bold; text-decoration: underline; }
  .submenu { border: 0.5pt solid #999; background: none; }

  /* Leader dots are a resume convention on paper as much as a BIOS one,
     so they stay, just not in CRT grey. */
  .leader { color: #999; }

  /* Whichever row happened to be selected when the print was triggered
     would otherwise carry the inverted selection bar onto the page, so
     one arbitrary line comes out blue on grey. */
  .js .row.selected,
  .js .row.selected > .label, .js .row.selected > .value, .js .row.selected > .leader,
  .js .row.selected > a > .label, .js .row.selected > a > .value, .js .row.selected > a > .leader {
    background: none !important;
    color: #000 !important;
  }

  .submenu h3 { color: #000; font-weight: bold; }

  /* Keep a row and its help together, and never strand a heading at the
     foot of a page. */
  .row, .submenu { break-inside: avoid; page-break-inside: avoid; }
  .pane-title { break-after: avoid; page-break-after: avoid; }

  /* A printed link is useless without its target. The submenu and row
     rules are named explicitly because their on-screen colours outrank a
     bare "a" selector, which left the appended URLs near white on paper. */
  a, .submenu a, .row a { color: #000; text-decoration: underline; }
  .submenu a[href^="http"]::after,
  .row a[href^="http"]::after {
    content: " (" attr(href) ")";
    color: #555;
    font-size: 0.85em;
    text-decoration: none;
    word-break: break-all;
  }

  @page { margin: 14mm; }
}
