html, body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    display: flex;
    overflow: hidden;
    background-color: #000;
}

.panel {
    height: 100%;
}

#map-pane-wrap {
    flex-basis: 55%;
    position: relative;
}

#map-pane-wrap.drag-over::after,
#terminal-container.drag-over::after {
    position: absolute;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 20px;
    background: rgba(0, 0, 0, 0.65);
    outline: 3px dashed var(--term-fg-bright, orange);
    outline-offset: -8px;
    color: var(--term-fg-bright, orange);
    font-family: 'Courier New', Courier, monospace;
    font-size: 18px;
    font-weight: bold;
    pointer-events: none;
}

#map-pane-wrap.drag-over::after {
    content: 'drop to import (GeoJSON / KML / KMZ / CSV / zipped Shapefile)';
}

/* Dropping on the terminal pane instead switches into full terminal mode
   and plots the import there - see mapdev-import.js's wireDropTarget. */
#terminal-container.drag-over::after {
    content: 'drop to import into full terminal mode (GeoJSON / KML / KMZ / CSV / zipped Shapefile)';
}

#map-pane {
    width: 100%;
    height: 100%;
    background: #000;
}

#divider {
    flex: none;
    width: 8px;
    background: #222;
    cursor: ew-resize;
}

/* Full terminal mode (mapdev.js's double-tap ~) - the map pane and its
   divider are simply removed from layout (#terminal-container's own width
   swap to fill the freed space is JS, in mapdev-layout.js, since the
   divider drag sets that element's width as an inline style which a plain
   CSS rule can't win against). #crosshair lives inside #map-pane-wrap so it
   goes with it for free; the coordinates readout lives in
   #terminal-container instead (it's docked to the terminal pane, not the
   map), so it needs its own rule here. */
body.full-terminal-mode #map-pane-wrap,
body.full-terminal-mode #divider,
body.full-terminal-mode #coordinates-display,
body.full-terminal-mode #coordinates-list {
    display: none;
}

/* Themes are CSS custom properties on <body>, swapped via [data-theme].
   Set on body (not #terminal-container) so the crosshair - a sibling
   subtree under #map-pane-wrap - can see them too; custom properties only
   cascade to descendants. Every theme defines all six variables, no
   fallbacks - a missing one would inherit whatever the previous theme left
   behind.
   --term-fg-dim: table borders/secondary text. --term-fg: body text.
   --term-fg-bright: the prompt, your own typed commands, headers.
   --term-error-fg/-bg: how "command not found" etc. render.
   --term-glow: phosphor/plasma halo - only the two CRT-era themes use it;
   modern schemes render flat, same as a real terminal emulator would. */
body[data-theme="ibm3151"] {
    --term-bg: #001400;
    --term-fg: #33ff33;
    --term-fg-bright: #9dffa8;
    --term-fg-dim: #1c7a1c;
    --term-glow: 0 0 2px rgba(51, 255, 51, 0.9), 0 0 6px rgba(51, 255, 51, 0.5), 0 0 16px rgba(51, 255, 51, 0.25);
    --term-error-fg: #001400;
    --term-error-bg: #9dffa8;
}

body[data-theme="compaq2"] {
    --term-bg: #1a0d00;
    --term-fg: #ff9d3d;
    --term-fg-bright: #ffd9a0;
    --term-fg-dim: #8a5220;
    --term-glow: 0 0 2px rgba(255, 157, 61, 0.9), 0 0 6px rgba(255, 157, 61, 0.5), 0 0 16px rgba(255, 157, 61, 0.25);
    --term-error-fg: #1a0d00;
    --term-error-bg: #ffd9a0;
}

body[data-theme="vt220"] {
    --term-bg: #0a0a0a;
    --term-fg: #d4d4d4;
    --term-fg-bright: #ffffff;
    --term-fg-dim: #6e6e6e;
    --term-glow: 0 0 2px rgba(255, 255, 255, 0.9), 0 0 6px rgba(255, 255, 255, 0.5), 0 0 16px rgba(255, 255, 255, 0.25);
    --term-error-fg: #0a0a0a;
    --term-error-bg: #ffffff;
}

