/* ==========================================================================
   spaceadler.com — "NASA Spec Sheet"
   Visual reference: NASA Graphics Standards Manual (1975) crossed with an
   IC datasheet. Absolute clarity, strict grid, hairline rules, no ornament.

   Rules of the house:
     - Helvetica is the historically correct face. It is a system font here,
       so there is no webfont request and no CDN dependency.
     - Red is a signal, not a decoration. Links and warnings only.
     - Everything aligns to the grid. Nothing floats or centers arbitrarily.
     - It mimics paper, but it is not paper: type scales, tables scroll,
       columns collapse. Vintage accuracy never wins over legibility.
   ========================================================================== */

:root {
    /* Surface */
    --paper:      #ffffff;   /* content ground */
    --paper-tint: #f4f4f9;   /* spec boxes, table zebra, code */
    --ink:        #000000;   /* body text and every rule */
    --ink-muted:  #55555f;   /* metadata, captions, secondary */
    --rule:       #000000;
    --rule-hair:  rgba(0, 0, 0, 0.28);
    --grid-line:  rgba(0, 0, 0, 0.05);   /* graph paper under the sheet */

    /* Signal */
    --accent:     #fc3d21;   /* NASA Red — links, warnings, active state */
    --accent-ink: #ffffff;   /* text on an accent fill */

    /* Type */
    --font-sans: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif;
    --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "DejaVu Sans Mono",
                 "Liberation Mono", Consolas, monospace;

    /* Grid */
    --sheet-max:  1100px;
    --meta-col:   210px;   /* left metadata rail on document pages */
    --gutter:     2.5rem;
    --rule-heavy: 4px;
    --rule-mid:   2px;
}

/* The manual is a print artifact, but the screen is not paper. A dark
   inversion keeps the same grid and the same red, and only swaps the ground.

   Three states, not two. `data-theme` is absent until the reader picks one, so
   until then the operating system decides. Once they pick, the attribute wins
   in BOTH directions, which is why the media query is guarded with
   :not([data-theme="light"]) — without that guard, choosing light on a machine
   set to dark would still give dark.

   The token list is written twice on purpose. Plain CSS cannot share one
   declaration block between a media query and an attribute selector, and a
   preprocessor is not worth adding for nine lines. Change one, change both. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --paper:      #0d0d0f;
        --paper-tint: #17171b;
        --ink:        #f2f2f4;
        --ink-muted:  #9a9aa4;
        --rule:       #f2f2f4;
        --rule-hair:  rgba(242, 242, 244, 0.30);
        /* Slightly hotter than the light value: a 5% wash reads as nothing
           against near-black, where it is clearly visible against white. */
        --grid-line:  rgba(242, 242, 244, 0.07);
        --accent-ink: #0d0d0f;
    }
}

:root[data-theme="dark"] {
    --paper:      #0d0d0f;
    --paper-tint: #17171b;
    --ink:        #f2f2f4;
    --ink-muted:  #9a9aa4;
    --rule:       #f2f2f4;
    --rule-hair:  rgba(242, 242, 244, 0.30);
    --grid-line:  rgba(242, 242, 244, 0.07);
    --accent-ink: #0d0d0f;
}

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

/* --------------------------------------------------------------------------
   Search — a command line, not a modal
   --------------------------------------------------------------------------
   It drops in below the sub-strip and pushes the page down rather than floating
   over it. A datasheet does not have pop-ups, and an inline panel needs no focus
   trap, no scroll lock and no backdrop to get right.
   -------------------------------------------------------------------------- */

.search-panel {
    border-bottom: var(--rule-mid) solid var(--rule);
    margin: -2.25rem 0 2.25rem;
    padding: 0.75rem 0;
}

.search-panel[hidden] {
    display: none;
}

.search-field {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}

.search-prompt {
    font-family: var(--font-mono);
    font-weight: 700;
    color: var(--accent);
}

#search-input {
    flex: 1 1 auto;
    min-width: 0;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--rule-hair);
    color: var(--ink);
    font-family: var(--font-mono);
    font-size: 0.95rem;
    padding: 0.15rem 0;
    /* Safari draws a rounded box and an inner shadow on type=search. */
    -webkit-appearance: none;
    appearance: none;
}

#search-input:focus {
    outline: none;
    border-bottom-color: var(--accent);
}

.search-results {
    margin-top: 0.6rem;
}

.search-hit {
    display: block;
    padding: 0.45rem 0;
    border-top: 1px solid var(--rule-hair);
    border-bottom: 0;
    color: var(--ink);
    text-decoration: none;
}

.search-hit:hover {
    color: var(--accent);
}

