* {
    box-sizing: border-box;
}

body {
    margin: 0;
    min-height: var(--viewport-h, 100vh);
    padding: 1rem;
    /* --control-bar-h is set only inside the touch query below, so this is
       plain 1rem wherever that query doesn't match; on a full-screen iPad
       it grows to clear the bottom-fixed bar. A phone overrides this
       padding-bottom back down, since its bar sits at the top instead. */
    padding-bottom: calc(1rem + var(--control-bar-h, 0px));
    font-family: system-ui, sans-serif;
    background: #1e1e1e;
    color: #ddd;
}

h1 {
    font-size: 1.2rem;
    margin: 0;
}

pre {
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 0.9rem;
    line-height: 1.4;
    background: #111;
    color: #ddd;
    border: 1px solid #333;
    border-radius: 4px;
    padding: 0.75rem;
    max-height: 80vh;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Screen layout: two-column CSS grid, per docs/layout-spec.md's Screen
   regions section. Header spans both columns; the editor and output pane
   share the left column, the machine pane owns the right. Under ~1100px
   the grid collapses to one column -- the machine pane's own internal
   order is already vertical, so nothing else changes.

   The column boundary (left column / machine column) and the row boundary
   (editor / output, within the left column) are user-draggable -- each has
   its own dedicated, fixed-size grid track (.col-splitter, .row-splitter)
   and reads its neighboring track's size from a `var(...)`, with today's
   values as the fallback, so a page that has never been dragged renders
   identically to before (§1.3). Driving sizing through a custom property
   rather than a full inline `grid-template-columns`/`-rows` restatement
   keeps the *other* track's rule (e.g. the machine column's `minmax(38rem,
   42rem)` below) declared here once, not duplicated into Rust, and keeps
   this rule -- not an inline style -- winning below the 1100px breakpoint.

   The editor row's `minmax(8rem, 1fr)` (matching `EDITOR_MIN_SHARE_PX` in
   `layout.rs`) is a hard floor: `--output-h`'s own `min(..., calc(...))`
   ceiling can't subtract this row's `auto`-height header sibling (its
   height isn't knowable in a static calc()), so it's a conservative bound,
   not an exact one. This floor is what actually guarantees the editor pane
   never collapses when a shortened window revisits a tall committed
   `--output-h` with no new drag. */

main {
    display: grid;
    grid-template-columns: var(--left-col, minmax(0, 1fr)) 6px minmax(38rem, 42rem);
    grid-template-rows: auto minmax(8rem, 1fr) 6px var(--output-h, auto);
    grid-template-areas:
        "header       header       header"
        "editor       col-splitter machine"
        "row-splitter col-splitter machine"
        "output       col-splitter machine";
    gap: 0.75rem;
    /* --viewport-h and --control-bar-h are set only inside the touch
       queries below -- the shared block, and the phone-only override that
       follows it -- so this is calc(100vh - 2rem) wherever neither
       matches. */
    height: calc(var(--viewport-h, 100vh) - 2rem - var(--control-bar-h, 0px));
}

@media (max-width: 1100px) {
    main {
        /* minmax(0, 1fr), not bare 1fr: 1fr alone still sizes the track to
           its widest item's min-content (the memory rows, ~680px).
           minmax(0, ...) caps the track at the viewport instead, so every
           one-column pane -- machine, editor, output -- scrolls its own
           overflow internally rather than widening the page. */
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto auto auto auto;
        grid-template-areas:
            "header"
            "editor"
            "output"
            "machine";
        height: auto;
    }

    /* Resize is a desktop-only affordance: below the collapse breakpoint
       both handles would auto-place into a stray, non-functional bar,
       since this block doesn't reference either splitter's grid area. */
    .col-splitter,
    .row-splitter {
        display: none;
    }

    /* `.editor`'s base rule sets no height property, so in this
       single-column layout (a grid row sized `auto`) its height comes from
       in-flow content -- `.gutter-content`'s line-number spans -- rather
       than from `.source-input` itself, which is absolutely positioned and
       so contributes no height of its own. A bounded height here gives
       `.source-input`'s `overflow: auto` something definite to scroll
       within, so a long program scrolls inside the editor instead of
       growing the whole page. `min-height` keeps a short viewport from
       squeezing the editor down to a couple of visible lines. */
    .editor {
        height: 50vh;
        min-height: 14rem;
    }
}

.app-header {
    grid-area: header;
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

/* Control pane: Run / Continue / Step / Next / Interrupt / Reset and the
   run-state indicator. Real <button> elements, so every control is
   keyboard-reachable without extra wiring; styled on its own classes per
   the editor pane's rule below. Fixed to a viewport edge on touch devices
   -- the bottom on a full-screen iPad, the top on a phone -- see the
   (hover: none) and (pointer: coarse) queries below. */

.controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    /* Removes the double-tap-zoom wait on a fast tap across a button gap or
       the status line; pinch zoom, unaffected, stays for accessibility. */
    touch-action: manipulation;
    /* Desktop only in effect: touch pins `.controls` with `position: fixed`
       below, which removes it from `.app-header`'s flex layout entirely, so
       these two properties never apply there. On desktop they let
       `.controls` fill the header's leftover width so Share, right after it
       in DOM order, lands at the header's right edge. */
    flex: 1 1 auto;
    min-width: 0;
}

/* Shared by New and Share (`.header-button`, in `.app-header`) and the run
   controls: same font, border, radius and colors, so New and Share read as
   one family with the buttons beside them. `.controls button` alone adds
   the cue+label flex layout the run buttons need; Share, plain text on
   every device, and New, an icon on desktop, don't. */
.controls button,
.header-button {
    font: inherit;
    font-size: 0.9rem;
    padding: 0.4rem 0.9rem;
    border: 1px solid #444;
    border-radius: 4px;
    background: #2d2d2d;
    color: #ddd;
    cursor: pointer;
}

.controls button {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    /* Holds each run button at its natural width on desktop, so the status
       message (below) is what shrinks first as the header narrows, not the
       buttons themselves. Touch's own rule below fully restates `flex`,
       overriding this. */
    flex-shrink: 0;
}

.controls button:disabled {
    color: #666;
    cursor: default;
    opacity: 0.6;
}

/* iOS applies :hover on tap and leaves it stuck, so this only applies where
   a real pointing device drives :hover. */
@media (hover: hover) {
    .controls button:not(:disabled):hover,
    .header-button:hover {
        background: #383838;
    }
}

/* The keyboard cue on each button's face (§1.4): the label's first letter,
   bold and amber so the key reads without color vision -- 5.68:1 against
   the button background, 4.84:1 against the hover background, both clear
   of WCAG AA's 4.5:1. Retune the color here, the one rule that sets it.
   Reset carries no `.control-cue` span -- it has no keyboard shortcut. */
.control-cue {
    font-weight: bold;
    color: #e69500;
}

/* A disabled button's own opacity alone would dim the amber to about 3:1 --
   still color, so still reading as a live key. Matching the disabled label
   color instead keeps a disabled cue visibly disabled. */
.controls button:disabled .control-cue {
    color: #666;
}

/* New, desktop: a new-document icon in place of its text, centered in the
   shared `.header-button` box; the icon's size (below) sets its height.
   `.new-label` stays in the DOM for the touch block below, which swaps the
   icon back for the text. */
.new-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* 1.2em: the run buttons' height comes from their text's line box, at the
   font's normal line-height, which is taller than the em-square; at 1.1em
   this button stood a pixel short of them. Touch never shows this icon, so
   its 44px floor on the shared `.header-button` rule is unaffected. */
.new-icon {
    width: 1.2em;
    height: 1.2em;
}

.new-label {
    display: none;
}

/* Desktop: the status message ellipsizes before Share ever moves. `.controls`
   sizes to its content, so without `width: 0` the message's full text would
   count as space `.controls` needs, and Share would wrap as the status grew;
   `flex-grow` still fills the row, and `min-width: 0` lets it shrink below
   its text. `flex: 1 1 0%`'s zero basis, not auto, keeps that same full text
   from counting toward any row's own fit decision, desktop's or the touch
   bar's fixed one below; either way this item grows to fill the row and
   shrinks (`min-width: 0`) to its ellipsis rather than its own text
   deciding the row's size. `width: 0` carries into touch too, where the
   bar's fixed width already decides the message's size. */
.run-state {
    margin-left: 0.25rem;
    font-size: 0.85rem;
    color: #999;
    flex-shrink: 0;
}

.status-message {
    margin-left: 0.5rem;
    font-size: 0.85rem;
    color: #777;
    font-style: italic;
    flex: 1 1 0%;
    width: 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Touch devices: pin the control bar to a viewport edge instead of letting
   it scroll away, and reserve room for it in body's and main's base rules
   above. Keyed on the input device, not the viewport width, for the
   touch/non-touch split itself: a narrow desktop window stays unfixed, and
   an iPad with a trackpad reports a fine pointer and falls through to the
   desktop rule. Which edge -- bottom on a full-screen iPad, top on a
   phone, below -- keys on the viewport instead: the owner's reach argument
   follows the pane the user actually sees, not the device. The owner's
   tuning point for this whole layout. */
@media (hover: none) and (pointer: coarse) {
    :root {
        /* The bar's chrome, everything but the safe-area inset -- shared
           by the default --control-bar-h below and the phone override
           after it, so the two can't drift apart. The button row's floor
           is a literal 44px, not a rem approximation of it, so this stays
           exact at any root font size: 1.2rem (info row, its line-height
           below) + 0.3rem (the gap .controls::after forces) + 0.8rem
           (0.4rem padding top and bottom) = 2.3rem, plus the button row's
           own 44px, plus 1px (the bar's one border). Retune together with
           the rules below that set each figure. */
        --control-bar-chrome: calc(2.3rem + 44px + 1px);
        /* The bar's full rendered height, including its own bottom inset
           -- read by body's and main's base rules above to reserve exactly
           this much room. A phone below swaps the bottom inset for the top
           one. */
        --control-bar-h: calc(var(--control-bar-chrome) + env(safe-area-inset-bottom));
        /* Tracks Safari's collapsing toolbar (100vh there is the
           toolbar-hidden height), so the two-column layout a landscape
           iPad gets never runs under the bar. Supported since iOS 15.4. */
        --viewport-h: 100dvh;
    }

    /* viewport-fit=cover (index.html) lets page content run under the
       notch in landscape, so the side padding is at least the matching
       inset -- max() keeps it at the base 1rem wherever the inset is
       smaller. */
    body {
        padding-left: max(1rem, env(safe-area-inset-left));
        padding-right: max(1rem, env(safe-area-inset-right));
    }

    .controls {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 10;
        flex-wrap: wrap;
        column-gap: 0.3rem;
        row-gap: 0;
        margin: 0;
        background: #1e1e1e;
        border-top: 1px solid #333;
        padding: 0.4rem max(0.5rem, env(safe-area-inset-right))
            calc(0.4rem + env(safe-area-inset-bottom)) max(0.5rem, env(safe-area-inset-left));
    }

    /* Forces the button row onto its own line regardless of how little of
       the first line the label and message fill: a flex item can never
       share a line with a sibling that needs its own 100% of the
       container's width, so this zero-content item -- ordered right after
       them, before the buttons -- always wraps, and carries everything
       after it onto a fresh line with it. Its height is the row gap
       between the two content rows; row-gap above stays 0 so this is the
       only vertical spacing between them, keeping --control-bar-h's
       arithmetic exact. */
    .controls::after {
        content: "";
        order: -1;
        flex-basis: 100%;
        height: 0.3rem;
    }

    /* Ordered above the button-row break; both keep order -1 so the label
       stays left of the message, same as their DOM order. A fixed
       line-height -- rather than the font's own metrics -- keeps this
       row's height, and so --control-bar-h, exact. `flex-shrink: 1`
       restores the initial value over the desktop rule's `0`: run-state
       never needs to shrink in this fixed-width bar, but the desktop-only
       reason for holding it at its natural size doesn't apply here either. */
    .run-state,
    .status-message {
        order: -1;
        line-height: 1.2rem;
        flex-shrink: 1;
    }

    /* One row of six equal-width buttons, each at or above Apple's 44px
       touch-target floor and centered (overriding the base rule's
       align-items: baseline, which reads as text stuck to the button's
       top edge once its box is taller than a line). overflow-wrap guards
       viewports narrower than about 345px, where a button's share would
       otherwise force a long single-word label (Continue, Interrupt) past
       its box; at 375px itself neither label needs to break. */
    .controls button {
        flex: 1 1 0;
        min-width: 0;
        min-height: 44px;
        padding: 0.25rem 0.05rem;
        font-size: 0.6875rem;
        justify-content: center;
        align-items: center;
        overflow-wrap: anywhere;
    }

    /* New and Share stay in the header, not the fixed six-button row
       above, but still need a touch-sized target here. */
    .header-button {
        min-height: 44px;
    }

    /* Touch keeps today's rendering: New shows its text, not the desktop
       icon. */
    .new-icon {
        display: none;
    }

    .new-label {
        display: inline;
    }

    /* No keyboard to teach on a phone -- the cue reads as plain label text
       here. */
    .control-cue {
        font-weight: inherit;
        color: inherit;
    }
}

/* Phone override: a small touch viewport, either orientation, reads the
   reach the same way a full-screen iPad doesn't -- the bottom bar is not
   where a user looks for it there, so it moves to the top instead. The
   inner parentheses are required: Media Queries 4 forbids mixing `and` and
   `or` at one level, and an unparseable query silently disables the whole
   block. Keyed on the app's own viewport, not the device, so an iPad in
   Slide Over or a narrow Split View gets the top bar too. */
@media (hover: none) and (pointer: coarse) and ((max-width: 600px) or (max-height: 500px)) {
    :root {
        /* --control-bar-chrome (above), the top inset in place of the
           bottom one. */
        --control-bar-h: calc(var(--control-bar-chrome) + env(safe-area-inset-top));
    }

    .controls {
        top: 0;
        /* Exactly one edge may resolve to a length; top does. This clears
           the shared block's bottom: 0, which otherwise leaves both edges
           resolved and stretches the fixed bar to fill the viewport. */
        bottom: auto;
        /* The one border --control-bar-h counts moves with the bar: none
           at the top, one at the bottom instead. */
        border-top-width: 0;
        border-bottom: 1px solid #333;
        padding: calc(0.4rem + env(safe-area-inset-top))
            max(0.5rem, env(safe-area-inset-right)) 0.4rem max(0.5rem, env(safe-area-inset-left));
    }

    body {
        /* Mirrors the shared padding-bottom rule above, reserving room at
           the top instead. */
        padding-top: calc(1rem + var(--control-bar-h, 0px));
        /* No bar to clear at the bottom -- just the home indicator. */
        padding-bottom: max(1rem, env(safe-area-inset-bottom));
    }
}

/* Editor pane: a transparent textarea layered over a highlight-colored
   overlay, with a line-number gutter. Every rule below is scoped to its own
   class rather than the bare `pre` selector above, whose wrapping and
   padding/border/overflow would break the glyph-grid alignment and the
   line-number mapping this pane depends on. */

.editor {
    grid-area: editor;
    display: flex;
    border: 1px solid #333;
    border-radius: 4px;
    background: #111;
    min-height: 0;
}

.editor-surface {
    position: relative;
    flex: 1;
    min-width: 0;
}

/* Resize splitters (§1.3): draggable boundaries, dragging writes
   --left-col/--output-h on `main` (read by its own grid-template rule
   above) plus --output-pane-cap (read by .output-pane's own max-height
   below), reconciled into `App` state on pointerup only -- see layout.rs's
   `DragState`. */

.col-splitter,
.row-splitter {
    background: #333;
    touch-action: none;
}

.col-splitter {
    grid-area: col-splitter;
    cursor: col-resize;
}

.row-splitter {
    grid-area: row-splitter;
    cursor: row-resize;
}

/* iOS applies :hover on tap and leaves it stuck, so this only applies where
   a real pointing device drives :hover. */
@media (hover: hover) {
    .col-splitter:hover,
    .row-splitter:hover {
        background: #4a4a4a;
    }
}

.gutter-content,
.overlay-content,
.source-input {
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 0.9rem;
    line-height: 1.4;
    tab-size: 4;
    padding: 0.75rem;
    margin: 0;
    border: 0;
    white-space: pre;
    box-sizing: border-box;
}

.gutter {
    flex: none;
    height: 100%;
    /* A fixed clipping viewport that never itself moves; .gutter-content
       carries the scroll-mirroring transform instead (see editor.rs's
       module doc comment) -- no independent scrollbar of its own. */
    overflow: hidden;
    color: #666;
    text-align: right;
    user-select: none;
    border-right: 1px solid #333;
}

.gutter-line,
.overlay-line {
    display: block;
    height: 1.4em;
}

.gutter-line {
    cursor: pointer;
    padding-right: 0.5em;
    /* A fast tap toggles a breakpoint without the double-tap-zoom wait;
       pinch zoom stays. */
    touch-action: manipulation;
}

/* iOS applies :hover on tap and leaves it stuck -- a tap toggles the
   breakpoint via :active/click regardless, so this only applies where a
   real pointing device drives :hover. */
@media (hover: hover) {
    .gutter-line:hover {
        color: #aaa;
    }
}

/* A line with no address (blank, comment, some directives) rejects a
   breakpoint as a no-op, so this class only ever appears on a line that can
   actually stop a run. */
.gutter-breakpoint {
    color: #f14c4c;
    font-weight: bold;
}

.gutter-breakpoint::before {
    content: "\25cf ";
}

/* The paused machine's current line, from marker_pc; absent while a run is
   in flight. */
.gutter-current {
    background: #2d2d2d;
    color: #dcdcaa;
}

/* The same current-line signal, layered onto the overlay as a full-width
   background band -- additive alongside gutter-current, not a replacement:
   the gutter-only marker alone is easy to miss (defect 4). */
.overlay-current {
    background: #2d2d2d;
}

/* A parsed assembly-error location (finding 7). Declared after
   `.gutter-current`/`.overlay-current` so it wins the tie when the error
   line and the paused current line coincide -- an error is the more
   urgent signal, so it must read as a distinct color, not the same grey
   band both `-current` classes share. */
.gutter-error {
    background: #4b1f1f;
    color: #f14c4c;
    font-weight: bold;
}

.overlay-error {
    background: rgba(241, 76, 76, 0.18);
}

/* The exact character a parsed error location's column names, nested
   inside its line's own highlight span. No glyph metric changes (no
   weight, padding, border or letter spacing), so the overlay stays
   aligned with the textarea underneath it. */
.error-column {
    text-decoration: underline wavy #f14c4c;
}

.overlay,
.source-input {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    resize: none;
}

.overlay {
    /* A fixed-size viewport that never itself moves or scrolls;
       .overlay-content carries the scroll-mirroring transform instead, so
       translating it never drags its own clip box along with it (the bug
       an earlier draft of this fix had). */
    overflow: hidden;
    background: #111;
    border-radius: 4px;
    /* Cosmetic layer only; the textarea above it is what the user scrolls
       and types into. */
    pointer-events: none;
}

.overlay-content {
    /* Sized to the full, unclipped content -- .overlay's overflow: hidden
       is what clips it. Overrides the bare `pre` selector above, which is
       tuned for a single scrolling preformatted block, not this layered
       pane. */
    background: transparent;
    color: #ddd;
    border-radius: 0;
    max-height: none;
    overflow: visible;
    word-break: normal;
}

.source-input {
    overflow: auto;
    background: transparent;
    color: transparent;
    caret-color: #ddd;
}

.source-input::selection {
    /* Translucent, not opaque: the textarea's own text is `color:
       transparent` (see editor.rs's module doc comment), so an opaque
       selection paints over the overlay's syntax colors underneath
       completely. The same color as before, with alpha added, lets it read
       through as a tint instead of blotting it out. */
    background: rgba(38, 79, 120, 0.55);
}

.tok-comment {
    color: #6a9955;
}

.tok-string {
    color: #ce9178;
}

.tok-char {
    color: #d16969;
}

.tok-register {
    color: #9cdcfe;
}

.tok-label {
    color: #dcdcaa;
}

.tok-keyword {
    color: #569cd6;
}

/* An unrecognized mnemonic (the token right after a label) -- distinct
   from every other token class and from unstyled text, so a misspelling
   reads as wrong rather than merely uncolored. */
.tok-unknown-mnemonic {
    color: #f14c4c;
    text-decoration: underline wavy;
}

/* Output pane: the program's captured stdout/stderr/diagnostic output,
   left column, under the editor. */

:root {
    /* The output pane's cap absent any drag -- both the base rule below
       and its @media backstop must agree, since disagreement between them
       is exactly the class of bug both exist to prevent. One property,
       not two literals. */
    --output-pane-default-cap: 14rem;
}

.output-pane {
    grid-area: output;
    display: flex;
    flex-direction: column;
    border: 1px solid #333;
    border-radius: 4px;
    background: #111;
    /* Deliberately NOT var(--output-h, ...): percentages in max-height
       resolve against this pane's own containing block (its grid area,
       i.e. the row track itself), not against `main`. Feeding --output-h's
       min(px, calc(100% - ...)) value in here directly once made "100%"
       mean two different things in two places -- on the grid track it was
       main's height (correct), but on this property it was the track's OWN
       height, so the same px number capped the pane 158px short of its
       track on every drag. --output-pane-cap is a second, simpler property
       written only alongside a committed --output-h (layout.rs's
       main_style): "100%" is unambiguous once the track itself is a
       definite size, which is exactly when this is set on `main` -- while
       `main` stays in the two-column grid below. Left unset (falls back to
       --output-pane-default-cap, a fixed length, never a percentage) for
       the undragged default, where the row is `auto` -- an indefinite
       containing block, against which a percentage max-height computes to
       "none" per the CSS box spec, silently removing the cap rather than
       resolving to 0.

       --output-pane-cap is an inline property on `main`: it does NOT stop
       applying below the @media (max-width: 1100px) breakpoint just
       because the two-column grid does. If a drag committed it at a wide
       viewport and the window is later narrowed past the breakpoint,
       `main` becomes height: auto -- the same indefinite containing block
       as the undragged case above, so "100%" resolves to "none" there too,
       silently uncapping the pane. The override below is the backstop:
       same specificity as this rule, declared after it, so it wins
       whenever the breakpoint applies regardless of what --output-pane-cap
       says. */
    max-height: var(--output-pane-cap, var(--output-pane-default-cap));
}

/* Backstop for the leak described above: immune to the percentage-
   resolution question entirely, wins by source order once the grid
   collapses. Must be declared after the base rule, not folded into the
   @media block near .col-splitter/.row-splitter above -- same specificity,
   but earlier in the file loses. */
@media (max-width: 1100px) {
    .output-pane {
        max-height: var(--output-pane-default-cap);
    }
}

.output-header {
    flex: none;
    padding: 0.4rem 0.75rem;
    font-size: 0.75rem;
    color: #999;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid #333;
}

.output-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0.5rem 0.75rem;
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 0.85rem;
    white-space: pre-wrap;
}

.output-stdout {
    color: #ddd;
}

.output-stderr {
    color: #f14c4c;
}

.output-diagnostic {
    color: #999;
    font-style: italic;
}

/* Machine pane: registers, special registers, memory. A table-like grid,
   not the bare `pre` selector above, whose wrap/max-height/border/padding
   are tuned for a single preformatted block, not a scrolling row list. */

.machine-slot {
    grid-area: machine;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.machine-pane {
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 0.8rem;
    background: #111;
    color: #ddd;
    border: 1px solid #333;
    border-radius: 4px;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.machine-pane h2 {
    font-size: 0.75rem;
    margin: 0.75rem 0 0.4rem;
    color: #999;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.machine-pane section:first-of-type h2 {
    margin-top: 0;
}

.machine-status {
    flex: none;
    display: flex;
    gap: 1rem;
    color: #dcdcaa;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid #333;
}

/* Registers and Specials share one independently-scrolling region (Specials
   renders immediately under Registers, per this prompt's deviation from a
   literal reading of the layout spec's three-pane scroll list). Memory
   scrolls in its own region below -- each pane's own scrollbar, never the
   machine pane as a whole. */

.registers-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.memory {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    border-top: 1px solid #333;
}

/* Registers/specials: a single vertical column, one row per register, with
   fixed ch-based column widths so a value updates in place without reflow
   (the load-bearing fix for the garbled multi-column grid this pane
   previously rendered). */

.register-grid {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
}

.register-row {
    display: flex;
    gap: 0.5rem;
    white-space: nowrap;
}

/* Every fixed-width column carries flex-shrink: 0 -- .register-row is a
   flex container, and without it a squeezed row compresses a column below
   its own ch-width, drifting name/hex alignment out across rows. */
.reg-name {
    display: inline-block;
    width: 4ch;
    flex-shrink: 0;
    text-align: right;
    color: #9cdcfe;
}

.register-marginal .reg-name,
.register-marginal .reg-hex,
.register-marginal .reg-dec {
    color: #666;
}

/* A changed value keeps its accent colour on a marginal row: the dimming
   selector above carries two classes, outranking plain .changed's one, so
   restate it here with .changed folded in, at higher specificity than the
   dimming rule regardless of source order. The highlight stays value-only,
   whatever the row's class. */
.register-marginal .reg-hex.changed,
.register-marginal .reg-dec.changed {
    color: #4ec9b0;
}

.reg-hex {
    display: inline-block;
    width: 18ch;
    flex-shrink: 0;
    color: #ddd;
}

/* Natural width: an inline-block sized to its own content, reading
   left-aligned directly after the hex -- the row's own flex gap
   separates them, not a gap of this cell's own. Fits the common row
   (about 208px total) at the pane's own font on a 375px phone. The
   17-character budget, parentheses included, spans an integer reading
   from 999999999999999 down to -99999999999999; a wider cell, integer or
   float, scrolls inside the pane rather than growing it. */
.reg-dec {
    display: inline-block;
    flex-shrink: 0;
    color: #888;
}

/* The global · rG=N caption: smaller and more muted than a register row --
   teaching the rL/rG split once, in the pane, rather than per row. */
.register-boundary {
    padding-top: 0.2rem;
}

.reg-boundary-text {
    font-size: 0.7rem;
    color: #666;
}

.memory-grid {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.memory-row {
    display: flex;
    gap: 0.75rem;
    white-space: nowrap;
}

/* The row containing marker_pc -- same background as gutter-current, so the
   two panes read as one marker. */
.mem-current {
    background: #2d2d2d;
}

.mem-segment {
    color: #569cd6;
    min-width: 3.5em;
}

.mem-addr {
    color: #dcdcaa;
}

.mem-hex {
    color: #ddd;
    letter-spacing: 0.05em;
}

.mem-byte {
    display: inline-block;
    width: 2.4ch;
}

/* The 4-byte instruction span within the current row -- a distinct accent
   color layered over the row's own mem-current background. */
.mem-current-instruction {
    color: #dcdcaa;
    font-weight: bold;
}

/* Changed-since-last-pause: accent text, no background, visible only while
   paused or halted -- cleared during a Run, Continue, or Next's
   intermediate chunk repaints. Declared after .mem-current-instruction so a
   byte that is both changed and part of the current instruction span keeps
   its changed color (with mem-current-instruction's bold weight still
   applying, so the two states stay distinguishable together rather than one
   silently overriding the other). */
.changed {
    color: #4ec9b0;
}

.mem-ascii {
    display: inline-block;
    width: 16ch;
    white-space: pre;
    color: #6a9955;
}

.mem-label {
    color: #ce9178;
}

/* A thin marker between two runs in different segments, replacing the
   implicit adjacency the single-column memory layout used to rely on. */
.memory-separator {
    height: 1px;
    margin: 0.35rem 0;
    background: #333;
}