body[data-theme="ibm3278"] {
    --term-bg: #000814;
    --term-fg: #4a9eff;
    --term-fg-bright: #b3d9ff;
    --term-fg-dim: #2a5a8a;
    --term-glow: 0 0 2px rgba(74, 158, 255, 0.9), 0 0 6px rgba(74, 158, 255, 0.5), 0 0 16px rgba(74, 158, 255, 0.25);
    --term-error-fg: #000814;
    --term-error-bg: #b3d9ff;
}

body[data-theme="teletype"] {
    --term-bg: #f7f5f0;
    --term-fg: #1a1a1a;
    --term-fg-bright: #000000;
    --term-fg-dim: #8a8a8a;
    --term-glow: none;
    --term-error-fg: #f7f5f0;
    --term-error-bg: #1a1a1a;
}

body[data-theme="solarized-dark"] {
    --term-bg: #002b36;
    --term-fg: #839496;
    --term-fg-bright: #93a1a1;
    --term-fg-dim: #586e75;
    --term-glow: none;
    --term-error-fg: #dc322f;
    --term-error-bg: transparent;
}

body[data-theme="solarized-light"] {
    --term-bg: #fdf6e3;
    --term-fg: #657b83;
    --term-fg-bright: #586e75;
    --term-fg-dim: #93a1a1;
    --term-glow: none;
    --term-error-fg: #dc322f;
    --term-error-bg: transparent;
}

body[data-theme="gruvbox"] {
    --term-bg: #282828;
    --term-fg: #ebdbb2;
    --term-fg-bright: #fbf1c7;
    --term-fg-dim: #928374;
    --term-glow: none;
    --term-error-fg: #fb4934;
    --term-error-bg: transparent;
}

body[data-theme="dracula"] {
    --term-bg: #282a36;
    --term-fg: #f8f8f2;
    --term-fg-bright: #ff79c6;
    --term-fg-dim: #6272a4;
    --term-glow: none;
    --term-error-fg: #ff5555;
    --term-error-bg: transparent;
}

body[data-theme="nord"] {
    --term-bg: #2e3440;
    --term-fg: #d8dee9;
    --term-fg-bright: #88c0d0;
    --term-fg-dim: #4c566a;
    --term-glow: none;
    --term-error-fg: #bf616a;
    --term-error-bg: transparent;
}

body[data-theme="monokai"] {
    --term-bg: #272822;
    --term-fg: #f8f8f2;
    --term-fg-bright: #a6e22e;
    --term-fg-dim: #75715e;
    --term-glow: none;
    --term-error-fg: #f92672;
    --term-error-bg: transparent;
}

body[data-theme="catppuccin-mocha"] {
    --term-bg: #1e1e2e;
    --term-fg: #cdd6f4;
    --term-fg-bright: #cba6f7;
    --term-fg-dim: #6c7086;
    --term-glow: none;
    --term-error-fg: #f38ba8;
    --term-error-bg: transparent;
}

body[data-theme="tokyo-night"] {
    --term-bg: #1a1b26;
    --term-fg: #a9b1d6;
    --term-fg-bright: #7aa2f7;
    --term-fg-dim: #565f89;
    --term-glow: none;
    --term-error-fg: #f7768e;
    --term-error-bg: transparent;
}

body[data-theme="one-dark"] {
    --term-bg: #282c34;
    --term-fg: #abb2bf;
    --term-fg-bright: #61afef;
    --term-fg-dim: #5c6370;
    --term-glow: none;
    --term-error-fg: #e06c75;
    --term-error-bg: transparent;
}

body[data-theme="high-contrast"] {
    --term-bg: #000000;
    --term-fg: #ffffff;
    --term-fg-bright: #ffff00;
    --term-fg-dim: #aaaaaa;
    --term-glow: none;
    --term-error-fg: #ff0000;
    --term-error-bg: transparent;
}

/* Okabe-Ito palette (Okabe & Ito, 2008) - designed and validated for
   deuteranopia/protanopia, the two most common forms of color blindness.
   Stays off the red/green axis entirely (blue body text, yellow for
   bright/emphasis, orange for errors), unlike high-contrast above which
   pairs yellow with red - a combination that CVD can compress toward each
   other. */