.search-hit-title {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.search-hit-summary {
    display: block;
    color: var(--ink-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}

.cv-note {
    font-weight: 400;
    color: var(--ink-muted);
    text-transform: none;
    letter-spacing: 0;
}

/* A CV row's left rail holds a date, not an index number, so it needs the width
   for one and must not be forced to uppercase digits. */
.tabular-row .tabular-index {
    white-space: nowrap;
}

.search-none {
    margin: 0;
    color: var(--ink-muted);
    font-family: var(--font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

/* Tag chips are links now, so the generic `a` rule would give them accent text
   and an underline. They are labels that happen to be clickable: ink text in a
   hairline box, and the box takes the accent on hover rather than the text. */
a.tag {
    color: var(--ink);
    border: 1px solid var(--rule-hair);
    text-decoration: none;
    font-weight: 700;
}

a.tag:hover {
    color: var(--accent-ink);
    background: var(--accent);
    border-color: var(--accent);
}

/* Keyboard focus. Nothing here suppresses outlines, so the browser default was
   already doing the job — but a default ring around a bracketed mono control
   looks foreign. :focus-visible rather than :focus, so a mouse click does not
   leave a ring behind on a button that was just pressed. */
:focus-visible {
    outline: var(--rule-mid) solid var(--accent);
    outline-offset: 2px;
}

/* The portrait is round, so a rectangular ring around it is wrong. */
#intro-trigger:focus-visible {
    outline-offset: 4px;
    border-radius: 50%;
}

/* Selecting text is the reader marking something, which is exactly what the
   accent is for. --accent-ink flips with the theme, so the selected words stay
   legible against the fill in both. */
::selection {
    background: var(--accent);
    color: var(--accent-ink);
}

::-moz-selection {
    background: var(--accent);
    color: var(--accent-ink);
}

/* Several components below set `display` on elements that also get toggled
   with the `hidden` attribute. An author `display` rule outranks the user
   agent's `[hidden] { display: none }`, so restate it with weight. */
[hidden] {
    display: none !important;
}

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

body {
    background-color: var(--paper);
    /* Engineering graph paper, 40px module — the sheet the document is drawn
       on, not an element of the document. It is a background-image so the
       --paper colour token still supplies the ground underneath it, and it is
       held far below the weight of a hairline rule: at this alpha it survives
       a squint test and changes body-text contrast by well under a point. */
    background-image:
        repeating-linear-gradient(to right,  var(--grid-line) 0 1px, transparent 1px 40px),
        repeating-linear-gradient(to bottom, var(--grid-line) 0 1px, transparent 1px 40px);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.55;
    margin: 0;
    padding: 0;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* --------------------------------------------------------------------------
   1. Sheet
   -------------------------------------------------------------------------- */

.container {
    max-width: var(--sheet-max);
    margin: 0 auto;
    padding: 2rem 1.5rem 0;
}

/* --------------------------------------------------------------------------
   2. Masthead
   -------------------------------------------------------------------------- */

header.masthead {
    border-bottom: var(--rule-heavy) solid var(--rule);
    padding-bottom: 0.9rem;
    margin-bottom: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-end;
    gap: 1rem 1.5rem;
}

.logo-container {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    min-width: 0;
}

/* A circular cut-out, not a framed photograph. The ring is paper-coloured so
   it punches a clean hole in the background grid; the hairline outside it is
   what actually draws the circle, in the same weight as every other rule on
   the page. The global `img` border is cancelled — it would square the edge. */
/* The portrait is a control now: clicking it plays the intro sequence. The
   button carries no chrome of its own, so what the reader sees is still just
   the photograph. */
#intro-trigger {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    line-height: 0;
    cursor: pointer;
    border-radius: 50%;
    flex: 0 0 auto;
}

.profile-pic {
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 3px var(--paper),
                0 0 0 4px var(--rule-hair);
    object-fit: cover;
    background-color: var(--paper-tint);
    display: block;
    flex: 0 0 auto;
}

/* The wordmark is set lowercase, in Helvetica, like the rest of the site. It is
   a name, not a logotype: no display face, no uppercase, no attempt at the NASA
   "worm". `text-transform` is belt-and-braces — the markup already says
   "spaceadler", so the mark reads correctly with no CSS at all. */
.wordmark {
    color: var(--ink);
    font-weight: 700;
    font-size: clamp(1.25rem, 1rem + 1.1vw, 1.6rem);
    letter-spacing: 0.06em;
    text-transform: lowercase;
    line-height: 1;
    white-space: nowrap;
    /* The generic `a` rule underlines every link. The wordmark is a mark,
       not a link in running text — no rule under it, in any state. */
    border-bottom: 0;
    text-decoration: none;
}

.wordmark:hover {
    color: var(--accent);
    border-bottom: 0;
}

nav.primary {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    flex-wrap: wrap;
    justify-content: flex-start;
}

nav.primary a {
    color: var(--ink);
    text-transform: uppercase;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.11em;
    padding-bottom: 2px;
    border-bottom: var(--rule-mid) solid transparent;
    white-space: nowrap;
}

nav.primary a:hover,
nav.primary a[aria-current="page"] {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* Sub-rule under the masthead: the thin line that follows the thick one.
   Straight out of the 1975 manual's page furniture. */
.masthead-substrip {
    border-bottom: 1px solid var(--rule);
    padding: 0.35rem 0 0.4rem;
    margin-bottom: 2.75rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    font-family: var(--font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.masthead-substrip span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.substrip-tagline {
    flex: 1 1 auto;
    min-width: 0;
}

.substrip-doc {
    flex: 0 0 auto;
}

/* Language, theme and COMM are one component: three reader controls that sit
   together at the right-hand end of the strip. Each prints its CURRENT state
   and moves to the next one, so they behave identically to each other. */
.substrip-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.8rem;
}

/* Face, size, tracking and uppercasing all come from .masthead-substrip, so
   this sets only colour and the underline. The `font` shorthand carries
   neither `text-transform` nor `letter-spacing`, hence the two explicit
   inherits — without them a <button> prints [Light] beside an uppercase [EN]. */
.substrip-btn {
    background: none;
    border: 0;
    border-bottom: 1px solid transparent;
    padding: 0 0 1px;
    margin: 0;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    font-weight: 700;
    color: var(--ink);
    cursor: pointer;
    white-space: nowrap;
    text-decoration: none;
}

/* Group divider. Hairline weight and no interaction: it separates, it is not a
   control, and aria-hidden keeps it out of the reading order. */
.substrip-sep {
    color: var(--rule-hair);
    user-select: none;
}

.substrip-btn:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* COMM live is a state, not a hover: it stays marked while the audio plays. */
#comm-toggle[aria-pressed="true"] {
    color: var(--accent);
    border-bottom-color: var(--accent);
}




/* --------------------------------------------------------------------------
   3. Typography
   -------------------------------------------------------------------------- */

h1, h2, h3, h4, h5, h6 {
    color: var(--ink);
    font-weight: 700;
    text-align: left;
    letter-spacing: -0.01em;
    line-height: 1.08;
    text-wrap: balance;
}

/* Massive, left-aligned, followed by a heavy rule. */
h1 {
    font-size: clamp(2.1rem, 1.2rem + 4.2vw, 4rem);
    text-transform: uppercase;
    letter-spacing: -0.02em;
    margin: 0 0 1rem;
    hyphens: auto;
    overflow-wrap: break-word;
}

h2 {
    font-size: clamp(1.3rem, 1.05rem + 1.1vw, 1.75rem);
    text-transform: uppercase;
    letter-spacing: 0.01em;
    margin: 3rem 0 0.9rem;
    padding-bottom: 0.4rem;
    border-bottom: var(--rule-mid) solid var(--rule);
}

/* A heading that sits directly on top of an index prints two rules: its own
   2px underline and the list's 4px top rule, a hair apart. Same defect as
   .doc-head:last-child. Drop the heading's own rule and close the gap, so the
   list's heavy rule reads as the heading's underline. */
h2:has(+ .tabular-list) {
    border-bottom: 0;
    padding-bottom: 0;
    margin-bottom: 0.55rem;
}

h3 {
    font-size: 1.1rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 2rem 0 0.6rem;
}

h4, h5, h6 {
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    margin: 1.5rem 0 0.5rem;
}

p {
    margin: 0 0 1.1rem;
    max-width: 68ch;   /* measure control — the one thing print gets right */
}

a {
    color: var(--accent);
    text-decoration: none;
    font-weight: 700;
    border-bottom: 1px solid currentColor;
}

a:hover {
    color: var(--ink);
}

strong, b {
    font-weight: 700;
}

hr {
    border: 0;
    border-top: var(--rule-heavy) solid var(--rule);
    margin: 2.5rem 0;
}

ul, ol {
    max-width: 68ch;
    padding-left: 1.2rem;
    margin: 0 0 1.1rem;
}

li {
    margin-bottom: 0.4rem;
}

blockquote {
    border-left: var(--rule-heavy) solid var(--accent);
    margin: 1.5rem 0;
    padding: 0.2rem 0 0.2rem 1.2rem;
    font-style: normal;
    color: var(--ink-muted);
    max-width: 68ch;
}

blockquote p:last-child {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   3b. Task lists — a hardware sign-off sheet
   -------------------------------------------------------------------------- */

/* Goldmark turns `- [ ]` into an ordinary <li> holding a disabled checkbox.
   The class names it hangs on the list and the item have moved between
   goldmark versions, so every selector here keys off the input itself, which
   has not. A native checkbox is a UI widget from another century of design
   language; what belongs on a spec sheet is the bracket pair an engineer
   would have inked by hand. */

/* The marker is drawn by the input, so the list's own bullet and its indent
   would only push the column off the prose measure. */
.content ul:has(li > input[type="checkbox"]) {
    list-style: none;
    padding-left: 0;
}

.content li:has(> input[type="checkbox"]) {
    list-style: none;
}

.content input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    /* inline-block, not inline: it stops the parent's line-through from
       being drawn across the bracket glyph on a completed item. */
    display: inline-block;
    width: 3ch;   /* exactly the three cells the glyph occupies */
    margin: 0 0.6ch 0 0;
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--font-mono);
    font-size: 0.9em;
    color: var(--ink-muted);
    vertical-align: baseline;
}

.content input[type="checkbox"]::before {
    content: "[ ]";
}

/* The signature is the one thing on the sheet worth a signal colour. */
.content input[type="checkbox"]:checked {
    color: var(--accent);
}

.content input[type="checkbox"]:checked::before {
    content: "[x]";
}

/* Crossed off. Isolated in its own rule on purpose: where :has() is not
   supported the whole rule drops and a completed item simply reads as
   unstruck prose, still correctly marked [x] by the block above. */
.content li:has(> input[type="checkbox"]:checked) {
    color: var(--ink-muted);
    text-decoration: line-through;
    text-decoration-thickness: 1px;
}

/* --------------------------------------------------------------------------
   4. Code — mono, because it is code
   -------------------------------------------------------------------------- */

code {
    background: var(--paper-tint);
    padding: 0.1rem 0.3rem;
    border: 1px solid var(--rule-hair);
    font-size: 0.87em;
    color: var(--ink);
    font-family: var(--font-mono);
}

pre {
    background: var(--paper-tint);
    padding: 1rem;
    border: 1px solid var(--rule);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: 0.83rem;
    line-height: 1.5;
}

pre code {
    background: none;
    padding: 0;
    border: none;
    font-size: inherit;
}

/* --------------------------------------------------------------------------
   4b. Code listings — a datasheet prints code in one ink
   --------------------------------------------------------------------------
   Hugo emits Chroma classes (noClasses = false in hugo.toml), so the palette
   lives here rather than inline. Deliberately near-monochrome: weight and
   italics carry the syntax, and the accent marks only literals — the values a
   reader actually scans a listing for. A ten-colour theme would put more colour
   in one code block than the entire rest of the site uses.
   -------------------------------------------------------------------------- */

/* A listing is a figure: a header strip that names it, then the code. The strip
   carries the same weight as a spec box title, so a code block and a
   specification read as the same family of object. */
.listing {
    margin: 1.5rem 0;
    border: 1px solid var(--rule);
    background: var(--paper-tint);
}

.listing-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.58rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    padding: 0.3rem 0.6rem;
}

.listing-copy {
    background: none;
    border: 1px solid var(--paper);
    color: var(--paper);
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    padding: 0.05rem 0.35rem;
    cursor: pointer;
    white-space: nowrap;
}

.listing-copy:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}

.listing .highlight {
    margin: 0;
    border: 0;
    background: none;
    overflow-x: auto;
}

.highlight {
    margin: 1.5rem 0;
    border: 1px solid var(--rule);
    background: var(--paper-tint);
    overflow-x: auto;
}

.highlight pre {
    margin: 0;
    padding: 0.85rem 0.9rem;
    background: none;
    border: 0;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    line-height: 1.55;
    tab-size: 4;
}

/* Comments: present, subordinate, never competing with the code. */
.chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs {
    color: var(--ink-muted);
    font-style: italic;
}

/* Preprocessor lines are structure, not prose: upright and muted. */
.chroma .cp, .chroma .cpf {
    color: var(--ink-muted);
    font-style: normal;
}

/* Keywords and types: weight alone. */
.chroma .k, .chroma .kc, .chroma .kd, .chroma .kn,
.chroma .kp, .chroma .kr, .chroma .kt {
    color: var(--ink);
    font-weight: 700;
}

/* Literals — the numbers and strings — are what the accent is for here. */
.chroma .s, .chroma .s1, .chroma .s2, .chroma .sa, .chroma .sb, .chroma .sc,
.chroma .dl, .chroma .sd, .chroma .se, .chroma .sh, .chroma .si, .chroma .sx,
.chroma .sr, .chroma .ss,
.chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi, .chroma .mo,
.chroma .il {
    color: var(--accent);
}

/* Function and declaration names: upright, bold, ink. */
.chroma .nf, .chroma .nd, .chroma .nc {
    color: var(--ink);
    font-weight: 700;
}

/* Everything else stays body ink: operators, punctuation, plain identifiers. */
.chroma .o, .chroma .p, .chroma .n, .chroma .nv, .chroma .na, .chroma .nl {
    color: var(--ink);
}

.chroma .err {
    color: var(--accent);
    font-weight: 700;
    text-decoration: underline;
}

/* Line numbers, if a block ever turns them on. */
.chroma .lnt, .chroma .ln {
    color: var(--ink-muted);
    user-select: none;
}

/* --------------------------------------------------------------------------
   4c. Diagrams — inline SVG, drawn in the site's own ink
   --------------------------------------------------------------------------
   The shapes carry no colour of their own. Everything is set here from the
   tokens, so a diagram inverts with the theme like the rest of the page and a
   contributor cannot hardcode a colour that survives into dark mode.
   -------------------------------------------------------------------------- */

.diagram {
    margin: 1.75rem 0;
}

.diagram-svg {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--rule);
    background: var(--paper-tint);
    padding: 0.75rem;
    /* Hairlines that stay hairlines: strokes are given in viewBox units, so
       without this they thicken as the figure scales up. */
    vector-effect: non-scaling-stroke;
}