body[data-theme="colorblind-safe"] {
    --term-bg: #000000;
    --term-fg: #56b4e9;
    --term-fg-bright: #f0e442;
    --term-fg-dim: #4a6b80;
    --term-glow: none;
    --term-error-fg: #e69f00;
    --term-error-bg: transparent;
}

/* ngd_index.html's actual palette, restored - black bg, matrix green body
   text, white headings with a green glow (its <h1>/<h2> are literally
   `color: #fff` with a `text-shadow` of #0f0), magenta/cyan glitch accents.
   No orange - ngd_index.html has exactly one orange-ish element (the
   #a86b16 CTA button) and it's not part of the page's actual text/heading
   language, so --term-fg-bright is white here, not orange, and bright
   text/the banner just inherit the same green --term-glow every other line
   already gets (no separate override needed - see .role-bright below).
   --term-glow itself is toned down from ngd_index's literal (full-opacity,
   10/20/30px) glow - fine for two static headings there, but smudges a
   scrolling terminal's worth of text - so it borrows the alpha-faded
   2/6/16px convention the CRT themes above already use, just in green. */
body[data-theme="neogeodev"] {
    --term-bg: #000000;
    --term-fg: #00ff00;
    --term-fg-bright: #ffffff;
    --term-fg-dim: #0a5c0a;
    --term-glow: 0 0 2px rgba(0, 255, 0, 0.9), 0 0 6px rgba(0, 255, 0, 0.5), 0 0 16px rgba(0, 255, 0, 0.25);
    --term-error-fg: #ff00c1;
    --term-error-bg: transparent;
}

/* The neogeodev banner (mapdev.js's NEOGEODEV_BANNER, role 'banner')
   gets the same RGB-split glitch ngd_index.html's <h1 class="glitch">
   uses - a cyan/magenta-shadowed duplicate of the text, clipped to a
   thin sliver and animated to jump around. role-banner itself just
   inherits role-bright's look everywhere else (no glitch outside this
   theme, since the pseudo-elements below are gated on it) so switching
   away from 'neogeodev' leaves the banner readable and static. */
#terminal-output .term-line.role-banner {
    color: var(--term-fg-bright);
    font-weight: bold;
}

body[data-theme="neogeodev"] #terminal-output .term-line.role-banner {
    position: relative;
}

body[data-theme="neogeodev"] #terminal-output .term-line.role-banner::before,
body[data-theme="neogeodev"] #terminal-output .term-line.role-banner::after {
    content: attr(data-text);
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    white-space: pre;
    overflow: hidden;
}

body[data-theme="neogeodev"] #terminal-output .term-line.role-banner::before {
    left: 2px;
    text-shadow: -2px 0 #ff00c1;
    animation: neogeodev-glitch 5s infinite linear alternate-reverse;
}

body[data-theme="neogeodev"] #terminal-output .term-line.role-banner::after {
    left: -2px;
    text-shadow: -2px 0 #00fff9, 2px 2px #ff00c1;
    animation: neogeodev-glitch 1s infinite linear alternate-reverse;
}

@keyframes neogeodev-glitch {
    0% {
        clip-path: inset(50% 0 30% 0);
        transform: translate(-3px, 2px);
    }
    20% {
        clip-path: inset(15% 0 65% 0);
        transform: translate(3px, -2px);
    }
    40% {
        clip-path: inset(80% 0 5% 0);
        transform: translate(-3px, 2px);
    }
    60% {
        clip-path: inset(40% 0 43% 0);
        transform: translate(3px, -2px);
    }
    80% {
        clip-path: inset(5% 0 75% 0);
        transform: translate(-3px, 2px);
    }
    100% {
        clip-path: inset(60% 0 15% 0);
        transform: translate(3px, -2px);
    }
}

/* The containing block for #coordinates-display/#coordinates-list below -
   deliberately NOT #terminal-output, even though that's the scrolling
   element. bottom-anchored position:absolute children of a scrolling box
   are supposed to stay pinned to its fixed viewport regardless of scroll
   position, but that broke down in practice here once actual scrolling
   started. #terminal-container itself never scrolls, so anchoring the
   overlays to it sidesteps the question entirely instead of relying on it. */
#terminal-container {
    position: relative;
    flex-basis: 45%;
    min-width: 0;
    min-height: 0; /* same nested-flex issue as #terminal-output below, one
                       level up: without this, this box's own automatic
                       min-height still grows with its content even though
                       height:100% (from .panel) is set - min-height always
                       wins over an explicit height when it's the larger one. */
    overflow: hidden; /* hard backstop - #terminal-output handles its own
                          scrolling, nothing should ever need to escape this box */
    display: flex;
    flex-direction: column;
    background: var(--term-bg);
}

#terminal-output {
    flex: 1;
    min-height: 0; /* flex items default to min-height:auto, which stops them
                       shrinking below their own content - without this,
                       #terminal-output just grows taller with every line
                       instead of staying fixed-height and scrolling. */
    overflow-y: auto;
    padding: 12px;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    line-height: 1.35;
}

#terminal-output .term-line {
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0 0 2px 0;
    color: var(--term-fg);
    text-shadow: var(--term-glow);
}

#terminal-output .term-line.role-bright {
    color: var(--term-fg-bright);
    font-weight: bold;
}

#terminal-output .term-line.role-dim {
    color: var(--term-fg-dim);
    text-shadow: none;
}

#terminal-output .term-line.role-error {
    display: inline-block;
    color: var(--term-error-fg);
    background: var(--term-error-bg);
    text-shadow: none;
    font-weight: bold;
    padding: 0 4px;
}

/* printTable's output - real table layout instead of box-drawn characters,
   so a cell wraps its own text at any pane width instead of the whole row
   reflowing and desyncing the borders (see printTable's comment). Border
   color/header weight echo the old ┌─┬─┐/bright-header look on purpose. */
#terminal-output table.term-table {
    border-collapse: collapse;
    margin: 0 0 2px 0;
    color: var(--term-fg);
    max-width: 100%;
}

#terminal-output table.term-table th,
#terminal-output table.term-table td {
    border: 1px solid var(--term-fg-dim);
    padding: 1px 8px;
    text-align: left;
    vertical-align: top;
}

#terminal-output table.term-table th {
    color: var(--term-fg-bright);
    font-weight: bold;
}

/* Full terminal mode's plot (mapdev-digitize.js's plotVisibleLayers) - a
   static <canvas> logged into the scrollback like any other result. The
   canvas's own drawing buffer is a fixed pixel size (crisp at that size);
   max-width:100%/height:auto here only ever shrinks it to fit a narrower
   pane, never stretches it up. */
#terminal-output canvas.term-plot {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 0 0 4px 0;
    border: 1px solid var(--term-fg-dim);
}

#terminal-current-line {
    display: flex;
    align-items: center;
    gap: 8px;
}

#terminal-prompt {
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    line-height: 1.35;
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    font-weight: bold;
    user-select: none;
}

#terminal-input-wrap {
    position: relative;
    flex: 1;
    min-width: 0;
}

/* Ghost-text autosuggestion: sits directly behind the input, same font
   metrics (monospace, so character width can't drift). The already-typed
   portion is rendered transparent so only the suggested remainder shows,
   peeking out past the real caret. #terminal-input needs its own
   position:relative so it still paints on top of this (also positioned)
   layer - painting order otherwise favors positioned elements by DOM
   order, and this one comes first. */
#terminal-suggestion {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    overflow: hidden;
    pointer-events: none;
    white-space: pre;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    line-height: 1.35;
    color: var(--term-fg-dim);
}

#terminal-suggestion .typed-spacer {
    color: transparent;
}

#terminal-input {
    position: relative;
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    background: transparent;
    background-color: transparent;
    border: none;
    outline: none;
    box-shadow: none;
    border-radius: 0;
    appearance: none;
    -webkit-appearance: none;
    color: var(--term-fg-bright);
    caret-color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    font: inherit;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    line-height: 1.35;
}

#terminal-input::selection {
    background: var(--term-fg-dim);
}

/* Chrome/Safari force a white autofill background even with autocomplete
   off once the field has been focused - override it back to the theme bg. */
#terminal-input:-webkit-autofill,
#terminal-input:-webkit-autofill:hover,
#terminal-input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--term-fg-bright);
    -webkit-box-shadow: 0 0 0px 1000px var(--term-bg) inset;
    box-shadow: 0 0 0px 1000px var(--term-bg) inset;
    caret-color: var(--term-fg-bright);
    transition: background-color 9999s ease-in-out 0s;
}