/* Strokes and fills for anything drawn inside. */
.diagram-svg line,
.diagram-svg path,
.diagram-svg rect,
.diagram-svg circle,
.diagram-svg ellipse,
.diagram-svg polyline,
.diagram-svg polygon {
    fill: none;
    stroke: var(--ink);
    stroke-width: 2;
    stroke-linecap: square;
}

/* class="signal" marks the one path a figure is actually about. */
.diagram-svg .signal {
    stroke: var(--accent);
    stroke-width: 3;
}

.diagram-svg .fill {
    fill: var(--paper);
}

.diagram-svg .hair {
    stroke: var(--rule-hair);
    stroke-width: 1;
}

.diagram-svg .dashed {
    stroke-dasharray: 6 4;
}

.diagram-svg text {
    fill: var(--ink);
    stroke: none;
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 700;
    text-anchor: middle;
    letter-spacing: 0.06em;
}

.diagram-svg text.label {
    fill: var(--ink-muted);
    font-size: 11px;
    font-weight: 400;
}

.diagram-svg .arrow {
    fill: var(--ink);
    stroke: none;
}

/* --------------------------------------------------------------------------
   5. Tables — electrical characteristics, not blog tables
   -------------------------------------------------------------------------- */

table {
    border-collapse: collapse;
    width: 100%;
    min-width: 30rem;
    font-size: 0.82rem;
    line-height: 1.4;
}