/* Follows the theme (var(--term-glow) is written offset/blur/color, which
   is valid box-shadow syntax too, so the same halo used on terminal text
   works here unchanged). */
#crosshair {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 2px;
    height: 20px;
    z-index: 1000;
    background: var(--term-fg-bright);
    box-shadow: var(--term-glow);
    transform: translate(-50%, -50%);
    pointer-events: none;
}

#crosshair::after {
    content: '';
    position: absolute;
    z-index: 1000;
    top: 50%;
    left: 50%;
    width: 20px;
    height: 2px;
    background: inherit;
    box-shadow: inherit;
    transform: translate(-50%, -50%);
}

/* Siblings of #terminal-output, not descendants (see the comment on
   #terminal-container above) - still inside #terminal-container, so they
   still inherit the theme variables and follow the active theme (not
   'color', which is strictly what you draw - see mapdev-digitize.js). */
#coordinates-display {
    position: absolute;
    bottom: 44px; /* clears the live input line pinned at the bottom of the scrollback */
    right: 10px;
    z-index: 1000;
    background: var(--term-bg);
    padding: 4px 8px;
    border-radius: 4px;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
}

#coordinates-list {
    position: absolute;
    bottom: 76px; /* stacked above #coordinates-display, which now clears the input line */
    right: 10px;
    z-index: 1000;
    background: var(--term-bg);
    padding: 6px 10px;
    border-radius: 4px;
    max-height: 55%;
    overflow-y: auto;
    font-family: 'Courier New', Courier, monospace;
    font-size: 14px;
    color: var(--term-fg);
    text-shadow: var(--term-glow);
}

#coordinates-list ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

#coordinates-list li {
    white-space: nowrap;
}

/* Popup attribute table - same overlay family as #coordinates-display/
   #coordinates-list above (anchored to #terminal-container, not
   #terminal-output, for the same scroll-position reason), but tall enough
   to actually show a grid: it pops up from the bottom (bottom:44px, same
   input-line clearance) and fills nearly the whole terminal pane while
   open. Hidden by default; mapdev-table.js toggles .open. */
#table-panel {
    position: absolute;
    left: 10px;
    right: 10px;
    top: 12px;
    bottom: 44px;
    z-index: 1100;
    display: none;
    flex-direction: column;
    background: var(--term-bg);
    border: 1px solid var(--term-fg-dim);
    border-radius: 4px;
    box-shadow: var(--term-glow);
}

#table-panel.open {
    display: flex;
}

#table-panel-header,
#table-panel-footer {
    flex: none;
    padding: 6px 10px;
    font-family: 'Courier New', Courier, monospace;
    font-size: 13px;
    white-space: pre-wrap;
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
}

#table-panel-footer {
    color: var(--term-fg-dim);
    text-shadow: none;
    border-top: 1px solid var(--term-fg-dim);
}

/* A guard-clause error or cancel note from a table-mode keyboard shortcut
   (Enter/Delete/Shift+C/D/S) briefly takes over #table-panel-header - it's
   the only always-visible text surface while a table is open (the
   scrollback #terminal-output, where term.print/printError would otherwise
   go, is covered by #table-panel itself). render() clears these classes on
   its next call, restoring the normal row/keybinding hint. */
#table-panel-header.flash-error {
    color: var(--term-error-fg);
    background: var(--term-error-bg);
    text-shadow: none;
}

#table-panel-header.flash-note {
    color: var(--term-fg-dim);
    text-shadow: none;
}

#table-panel-scroll {
    flex: 1;
    min-height: 0;
    overflow: auto;
    padding: 0 10px;
}

/* In-panel modal (confirm / field-type picker) - a fixed black backdrop
   works across every theme since they're all dark. Deliberately NOT the
   terminal scrollback (term.print/pickFromList/confirmYesNo etc.) - that's
   rendered in #terminal-output, which #table-panel itself sits on top of
   and visually covers while open, so anything printed there while a table
   is open is invisible. Prompts triggered from *inside* table mode
   (Delete, Shift+A, Shift+D) render here instead, where they're
   guaranteed visible. */