/* Standalone markdown tables get their own frame; nested ones inherit it. */
/* Scrolling lives on the wrapper, not on the table. Put `display: block` on the
   table itself and it stops laying out as one: the rows size to their content
   while the box stretches to 100%, so every rule and every zebra band stops
   short of the border. The wrapper comes from
   layouts/_markup/render-table.html. */
.table-scroll {
    margin: 1.5rem 0;
    border: 1px solid var(--rule);
    overflow-x: auto;
}

.table-scroll table {
    /* No border of its own: the wrapper draws it, and doubling them makes a
       2px rule down one side only. */
    border: 0;
}

thead th {
    background: var(--ink);
    color: var(--paper);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    font-size: 0.68rem;
    font-weight: 700;
    text-align: left;
    padding: 0.45rem 0.6rem;
    border-right: 1px solid var(--paper);
    white-space: nowrap;
}

thead th:last-child {
    border-right: 0;
}

tbody td {
    padding: 0.45rem 0.6rem;
    border-top: 1px solid var(--rule-hair);
    border-right: 1px solid var(--rule-hair);
    vertical-align: top;
}

tbody td:last-child {
    border-right: 0;
}

tbody tr:nth-child(even) {
    background: var(--paper-tint);
}

/* Numbers read as data. */
tbody td:not(:first-child) {
    font-family: var(--font-mono);
    font-size: 0.78rem;
}