#table-panel-modal {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 24px;
    background: rgba(0, 0, 0, 0.82);
    font-family: 'Courier New', Courier, monospace;
    text-align: center;
}

#table-panel-modal.open {
    display: flex;
}

#table-panel-modal-message {
    font-size: 16px;
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    white-space: pre-wrap;
    max-width: 90%;
}

/* Real text entry (the field-name prompt) - a dedicated input inside the
   modal itself, styled like the main terminal prompt/input. Necessary
   because the real #terminal-input, despite being positioned to clear
   #table-panel (bottom:44px on both), reads as effectively invisible to
   users in practice - putting the input where the message already is
   removes any doubt. Hidden (display:none) unless .open; toggled by
   mapdev-table.js's openPromptModal/closeModal alongside #table-panel-modal
   itself, so it's never briefly visible while a choice-based modal (confirm,
   type picker) is showing instead. */
#table-panel-modal-input-wrap {
    display: none;
    align-items: center;
    gap: 8px;
}

#table-panel-modal-input-wrap.open {
    display: flex;
}

#table-panel-modal-prompt {
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    font-weight: bold;
    font-size: 16px;
}

#table-panel-modal-input {
    background: transparent;
    border: none;
    border-bottom: 2px solid var(--term-fg-dim);
    outline: none;
    color: var(--term-fg-bright);
    caret-color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    font: inherit;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    text-align: center;
    min-width: 240px;
    padding: 4px 2px;
}

#table-panel-modal-choices {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 220px;
    overflow-y: auto;
}

#table-panel-modal-choices .modal-choice {
    color: var(--term-fg);
    font-size: 15px;
    padding: 2px 14px;
}

#table-panel-modal-choices .modal-choice.active {
    color: var(--term-fg-bright);
    font-weight: bold;
    text-shadow: var(--term-glow);
}

/* combobox mode's "nothing matches what you typed" placeholder row */
#table-panel-modal-choices .modal-choice-empty {
    color: var(--term-fg-dim);
    font-style: italic;
}

#table-panel-modal-hint {
    color: var(--term-fg-dim);
    font-size: 12px;
}

/* Standalone version of #table-panel-modal above, for prompts triggered
   from anywhere (not just while the attribute table is open) - same
   structure and look, just not nested inside #table-panel. See
   select-by-loc's within-distance flow (mapdev-digitize.js) for the first
   user: promptText/promptChoices (mapdev.js). */
#app-modal {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 24px;
    background: rgba(0, 0, 0, 0.82);
    font-family: 'Courier New', Courier, monospace;
    text-align: center;
}

#app-modal.open {
    display: flex;
}

#app-modal-message {
    font-size: 16px;
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    white-space: pre-wrap;
    max-width: 90%;
}

/* An invalid promptText entry re-prompts in place rather than aborting -
   this is how that's shown. */
#app-modal-message.error {
    color: var(--term-error-fg);
    text-shadow: none;
}

#app-modal-input-wrap {
    display: none;
    align-items: center;
    gap: 8px;
}

#app-modal-input-wrap.open {
    display: flex;
}

#app-modal-prompt {
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    font-weight: bold;
    font-size: 16px;
}

#app-modal-input {
    background: transparent;
    border: none;
    border-bottom: 2px solid var(--term-fg-dim);
    outline: none;
    color: var(--term-fg-bright);
    caret-color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    font: inherit;
    font-family: 'Courier New', Courier, monospace;
    font-size: 16px;
    text-align: center;
    min-width: 240px;
    padding: 4px 2px;
}

#app-modal-choices {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 220px;
    overflow-y: auto;
}

#app-modal-choices .modal-choice {
    color: var(--term-fg);
    font-size: 15px;
    padding: 2px 14px;
}

#app-modal-choices .modal-choice.active {
    color: var(--term-fg-bright);
    font-weight: bold;
    text-shadow: var(--term-glow);
}

#app-modal-hint {
    color: var(--term-fg-dim);
    font-size: 12px;
}

#table-panel-grid {
    border-collapse: collapse;
    width: max-content;
    min-width: 100%;
    font-family: 'Courier New', Courier, monospace;
    font-size: 14px;
}

#table-panel-grid th,
#table-panel-grid td {
    padding: 3px 12px;
    text-align: left;
    white-space: nowrap;
    color: var(--term-fg);
}