/* --------------------------------------------------------------------------
   6. Spec box — front matter rendered as a literal specification table
   -------------------------------------------------------------------------- */

/* The frame around the box. `--rule` inverts with the theme (black on light,
   near-white on dark), so the border used to change colour underneath the
   title bar. NASA Red is fixed, so the frame and its header are now one mark
   in both themes. The hairlines *between* rows stay `--rule-hair`: they
   separate data, they do not bound the box. */
.specbox {
    border: 1px solid var(--accent);
    background: var(--paper-tint);
    margin: 0 0 1.75rem;
}

/* The header strip of the box. It was `--ink`, which is black on a light
   ground but near-WHITE in dark mode, where the token inverts. NASA Red is
   fixed in both, so the bar now reads the same either way. `color` stays
   `--paper`: that is white on light and near-black on dark, which is exactly
   the contrast each ground needs against the red. */
.specbox-title {
    background: var(--accent);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    padding: 0.35rem 0.6rem;
}

.specbox dl {
    margin: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}

.specbox dt {
    font-family: var(--font-mono);
    font-size: 0.58rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--ink-muted);
    padding: 0.5rem 0.6rem 0;
    border-top: 1px solid var(--rule-hair);
}

.specbox dt:first-of-type {
    border-top: 0;
}

.specbox dd {
    margin: 0;
    padding: 0.05rem 0.6rem 0.5rem;
    font-family: var(--font-mono);
    /* 0.76rem put "MISRA C:2012 · BSSC(2000)1" at 190.3px against 188.8px of
       usable rail — over by 1.5px, so it wrapped. Dropping a step buys about
       9px of headroom, which matters because monospace metrics differ between
       platforms and a 1px margin would wrap again on someone else's machine.
       Same size as the document-control footer values, so it is not a new
       number in the design. */
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.35;
    overflow-wrap: break-word;
}

.specbox dd a {
    font-weight: 700;
}

.status-dot {
    display: inline-block;
    width: 0.5em;
    height: 0.5em;
    background: var(--accent);
    margin-right: 0.4em;
    vertical-align: baseline;
}

/* --------------------------------------------------------------------------
   7. Document layout — metadata rail + content column
   -------------------------------------------------------------------------- */

.doc {
    display: grid;
    grid-template-columns: var(--meta-col) minmax(0, 1fr);
    gap: 0 var(--gutter);
    align-items: start;
}

.doc-head {
    grid-column: 1 / -1;
    border-bottom: var(--rule-heavy) solid var(--rule);
    margin-bottom: 2rem;
    padding-bottom: 0.75rem;
}

/* On a page whose only content is its header — an index that carries a grey
   summary and nothing else — the header's bottom rule and the index's top rule
   land one after the other, separated by 2rem of nothing. Two heavy rules, no
   content between them. Drop the header's own rule and let the list supply the
   single one. */
.doc-head:last-child {
    border-bottom: 0;
    margin-bottom: 0;
}

/* On the 404 there is nothing between the header and the single line of body,
   so the header's heavy rule and the sheet's own furniture stack into two rules
   around one link. Drop the header's. */
.doc-404 .doc-head {
    border-bottom: 0;
    margin-bottom: 1.25rem;
}

/* Headings carry a top margin so they open a gap against the text above them.
   The first element in a body has no text above it — only the doc-head, which
   already supplies its own 2rem — so that margin stacks and the page appears to
   start one line low. Most visible on /hardware/, which opens on an h3. */
.content > :first-child {
    margin-top: 0;
}

.doc-head .eyebrow {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent);
    margin: 0 0 0.7rem;
    font-weight: 700;
}

/* The lede is a subtitle, not body prose: it is one sentence and it should
   land as one line on a desktop sheet. 60ch broke the ~104-character German
   summary onto a second line, so the measure is widened past it and then
   clamped to the column — the 68ch measure the running text uses is untouched.
   Below about 1000px it wraps, which is the correct behaviour there. */
.doc-head .lede {
    font-size: clamp(1rem, 0.94rem + 0.3vw, 1.15rem);
    color: var(--ink-muted);
    margin: 0.5rem 0 0;
    max-width: min(100%, 105ch);
    text-wrap: pretty;
}

.doc-rail {
    position: sticky;
    top: 1.5rem;
    min-width: 0;
}

.doc-body {
    min-width: 0;
}

/* No rail, but the same left edge. Column 1 is simply left empty. */
.doc-body-norail {
    grid-column: 2;
}

/* --------------------------------------------------------------------------
   8. Index rows — a parts list, one row per document
   -------------------------------------------------------------------------- */

.tabular-list {
    margin-top: 0;
    border-top: var(--rule-heavy) solid var(--rule);
}

.tabular-row {
    display: grid;
    grid-template-columns: var(--meta-col) minmax(0, 1fr);
    gap: 0 var(--gutter);
    padding: 1.35rem 0;
    border-bottom: 1px solid var(--rule-hair);
    align-items: start;
}

.tabular-row:hover {
    background: var(--paper-tint);
}

.tabular-meta {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-muted);
    line-height: 1.7;
    min-width: 0;
}

.tabular-index {
    display: block;
    color: var(--accent);
    font-weight: 700;
}

.tabular-status {
    display: block;
}

/* A document with no version in this language. Outlined rather than filled:
   it is a note about the row, not a warning about it. */
.lang-badge {
    display: inline-block;
    vertical-align: 0.15em;
    margin-left: 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.55rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-muted);
    border: 1px solid var(--rule-hair);
    padding: 0.1rem 0.32rem;
    white-space: nowrap;
}

.tabular-content {
    min-width: 0;
}

.tabular-content h3 {
    margin: 0 0 0.4rem;
    font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
    letter-spacing: 0.02em;
}

.tabular-content h3 a {
    color: var(--ink);
    border-bottom: 0;
}

.tabular-content h3 a:hover {
    color: var(--accent);
}

.tabular-content p {
    margin: 0;
    color: var(--ink-muted);
    font-size: 0.93rem;
    max-width: 68ch;
}

.tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-top: 0.6rem;
}

.tag {
    font-size: 0.58rem;
    font-family: var(--font-mono);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink);
    border: 1px solid var(--rule-hair);
    padding: 0.12rem 0.35rem;
    white-space: nowrap;
}

.tabular-meta .tags {
    margin-top: 0;
}

/* --------------------------------------------------------------------------
   9. Figures — technical captions, numbered
   -------------------------------------------------------------------------- */

img {
    max-width: 100%;
    height: auto;
    border: 1px solid var(--rule);
    display: block;
}

figure.spec-figure {
    margin: 2rem 0;
    max-width: 100%;
}

figure.spec-figure img {
    width: 100%;
}