#table-panel-grid th {
    position: sticky;
    top: 0;
    background: var(--term-bg);
    color: var(--term-fg-bright);
    text-shadow: var(--term-glow);
    border-bottom: 1px solid var(--term-fg-dim);
}

/* Focused column (Left/Right) - header gets an underline accent, body
   cells get a faint outline, so the focused field reads clearly without
   needing a background wash that would fight row selection's own. */
#table-panel-grid th.col-focused {
    box-shadow: inset 0 -3px 0 var(--term-fg-bright);
}

#table-panel-grid td.col-focused {
    box-shadow: inset 0 0 0 1px var(--term-fg-dim);
}

/* Focused row (the cursor Up/Down/Ctrl+Up/Down moves) - independent of
   selection, so free-roaming with Ctrl+Up/Down to Space-toggle scattered
   rows stays legible. */
#table-panel-grid tr.row-focused td {
    outline: 1px dashed var(--term-fg-dim);
    outline-offset: -1px;
}

/* Selected rows - what 'table calc' and 'delete' act on, and what's
   highlighted on the map (see .mapdev-highlighted below). */
#table-panel-grid tr.row-selected td {
    background: var(--term-fg-dim);
    color: var(--term-bg);
    font-weight: bold;
}

#table-panel-grid tr.row-selected.row-focused td {
    outline: 2px solid var(--term-fg-bright);
    outline-offset: -2px;
}

/* The active cell - the single (focused row x focused column) cell Enter
   or typing edits - gets an actual fill, distinct from row-focused's plain
   outline and row-selected's dimmer background. The extra 'tr' matches
   #table-panel-grid tr.row-selected td's specificity (both are
   id+class+2 types) so this reliably wins on a tied selected+active cell,
   with source order (this rule comes later) breaking the tie. */
#table-panel-grid tr td.cell-active {
    background: var(--term-fg-bright);
    color: var(--term-bg);
    font-weight: bold;
}

/* The real <input> beginCellEdit() swaps into the active cell's <td> -
   blends into that cell (transparent background, inherited color) rather
   than looking like a separate control. */
#table-panel-grid .cell-edit-input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    background: transparent;
    border: none;
    outline: 1px solid var(--term-bg);
    color: inherit;
    font: inherit;
}

/* Map highlight for selected features (mapdev-digitize.js toggles this
   class as the selection changes) - always the active theme's bright
   color, never the digitization 'color', same "terminal chrome follows the
   theme" rule the crosshair uses. Two rules because a marker is a divIcon
   <div> and a line/polygon is an SVG <path> - different properties actually
   ring/glow each. */
path.mapdev-highlighted {
    stroke: var(--term-fg-bright) !important;
    stroke-width: 5 !important;
    filter: drop-shadow(0 0 5px var(--term-fg-bright));
}

.leaflet-marker-icon.mapdev-highlighted {
    outline: 3px solid var(--term-fg-bright);
    outline-offset: 2px;
    box-shadow: var(--term-glow);
    border-radius: 50%;
    z-index: 10000 !important;
}

/* One-time entry splash, styled to match ngd_index.html (the neogeo.dev
   landing page) rather than whatever mapdev theme happens to be saved -
   hardcoded colors, not the --term-* custom properties, so it reads the
   same regardless of theme. Reuses @keyframes neogeodev-glitch (defined
   above, in the neogeodev theme block) rather than a duplicate - that
   keyframe is theme-agnostic itself, just used elsewhere behind a
   [data-theme="neogeodev"] selector. */
#splash-screen {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: #000000;
    overflow: hidden;
}

#splash-screen.closing {
    display: none;
}

#splash-matrix {
    position: absolute;
    inset: 0;
}

/* Bottom-anchored, not centered - the canvas now draws the banners
   dead-center (mapdev.js's initSplashScreen), so the hint sits below
   that instead of overlapping it. */
#splash-content {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 6vh;
    text-align: center;
    font-family: 'Courier New', Courier, monospace;
}

#splash-hint {
    color: #ffffff;
    font-size: 1em;
    text-shadow: 0 0 2px rgba(0, 255, 0, 0.9), 0 0 6px rgba(0, 255, 0, 0.5);
}