figure.spec-figure figcaption {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.08em;
    color: var(--ink-muted);
    padding-top: 0.5rem;
    border-top: 1px solid var(--rule-hair);
    margin-top: 0.5rem;
    line-height: 1.5;
}

figure.spec-figure figcaption .figure-number {
    color: var(--ink);
    font-weight: 700;
    text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   9b. Contact block — ordering information
   -------------------------------------------------------------------------- */

/* Framed like the specification box: NASA Red border and header. Both are
   datasheet furniture — one states the ratings, the other states where to write
   to — so they read as the same class of object. --rule inverts with the theme
   and the accent does not, which is the other half of why. */
.contact {
    border: 1px solid var(--accent);
    margin: 2rem 0;
}

.contact-title {
    background: var(--accent);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    padding: 0.4rem 0.7rem;
}

.contact dl {
    margin: 0;
    display: grid;
    grid-template-columns: 8.5rem minmax(0, 1fr);
}

.contact dt {
    font-family: var(--font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--ink-muted);
    padding: 0.6rem 0.7rem;
    border-top: 1px solid var(--rule-hair);
    border-right: 1px solid var(--rule-hair);
}

.contact dd {
    margin: 0;
    padding: 0.6rem 0.7rem;
    border-top: 1px solid var(--rule-hair);
    font-family: var(--font-mono);
    font-size: 0.76rem;
    font-weight: 700;
    overflow-wrap: anywhere;
}

.contact dt:first-of-type,
.contact dd:first-of-type {
    border-top: 0;
}

.contact-unset,
.contact-pending {
    font-weight: 400;
    color: var(--ink-muted);
    text-transform: none;
    letter-spacing: 0;
}

/* Pending is a decision, not a defect: it reads as ordinary prose rather than
   naming a config key at the visitor. */
.contact-pending {
    font-style: italic;
}

@media (max-width: 620px) {
    .contact dl {
        grid-template-columns: 6.5rem minmax(0, 1fr);
    }

    .contact dt,
    .contact dd {
        padding: 0.5rem 0.55rem;
        font-size: 0.7rem;
    }

    .contact dt {
        font-size: 0.55rem;
    }
}

/* --------------------------------------------------------------------------
   10. Resume viewer
   -------------------------------------------------------------------------- */

.resume {
    border: 1px solid var(--rule);
    margin: 2rem 0;
}

.resume-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    border-bottom: 1px solid var(--rule);
    background: var(--paper-tint);
}

.resume-label {
    font-family: var(--font-mono);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--paper);
    /* Ink, not accent. The selected language tab below is accent-filled, and an
       accent label beside it merged the two into one block so the selection
       stopped reading as a selection. Orange stays the selection signal here;
       the contact and specification panels use it as a frame instead. */
    background: var(--ink);
    padding: 0.55rem 0.7rem;
    display: flex;
    align-items: center;
    white-space: nowrap;
}

.resume-tabs {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
}

.resume-tab {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink);
    background: none;
    border: 0;
    border-right: 1px solid var(--rule-hair);
    padding: 0.55rem 0.9rem;
    cursor: pointer;
    white-space: nowrap;
}

.resume-tab:hover {
    color: var(--accent);
}

.resume-tab[aria-selected="true"] {
    background: var(--accent);
    color: var(--accent-ink);
}

.resume-download {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    padding: 0.55rem 0.8rem;
    display: flex;
    align-items: center;
    color: var(--ink);
    border: 0;
    border-left: 1px solid var(--rule-hair);
    white-space: nowrap;
}

.resume-download:hover {
    color: var(--accent);
}

.resume-frame {
    /* A4 portrait, capped so a tall viewport does not get a monster embed. */
    aspect-ratio: 1 / 1.294;
    max-height: 80vh;
    width: 100%;
    background: var(--paper-tint);
}

.resume-frame object,
.resume-frame iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

/* Shown when the browser refuses to render a PDF inline — which is most
   mobile browsers. Without this the panel is just a blank rectangle. */
.resume-fallback {
    padding: 1.5rem;
    font-family: var(--font-mono);
    font-size: 0.74rem;
    line-height: 1.6;
    color: var(--ink-muted);
}

.resume-missing {
    padding: 1.25rem;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    line-height: 1.6;
    color: var(--ink-muted);
    border-left: var(--rule-mid) solid var(--accent);
    background: var(--paper-tint);
}

.resume-missing code {
    background: var(--paper);
}

/* --------------------------------------------------------------------------
   11. Footer — document control block
   -------------------------------------------------------------------------- */

footer.doc-control {
    margin-top: 5rem;
    border-top: var(--rule-heavy) solid var(--rule);
    /* Clips the separator the right-hand column casts past the edge of the
       sheet. Without it that rule shows as a stray grey line hanging off the
       right side, most visibly once the grid wraps to two columns. */
    overflow: hidden;
}

.doc-control-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    border-bottom: 1px solid var(--rule);
}

/* Separators are box-shadows, not borders. `auto-fit` changes the column count
   with the viewport, so there is no selector for "last cell in a row" whose
   border needs removing — but a shadow that overhangs the sheet is clipped by
   the footer above. It also draws the horizontal rule between wrapped rows,
   which border-right never did: at two columns the footer read as one row of
   two cells rather than two rows of two. Shadows take no space, so the cells
   stay transparent and the graph paper still shows through. */
.doc-control-cell {
    padding: 0.6rem 0.7rem;
    min-width: 0;
    box-shadow: 1px 0 0 var(--rule-hair), 0 1px 0 var(--rule-hair);
}

.doc-control-cell .k {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.55rem;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 0.15rem;
}

.doc-control-cell .v {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-weight: 700;
    overflow-wrap: break-word;
}

/* --------------------------------------------------------------------------
   12. Responsive — the caution against making a website into a PDF
   -------------------------------------------------------------------------- */

@media (max-width: 860px) {
    :root {
        --gutter: 1.5rem;
    }

    .doc,
    .tabular-row {
        grid-template-columns: minmax(0, 1fr);
    }

    /* One column now, so there is no column 2 to sit in. */
    .doc-body-norail {
        grid-column: 1;
    }

    .doc-rail {
        position: static;
        margin-bottom: 1.75rem;
    }

    .tabular-meta {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem;
        margin-bottom: 0.5rem;
        line-height: 1.4;
    }

    .tabular-index {
        display: inline;
    }

    .specbox dl {
        grid-template-columns: 8.5rem minmax(0, 1fr);
    }

    .specbox dt {
        padding: 0.5rem 0.6rem;
        border-right: 1px solid var(--rule-hair);
    }

    .specbox dd {
        padding: 0.5rem 0.6rem;
        border-top: 1px solid var(--rule-hair);
    }

    .specbox dd:first-of-type {
        border-top: 0;
    }
}

/* Phones are tall and narrow — 9:16 and taller. The masthead is the only
   thing competing with content for the fold, so it gets compressed hard
   rather than allowed to spend 30% of the screen on navigation. */
@media (max-width: 620px) {
    body {
        font-size: 15px;
    }

    .container {
        /* max() keeps content clear of a notch or rounded corner in
           landscape without adding padding on ordinary screens. */
        padding: 1rem max(1rem, env(safe-area-inset-right))
                 0    max(1rem, env(safe-area-inset-left));
    }

    header.masthead {
        align-items: flex-start;
        padding-bottom: 0.6rem;
        gap: 0.6rem 1rem;
    }

    .profile-pic {
        width: 36px;
        height: 36px;
    }

    nav.primary {
        gap: 0.5rem 0.75rem;
    }

    nav.primary a {
        font-size: 0.65rem;
        letter-spacing: 0.08em;
    }

    .masthead-substrip {
        margin-bottom: 1.5rem;
        font-size: 0.55rem;
        letter-spacing: 0.1em;
        justify-content: space-between;
        /* The language switch costs the strip about 60px, and German prints
           DOKUMENT / AUSGABE where English prints DOCUMENT / REVISION. Let the
           identifier drop to a second line rather than run off the edge —
           wrapping survives a longer language, a clipped field does not. */
        flex-wrap: wrap;
        row-gap: 0.2rem;
    }

    /* This used to be `display: none`, because `white-space: nowrap` on every
       sub-strip span left the tagline one line to ellipsise into nonsense.
       Letting it wrap onto its own row costs a line and keeps the words, which
       is the better trade at 200% zoom where the reader most needs them. */
    /* Three rows, in source order: the tagline, the document identifier, then
       the controls. Each claims a full row rather than sharing one, because at
       this width two of them side by side either ellipsise or wrap mid-field.
       `white-space: normal` is what lets the tagline use its second line —
       every other sub-strip span stays nowrap so the identifier is never cut
       in half. */
    .substrip-tagline {
        white-space: normal;
        overflow: visible;
    }

    .substrip-tagline,
    .substrip-doc,
    .substrip-controls {
        flex-basis: 100%;
    }

    h2 {
        margin-top: 2.25rem;
    }

    .resume-frame {
        aspect-ratio: auto;
        height: 65vh;
        height: 65dvh;   /* dvh excludes the retracting address bar */
    }

    .resume-label {
        width: 100%;
    }

    footer.doc-control {
        margin-top: 3rem;
    }

}

/* --------------------------------------------------------------------------
   13. Print — it should look like the document it is imitating
   -------------------------------------------------------------------------- */

@media print {
    :root {
        --paper: #fff;
        --ink: #000;
        --ink-muted: #333;
        --accent: #000;
        /* On screen the grid is the sheet; on paper the sheet is the sheet. */
        --grid-line: transparent;
    }

    #intro-overlay,
    .substrip-controls,
    nav.primary,
    .resume {
        display: none !important;
    }

    body {
        font-size: 11pt;
    }

    a {
        border-bottom: 0;
    }

    .doc {
        display: block;
    }
}
