  :root {
    --bg: #0b0d14;
    --panel: #141826;
    --panel-2: #1d2235;
    --line: #2a3050;
    --text: #e6e8f2;
    --muted: #8a91ad;
    --accent: #56e0ff;
    --accent-2: #ff6ad5;
    --good: #66e08c;
    --bad: #ff5d6c;
    --warn: #ffd166;
  }
  * {
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent;
    /* Long-press on iOS otherwise raises the copy/share callout and starts a
       text selection, which is the single biggest "this is a web page" tell
       during play. Inputs opt back in below. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
  /* Text entry still needs selection and the callout, or editing a level name
     becomes miserable. */
  input, textarea { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
  html, body {
    margin: 0; padding: 0;
    height: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    overflow: hidden;
    overscroll-behavior: none;
    touch-action: none;
  }
  /* Fixed, not just overflow:hidden. In an iOS standalone web app the page
     itself can still be dragged; pinning the root removes the surface that
     rubber-banding grabs. */
  #app {
    display: flex; flex-direction: column;
    position: fixed; inset: 0;
    height: 100vh; height: 100dvh;
  }

  /* ---------- Stage + rail row ----------
     One row holding the play area and the control rail beside it. On a phone
     the rail has no width and its children go back to floating over the map
     (they are absolutely positioned, and #main is their containing block), so
     this row costs the phone layout nothing. The desktop block at the bottom
     of this file gives the rail a real width and turns those children into
     ordinary blocks inside it. */
  #main {
    flex: 1; min-height: 0;
    display: flex;
    position: relative;
  }
  #sideRail {
    flex: 0 0 0; width: 0; min-width: 0;
  }
  /* Rail section headings exist only in the desktop layout. */
  .railTitle { display: none; }

  /* ---------- Touch gesture ownership ----------
     touch-action is NOT inherited, so `touch-action: none` on <body> does
     nothing for descendants that set their own scrolling. #buildBar declares
     overflow-x:auto, which left it on the default `auto` — the browser owned
     the gesture, and dragging the bar dragged the whole app. Every scroller
     below now claims exactly the axis it needs and nothing more.

     `overscroll-behavior: contain` is the other half: without it, a swipe that
     reaches the end of a scroller hands the rest of the gesture to the page. */
  #buildBar {
    touch-action: pan-x;
    overscroll-behavior: contain;
  }
  #menu, #editorPanel, #waveList, #folderList,
  .modalBox, .bossLog, .modalBox .adminScroll, .sbBody {
    touch-action: pan-y;
    overscroll-behavior: contain;
  }
  /* Buttons: `manipulation` keeps the tap but drops double-tap-to-zoom, which
     is what makes a rapid second tap feel like it was swallowed. */
  button, .tool, .buildBtn, .tpBtn, .saveItem, .topBtn {
    touch-action: manipulation;
  }

  /* ---------- Safe areas ----------
     viewport-fit=cover means the page extends under the notch and the home
     indicator. Without these the bottom bar sits under the indicator, where
     iOS treats a swipe as a system gesture rather than a tap on your button. */
  #topbar { padding-top: calc(8px + env(safe-area-inset-top, 0px)); }
  #buildBar { margin-bottom: env(safe-area-inset-bottom, 0px); }
  #playBtnRow, #towerPanel { margin-bottom: env(safe-area-inset-bottom, 0px); }

  /* Top bar */
  #topbar {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 10px;
    background: var(--panel);
    border-bottom: 1px solid var(--line);
    z-index: 10;
  }
  #topbar .title {
    font-weight: 700; font-size: 14px;
    color: var(--accent);
    letter-spacing: 0.5px;
  }
  #topbar .spacer { flex: 1; }
  .modeBtns { display: flex; background: var(--panel-2); border-radius: 8px; padding: 2px; }
  .modeBtns button {
    background: transparent; color: var(--muted);
    border: none; padding: 6px 12px; font-size: 13px; font-weight: 600;
    border-radius: 6px; cursor: pointer;
  }
  .modeBtns button.active { background: var(--accent); color: #0b0d14; }
  .topBtn {
    background: var(--panel-2); color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px; padding: 6px 10px;
    font-size: 12px; font-weight: 600; cursor: pointer;
  }
  .badge {
    background: rgba(86,224,255,0.15);
    color: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 4px 8px;
    font-size: 11px; font-weight: 700;
  }

  /* Main menu */
  #menu {
    position: absolute; inset: 0;
    display: none;
    background: radial-gradient(ellipse at top, #1a2040 0%, #060810 70%);
    z-index: 20;
    padding: 20px;
    overflow-y: auto;
  }
  #menu.show { display: flex; }
  /* margin:auto centers the column when it fits and clamps to 0 when it
     doesn't, so a tall menu still scrolls to its own top — flex centering
     would clip the title off the top of the screen instead. */
  .menuInner {
    width: 100%; max-width: 340px;
    margin: auto;
  }
  .menuHead { text-align: center; }
  .menuCols { display: grid; gap: 18px; }
  .menuCol { display: grid; gap: 0; align-content: start; min-width: 0; }
  #menu h1 {
    font-size: 32px; margin: 0 0 4px;
    background: linear-gradient(90deg, var(--accent), var(--accent-2));
    -webkit-background-clip: text; background-clip: text;
    color: transparent;
    letter-spacing: 3px;
  }
  #menu .tagline { color: var(--muted); font-size: 12px; margin-bottom: 24px; letter-spacing: 1px; }
  .menuBtns {
    display: grid; gap: 10px;
    width: 100%;
  }
  .menuBtns button {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 14px 16px;
    font-size: 15px;
    font-weight: 700;
    cursor: pointer;
    text-align: left;
    transition: border-color .15s ease, transform .1s ease;
    display: flex; align-items: center; gap: 10px;
  }
  .menuBtns button:hover, .menuBtns button:active {
    border-color: var(--accent);
    transform: translateY(-1px);
  }
  .menuBtns button .ico {
    font-size: 20px; width: 28px; text-align: center;
  }
  .menuBtns button .sub {
    color: var(--muted); font-size: 11px; font-weight: 500; margin-top: 2px;
  }
  .menuBtns button .col { display: flex; flex-direction: column; }
  .menuFolder {
    border: 1px solid var(--line);
    border-radius: 12px;
    overflow: hidden;
  }
  .menuFolderLabel {
    display: flex; align-items: center; justify-content: space-between;
    font-size: 10px; font-weight: 700; letter-spacing: .09em;
    text-transform: uppercase; color: var(--muted);
    background: var(--panel-2);
    padding: 8px 14px;
    cursor: pointer;
    user-select: none;
    transition: color .15s ease;
  }
  .menuFolderLabel:hover { color: var(--text); }
  .menuFolderChevron {
    font-size: 12px;
    transition: transform .2s ease;
  }
  .menuFolder.open .menuFolderChevron { transform: rotate(90deg); }
  .menuFolderBtns {
    display: grid; gap: 0;
    max-height: 0; overflow: hidden;
    transition: max-height .25s ease;
  }
  .menuFolder.open .menuFolderBtns {
    max-height: 300px;
  }
  .menuFolder.open .menuFolderLabel {
    border-bottom: 1px solid var(--line);
  }
  .menuFolderBtns button {
    border: none; border-radius: 0;
    border-bottom: 1px solid var(--line);
  }
  .menuFolderBtns button:last-child { border-bottom: none; }
  #menuFooter { margin-top: 18px; color: var(--muted); font-size: 11px; text-align: center; }

  /* Playlist editor inside modal */
  .plSection { margin-top: 10px; }
  .plSection h3 {
    font-size: 12px; color: var(--muted); margin: 6px 0;
    text-transform: uppercase; letter-spacing: 1px;
  }
  .plList { display: grid; gap: 4px; }
  .plRow {
    display: flex; align-items: center; gap: 6px;
    background: var(--panel-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 6px 8px;
    font-size: 13px;
  }
  .plRow .name { flex: 1; }
  .plRow .idx { color: var(--accent); font-weight: 700; min-width: 18px; }
  .plRow button {
    background: var(--panel); color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px; padding: 4px 8px;
    font-size: 11px; cursor: pointer;
  }
  .plEmpty { color: var(--muted); font-size: 12px; padding: 8px; text-align: center; }
  .plName {
    width: 100%; padding: 8px;
    background: var(--panel-2); color: var(--text);
    border: 1px solid var(--line); border-radius: 6px;
    font-size: 13px; margin-bottom: 4px;
  }

  /* Loadout editor */
  .loList { display: grid; gap: 8px; margin-top: 8px; }
  .loRow {
    display: flex; align-items: stretch; gap: 8px;
    background: var(--panel-2); border: 1px solid var(--line);
    border-radius: 10px; padding: 10px;
  }
  .loRow.equipped { border-color: var(--accent); background: rgba(86,224,255,0.08); }
  .loRow .loInfo { flex: 1; min-width: 0; }
  .loRow .loName { font-weight: 700; font-size: 13px; }
  .loRow .loKind {
    display: inline-block; font-size: 10px; font-weight: 600;
    color: var(--accent); border: 1px solid var(--accent);
    border-radius: 4px; padding: 1px 5px; margin-left: 6px;
    text-transform: uppercase; letter-spacing: 0.5px;
  }
  .loRow .loDesc { color: var(--muted); font-size: 12px; margin-top: 2px; }
  .loRow .loStats { color: var(--text); font-size: 11px; margin-top: 4px; opacity: 0.85; }
  .loRow .loToggle {
    flex: 0 0 auto; width: 80px;
    background: var(--panel); color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px; cursor: pointer;
    font-size: 12px; font-weight: 600;
  }
  .loRow.equipped .loToggle {
    background: var(--accent); color: #0b0d14; border-color: var(--accent);
  }

  /* Resume banner — compact strip shown when a save exists */
  #resumeBanner {
    width: 100%;
    margin: 0 0 12px;
    border-radius: 10px;
    background: rgba(76,175,80,0.12);
    border: 1px solid #4caf50;
    padding: 8px 12px;
    box-sizing: border-box;
    display: flex; align-items: center; gap: 8px;
  }
  #resumeBanner .resumeIcon { font-size: 16px; flex-shrink: 0; }
  #resumeBanner .resumeInfo { flex: 1; min-width: 0; }
  #resumeBanner .resumeTitle {
    font-size: 11px; font-weight: 700; color: #4caf50; line-height: 1.2;
  }
  #resumeBanner .resumeMeta {
    font-size: 10px; color: var(--muted); line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #resumeBanner .resumeRow {
    display: flex; gap: 6px; flex-shrink: 0;
  }
  #resumeBanner .resumeBtn {
    padding: 5px 10px; border-radius: 7px; font-size: 12px;
    font-weight: 600; cursor: pointer; border: 1px solid #4caf50;
    background: #4caf50; color: #0b0d14;
    font-family: inherit;
    transition: opacity .15s; white-space: nowrap;
  }
  #resumeBanner .resumeBtn:hover { opacity: 0.85; }
  #resumeBanner .resumeBtn.secondary {
    background: transparent; color: var(--muted); border-color: var(--muted);
  }

  /* Enemy editor */
  .eeTabs {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
    margin-top: 8px;
  }
  .eeTab {
    display: flex; align-items: center; justify-content: center; gap: 5px;
    background: var(--panel-2); color: var(--text);
    border: 1px solid var(--line); border-radius: 8px;
    padding: 8px 4px; font-size: 11px; font-weight: 600;
    cursor: pointer; min-height: 34px;
  }
  .eeTab.active {
    border-color: var(--accent);
    background: rgba(86,224,255,0.15);
    color: var(--accent);
  }
  .eeTab .eeDot {
    display: inline-block; width: 9px; height: 9px; border-radius: 50%;
    border: 1px solid rgba(255,255,255,0.25);
    flex: 0 0 auto;
  }
  .eeTab .eeTabName {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .eeTab .eeDirty {
    color: var(--accent-2); font-weight: 800; margin-left: 4px;
  }
  .eeAnimWrap {
    margin-top: 10px;
    background: #060810;
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow: hidden;
    display: flex; justify-content: center;
  }
  .eeAnimWrap canvas { display: block; max-width: 100%; height: auto; }
  .eeForm {
    display: grid; grid-template-columns: 1fr 1fr; gap: 6px 10px;
    margin-top: 10px;
  }
  .eeForm label {
    display: flex; align-items: center; justify-content: space-between;
    color: var(--muted); font-size: 12px; gap: 8px;
  }
  .eeForm input[type="number"] {
    width: 80px;
    background: var(--panel); color: var(--text);
    border: 1px solid var(--line); border-radius: 6px;
    padding: 4px 6px; font-size: 12px;
  }
  .eeForm label.eeColor { grid-column: 1 / -1; }
  .eeForm input[type="color"] {
    width: 44px; height: 26px; padding: 0;
    background: transparent; border: 1px solid var(--line); border-radius: 4px;
    cursor: pointer;
  }
  .eeMeta {
    display: flex; justify-content: space-between; align-items: center; gap: 10px;
    margin-top: 10px; font-size: 11px;
  }
  .eeMeta .eeNote { color: var(--muted); flex: 1; line-height: 1.4; }
  .eeMeta .eeReset {
    background: var(--panel-2); color: var(--text);
    border: 1px solid var(--line); border-radius: 6px;
    padding: 5px 12px; font-size: 11px; font-weight: 600; cursor: pointer;
    flex: 0 0 auto;
  }

  /* Stage */
  #stageWrap {
    flex: 1; min-width: 0; position: relative; overflow: hidden;
    background: #060810;
  }
  canvas { display: block; touch-action: none; }

  /* ---------- Play HUD ----------
     One pill per stat instead of the old single run-on line. Two reasons:
     a phone can wrap and drop the least important pills without the numbers
     turning into a sentence that runs off the screen, and each value is its
     own node, so the per-frame update writes text in place rather than
     re-rendering the whole string.

     The HUD is the ONE thing that still floats over the map on desktop —
     it's small, it's read constantly, and putting it in the top bar would
     move it away from where the eyes already are. */
  #hud {
    position: absolute; left: 0; right: 0; top: 0;
    padding: 8px 10px;
    display: none;
    pointer-events: none;
    z-index: 5;
  }
  #hudStats {
    display: flex; flex-wrap: wrap; gap: 4px;
    /* Leave the top-right corner to #help. */
    max-width: calc(100% - 150px);
  }
  #hudStats .stat {
    display: flex; align-items: baseline; gap: 5px;
    background: rgba(11,13,20,0.82);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 3px 7px;
    line-height: 1.2;
    white-space: nowrap;
  }
  #hudStats .k {
    font-size: 9px; font-weight: 800; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--muted);
  }
  #hudStats .v {
    font-size: 11px; font-weight: 700; color: var(--text);
    font-variant-numeric: tabular-nums;
  }
  #hudStats .coins .v { color: var(--warn); }
  /* The base pill recolors with how close the run is to over — it's the only
     number you lose to, so it should stop looking like the others first. */
  #hudStats .stat.warn { border-color: rgba(255,209,102,0.55); }
  #hudStats .stat.warn .v { color: var(--warn); }
  #hudStats .stat.bad { border-color: rgba(255,93,108,0.65); }
  #hudStats .stat.bad .v { color: var(--bad); }
  /* Build phase: the wave pill becomes the countdown. */
  #hudStats .stat.build { border-color: var(--accent); }
  #hudStats .stat.build .k, #hudStats .stat.build .v { color: var(--accent); }
  #hudStats .stat[hidden] { display: none; }

  /* Editor side panel (mobile: bottom sheet) */
  #editorPanel {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: var(--panel);
    border-top: 1px solid var(--line);
    padding: 8px 8px 12px;
    z-index: 6;
    max-height: 45vh;
    overflow-y: auto;
  }
  #toolRow {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 6px;
  }
  .tool {
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 8px 4px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    text-align: center;
    line-height: 1.1;
  }
  .tool .icon { display: block; font-size: 18px; margin-bottom: 2px; }
  .tool.active {
    border-color: var(--accent);
    background: rgba(86,224,255,0.15);
    color: var(--accent);
  }
  .actionRow {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 6px;
    margin-top: 8px;
  }
  .actionRow button {
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 8px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
  }
  .actionRow button.primary { background: var(--accent); color: #0b0d14; border-color: var(--accent); }
  .actionRow button.danger { background: var(--bad); color: white; border-color: var(--bad); }
  /* Row shapes. These were inline styles on index.html, which meant the
     desktop layout (a narrow right-hand column) could not re-flow them —
     an inline grid-template beats any stylesheet rule. */
  .actionRow.three { grid-template-columns: repeat(3, 1fr); margin-top: 6px; }
  .actionRow.playRow { grid-template-columns: 1fr 2fr; margin-top: 6px; }
  #mapSizeRow {
    grid-template-columns: auto auto auto 1fr;
    gap: 6px; margin-top: 6px; align-items: center;
  }

  /* Cols/Rows mini-form for the map resize row. */
  #mapSizeRow .sizeLabel {
    display: flex; align-items: center; gap: 4px;
    font-size: 11px; color: var(--muted);
  }
  #mapSizeRow input[type="number"] {
    width: 56px;
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 4px 6px;
    font-size: 12px;
  }
  #mapSizeRow .hint { font-size: 10px; color: var(--muted); text-align: right; }

  /* Waves editor list. Each row: wave badge, start-time input, summary. */
  #waveList {
    display: flex; flex-direction: column; gap: 6px;
    max-height: 50vh; overflow-y: auto; margin-top: 8px;
  }
  .waveRow {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 8px;
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 6px 10px;
    font-size: 12px;
  }
  .waveBadge {
    font-weight: 700;
    color: var(--accent);
    background: rgba(86,224,255,0.12);
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 3px 8px;
    white-space: nowrap;
  }
  .waveStart {
    display: flex; align-items: center; gap: 6px;
    color: var(--muted);
  }
  .waveStart input[type="number"] {
    width: 70px;
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 4px 6px;
    font-size: 12px;
  }
  .waveUnit { color: var(--muted); font-size: 11px; }
  .waveCount {
    color: var(--muted);
    font-size: 11px;
    white-space: nowrap;
  }

  /* Spawn folder modal: one row per spawn at a tile. */
  #folderList {
    display: flex; flex-direction: column; gap: 6px;
    max-height: 50vh; overflow-y: auto; margin-top: 8px;
  }
  .folderRow {
    display: grid;
    grid-template-columns: auto auto 1fr auto auto;
    align-items: center;
    gap: 8px;
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 6px 10px;
    font-size: 12px;
  }
  .folderSwatch {
    width: 14px; height: 14px;
    border-radius: 4px;
    border: 1px solid rgba(255,255,255,0.18);
  }
  .folderName {
    font-weight: 600;
    color: var(--text);
  }
  .folderMeta {
    color: var(--muted);
    font-size: 11px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .folderTag {
    display: inline-block;
    color: var(--accent);
    background: rgba(86,224,255,0.12);
    border: 1px solid var(--accent);
    border-radius: 4px;
    padding: 0 4px;
    font-size: 10px;
    margin-left: 4px;
  }
  .folderBtn {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 4px 8px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
  }
  .folderBtn.danger {
    color: var(--bad);
    border-color: var(--bad);
  }

  /* Configurator (when a tool needs config) */
  #toolConfig {
    margin-top: 8px;
    padding: 8px;
    background: var(--panel-2);
    border-radius: 8px;
    border: 1px solid var(--line);
    font-size: 12px;
    display: none;
  }
  #toolConfig label {
    display: flex; align-items: center; justify-content: space-between;
    margin: 4px 0;
    color: var(--muted);
  }
  #toolConfig select, #toolConfig input[type=number] {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 4px 6px;
    font-size: 12px;
    width: 100px;
  }
  #toolConfig .hint { color: var(--muted); font-size: 11px; margin-top: 6px; }
  /* Checkbox-style row: checkbox on the left, text on the right.
     Overrides the default label flex layout used for type/count/interval. */
  #toolConfig label.checkLabel,
  .modalBox label.checkLabel {
    display: flex; align-items: center; justify-content: flex-start;
    gap: 8px; margin: 6px 0; color: var(--muted); font-size: 12px;
    cursor: pointer;
  }
  #toolConfig label.checkLabel input[type="checkbox"],
  .modalBox label.checkLabel input[type="checkbox"] {
    width: auto; margin: 0; cursor: pointer;
  }

  /* Modal */
  .modal {
    position: absolute; inset: 0;
    background: rgba(6,8,16,0.85);
    display: none;
    align-items: center; justify-content: center;
    z-index: 50;
    padding: 16px;
  }
  .modal.show { display: flex; }
  .modalBox {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 16px;
    max-width: 420px;
    width: 100%;
    max-height: 90vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
  }
  .modalBox h2 { margin: 0 0 8px; font-size: 16px; color: var(--accent); }
  .modalBox p { margin: 4px 0; color: var(--muted); font-size: 13px; }
  .modalBox .choices { display: grid; gap: 8px; margin-top: 12px; }
  .choice {
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 10px;
    cursor: pointer;
    transition: border-color .15s ease, transform .15s ease;
  }
  .choice:hover, .choice:active { border-color: var(--accent); transform: translateY(-1px); }
  .choice .ctitle { font-weight: 700; color: var(--text); font-size: 13px; }
  .choice .cdesc { color: var(--muted); font-size: 12px; margin-top: 2px; }
  .choice.weapon { border-color: var(--accent-2); background: linear-gradient(180deg, rgba(255,106,213,0.10), var(--panel-2)); }
  .choice.weapon:hover, .choice.weapon:active { border-color: var(--accent-2); }
  .choice.weapon .ctitle { color: var(--accent-2); }
  /* Barricades — always-available card, earthy amber tone */
  .choice.barricade { border-color: var(--warn); background: linear-gradient(180deg, rgba(255,209,102,0.10), var(--panel-2)); }
  .choice.barricade:hover, .choice.barricade:active { border-color: var(--warn); }
  .choice.barricade .ctitle { color: var(--warn); }
  .barricadeTag { background: var(--warn) !important; color: #0b0d14 !important; }
  .cTag {
    display: inline-block; margin-left: 8px;
    background: var(--accent-2); color: #0b0d14;
    font-size: 10px; font-weight: 800; letter-spacing: 0.5px;
    padding: 1px 6px; border-radius: 999px; vertical-align: 1px;
  }
  /* Tower-mode level-up picker. Two-column grid so all six choices fit
     without scrolling on a typical phone screen. The accent color
     differentiates it from the magenta "NEW WEAPON" cards. */
  .modalBox .choices.towerChoices {
    grid-template-columns: 1fr 1fr;
  }
  .choice.towerPick {
    border-color: var(--accent);
    background: linear-gradient(180deg, rgba(86,224,255,0.10), var(--panel-2));
    min-width: 0; /* allow grid items to shrink so long names wrap instead of overflowing */
  }
  .choice.towerPick:hover, .choice.towerPick:active { border-color: var(--accent); }
  .choice.towerPick .ctitle {
    color: var(--accent);
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 6px;
  }
  .choice.towerPick .ctitle .cTag { margin-left: 0; }
  .choice.towerPick .cTag { background: var(--accent); color: #0b0d14; }

  /* "Hide descriptions" toggle row — small muted control above the grid.
     Lets the player compact the picker when they already know the towers. */
  .towerOptionsRow {
    display: flex; justify-content: flex-end; align-items: center;
    margin-top: 8px;
  }
  .towerOptionsRow label.checkLabel {
    margin: 0;
    font-size: 11px;
    color: var(--muted);
  }

  /* Compact mode — JS adds .hideDesc to .towerChoices when the toggle is on. */
  .modalBox .choices.towerChoices.hideDesc .choice.towerPick { padding: 8px 10px; }
  .modalBox .choices.towerChoices.hideDesc .choice.towerPick .cdesc { display: none; }

  /* Mobile compaction — phones and narrow viewports. Without these tweaks
     the two-column grid + cTag chip pushes the title onto multiple lines
     and overflows. Tighter padding/typography keeps all six cards on
     screen without scrolling. */
  @media (max-width: 480px) {
    .modal { padding: 8px; }
    .modalBox { padding: 12px; }
    .modalBox h2 { font-size: 15px; }
    .modalBox p { font-size: 12px; }
    .modalBox .choices { gap: 6px; margin-top: 8px; }
    .choice.towerPick { padding: 8px; border-radius: 8px; }
    .choice.towerPick .ctitle { font-size: 12px; gap: 3px 5px; }
    .choice.towerPick .cdesc { font-size: 11px; line-height: 1.25; }
    .choice.towerPick .cTag {
      font-size: 9px; padding: 1px 5px; letter-spacing: 0.3px;
    }
    .saveForLaterRow { margin-top: 10px; }
    .saveForLaterBtn { font-size: 11px; padding: 6px 10px; }
  }
  @media (max-width: 360px) {
    .choice.towerPick { padding: 7px; }
    .choice.towerPick .ctitle { font-size: 11px; }
    .choice.towerPick .cdesc { font-size: 10px; }
  }
  /* Very short viewports (landscape phones) — switch to a 3-column grid
     so all six towers fit in just 2 rows and the modal doesn't outgrow
     90vh and start scrolling. */
  @media (max-height: 520px) {
    .modalBox .choices.towerChoices { grid-template-columns: 1fr 1fr 1fr; }
    .choice.towerPick { padding: 6px 8px; }
    .choice.towerPick .cdesc { font-size: 10px; }
  }
  .modalBox .row { display: flex; gap: 8px; margin-top: 12px; }
  .modalBox button {
    flex: 1; background: var(--panel-2); color: var(--text);
    border: 1px solid var(--line); padding: 8px; border-radius: 8px;
    font-weight: 600; cursor: pointer;
  }
  .modalBox button.primary { background: var(--accent); color: #0b0d14; border-color: var(--accent); }

  /* Toast */
  #toast {
    position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
    background: var(--panel-2); color: var(--text);
    border: 1px solid var(--line);
    padding: 8px 14px; border-radius: 8px;
    font-size: 12px; opacity: 0; transition: opacity 0.2s ease;
    z-index: 30; pointer-events: none;
  }
  #toast.show { opacity: 1; }

  /* Boss spawn announcement banner. Slides down from the top of the play
     area, holds for ~2s, then fades out. Total animation runs 3 seconds.
     JS toggles the .show class (re-adding it restarts the animation by
     forcing a reflow). Border color is set per-spawn from the boss's
     randomized color so different bosses visually differ at a glance.
     Visibility is hidden by default — `display:none` would skip the
     animation, so we keep it `block` with `pointer-events:none`. */
  #bossBanner {
    position: absolute;
    top: 16px;
    left: 50%;
    /* Starting transform — overridden by the .show animation. */
    transform: translateX(-50%) translateY(-40px);
    background: rgba(11, 13, 20, 0.92);
    border: 2px solid #ff4f8a;
    color: #fff;
    padding: 12px 28px;
    border-radius: 12px;
    text-align: center;
    z-index: 25;
    pointer-events: none;
    opacity: 0;
    min-width: 200px;
    box-shadow: 0 0 26px rgba(255, 79, 138, 0.5),
                inset 0 0 12px rgba(255, 79, 138, 0.15);
  }
  #bossBanner.show {
    animation: bossBannerShow 3s ease-out forwards;
  }
  #bossBanner .bossBannerLabel {
    font-size: 11px;
    letter-spacing: 0.35em;
    font-weight: 700;
    opacity: 0.75;
  }
  #bossBanner .bossBannerName {
    font-size: 22px;
    font-weight: 800;
    margin-top: 4px;
    text-shadow: 0 0 8px rgba(0, 0, 0, 0.7);
  }
  @keyframes bossBannerShow {
    0%   { opacity: 0; transform: translateX(-50%) translateY(-40px); }
    10%  { opacity: 1; transform: translateX(-50%) translateY(0); }
    18%  { opacity: 1; transform: translateX(-50%) translateY(4px); }
    25%  { opacity: 1; transform: translateX(-50%) translateY(0); }
    85%  { opacity: 1; transform: translateX(-50%) translateY(0); }
    100% { opacity: 0; transform: translateX(-50%) translateY(-12px); }
  }

  
  /* Save list */
  .saveList { display: grid; gap: 6px; margin-top: 8px; }
  .saveItem {
    display: flex; align-items: center; gap: 8px;
    background: var(--panel-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 8px;
  }
  .saveItem .name { flex: 1; font-size: 13px; font-weight: 600; }
  .saveItem button {
    flex: 0 0 auto;
    background: var(--panel); color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px; padding: 4px 8px;
    font-size: 11px; cursor: pointer;
  }
  .saveItem button.danger { color: var(--bad); border-color: var(--bad); }

  /* Boss trophy log */
  .bossLog {
    display: flex; flex-direction: column; gap: 4px;
    max-height: 55vh; overflow-y: auto;
    margin-top: 8px;
  }
  .bossEntry {
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 7px 12px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    display: flex; align-items: center; justify-content: space-between;
  }
  .bossCount {
    color: var(--accent);
    font-weight: 800;
    font-size: 11px;
    background: rgba(86, 224, 255, 0.12);
    border: 1px solid var(--accent);
    border-radius: 4px;
    padding: 1px 6px;
  }

  /* Mini help */
  #help {
    position: absolute; right: 8px; top: 8px;
    background: rgba(20,24,38,0.85);
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 6px;
    font-size: 11px; padding: 4px 8px;
    z-index: 4;
    pointer-events: none;
  }

  /* Damage meter — left side of the play area. Tap-to-toggle: tap anywhere
     on the meter to collapse to the DMG bar; tap the bar to expand again.
     pointer-events:auto so taps reach us instead of falling through to the
     canvas joystick / placement layer. */
  #dmgMeter {
    position: absolute; left: 8px; top: 96px;   /* clears the HUD: XP + Overdrive rows + small text */
    width: 150px;
    background: rgba(11,13,20,0.82);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 6px 0 4px;
    z-index: 5;
    pointer-events: auto;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    transition: width 0.18s ease, padding 0.18s ease;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  }
  /* Collapsed state — only the DMG bar shows; rows are hidden. */
  #dmgMeter.minimized {
    width: 56px;
    padding: 4px 0;
  }
  #dmgMeter.minimized .dmgRow { display: none; }
  #dmgMeter.minimized .dmgTitle { margin-bottom: 0; }
  .dmgTitle {
    font-size: 9px; font-weight: 800;
    color: var(--muted);
    letter-spacing: 1px;
    text-align: center;
    margin-bottom: 4px;
    text-transform: uppercase;
  }
  .dmgRow {
    position: relative;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 2px 8px;
    min-height: 18px;
    overflow: hidden;
  }
  .dmgFill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    border-radius: 0 2px 2px 0;
    pointer-events: none;
    transition: width 0.25s ease;
  }
  .dmgName {
    font-size: 10px; font-weight: 600;
    color: var(--text);
    position: relative; z-index: 1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    max-width: 100px;
  }
  .dmgVal {
    font-size: 10px;
    color: var(--muted);
    position: relative; z-index: 1;
    margin-left: 4px;
    font-variant-numeric: tabular-nums;
  }

  /* Play-mode button row — pause / speed / sell / eye. Sits ABOVE the build
     bar (which owns the bottom strip), so bottom is the build bar's height
     plus a gap rather than a bare 16px. */
  #playBtnRow {
    position: absolute; left: 50%; bottom: 84px;
    transform: translateX(-50%);
    display: flex; align-items: center; justify-content: center; gap: 8px;
    z-index: 6; pointer-events: none;
  }
  .speedBtn, .demolishBtn, .hideAttacksBtn, .pauseBtn {
    pointer-events: auto;
    border-radius: 8px;
    font-size: 13px; font-weight: 700;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    user-select: none;
    -webkit-user-select: none;
  }
  /* Pause button — sits left of the speed button. Neutral white outline
     so it doesn't compete with the warn/bad/accent themes of the other
     play buttons. When the game is paused (.active), the icon flips from
     two bars to a play triangle and the border picks up the accent color
     so the player can read at a glance that they're paused. */
  .pauseBtn {
    background: rgba(20, 24, 38, 0.85);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.45);
    padding: 8px 10px;
    line-height: 0;
  }
  .pauseBtn:hover { border-color: #fff; }
  .pauseBtn .pauseGlyph rect { fill: #fff; }
  .pauseBtn .playGlyph { fill: #fff; display: none; }
  .pauseBtn.active {
    background: rgba(86, 224, 255, 0.18);
    border-color: var(--accent);
  }
  .pauseBtn.active:hover { border-color: var(--accent); }
  .pauseBtn.active .pauseGlyph { display: none; }
  .pauseBtn.active .playGlyph  { display: inline; }
  .speedBtn {
    background: rgba(255,209,102,0.12);
    color: var(--warn);
    border: 1px solid var(--warn);
    padding: 8px 14px;
  }
  .speedBtn.active {
    background: var(--warn);
    color: #0b0d14;
    box-shadow: 0 0 12px rgba(255,209,102,0.5);
  }
  .demolishBtn {
    background: rgba(255,93,108,0.12);
    color: var(--bad);
    border: 1px solid var(--bad);
    padding: 8px 14px;
  }
  .demolishBtn.active {
    background: var(--bad);
    color: #fff;
    box-shadow: 0 0 12px rgba(255,93,108,0.5);
  }
  .hideAttacksBtn {
    background: rgba(86,224,255,0.10);
    border: 1px solid var(--accent);
    padding: 8px 10px;
    line-height: 0;
  }
  /* Default: attacks visible — solid teal eye */
  .hideAttacksBtn .eyeOuter { fill: var(--accent); stroke: none; }
  .hideAttacksBtn .eyePupil { fill: #0b0d14; stroke: none; }
  /* Active: attacks hidden — teal outline eye only */
  .hideAttacksBtn.active .eyeOuter { fill: none; stroke: var(--accent); stroke-width: 1.5px; }
  .hideAttacksBtn.active .eyePupil { fill: none; stroke: var(--accent); stroke-width: 1.5px; }

  /* Admin panel — difficulty tuning. Reuses .modal / .modalBox shell.
     The admin form is much wider than the default modal so the long
     ENEMY_/WEAPON_ keys have room to breathe; rows are a strict two-
     column grid (label | input) with everything left-justified. The
     default-value hint sits below the input inside the same column. */
  .modalBox:has(.adminScroll) {
    max-width: 860px;
    text-align: left;
  }
  .modalBox .adminScroll {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
    margin: 8px -4px 0;
    padding: 0 4px 4px;
    text-align: left;
  }
  .adminSection { margin-top: 12px; }
  .adminSection:first-child { margin-top: 0; }
  .adminSection h3 {
    font-size: 11px; font-weight: 800;
    color: var(--accent);
    letter-spacing: 0.5px;
    text-transform: uppercase;
    border-bottom: 1px solid var(--line);
    padding-bottom: 4px;
    margin: 0 0 6px;
    text-align: left;
  }
  /* Strict two-column layout: label on the left, input on the right.
     The default-value hint is a third grid item that spans the input
     column on a second row so it sits directly under the field. */
  .adminField {
    display: grid;
    grid-template-columns: 280px 1fr;
    grid-template-rows: auto auto;
    column-gap: 14px;
    row-gap: 2px;
    align-items: center;
    justify-items: start;
    padding: 6px 0;
    font-size: 12px;
    color: var(--text);
    text-align: left;
  }
  .adminField .adminKey  { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
  .adminField input      { grid-column: 2; grid-row: 1; }
  .adminField .adminDefault { grid-column: 2; grid-row: 2; }
  .adminField input[type="number"],
  .adminField input[type="text"] {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 4px 6px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    width: 100%;
    text-align: left;
  }
  .adminField input[type="checkbox"] {
    width: auto; margin: 0; cursor: pointer;
    justify-self: start;
  }
  .adminKey {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    color: var(--text);
    text-align: left;
    word-break: break-word;
  }
  .adminDefault {
    font-size: 10px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    text-align: left;
  }
  .adminActions {
    display: flex;
    gap: 6px;
    margin-top: 12px;
    flex-wrap: wrap;
  }
  .adminActions button {
    flex: 1 1 auto;
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
    padding: 8px;
    border-radius: 8px;
    font-weight: 600;
    font-size: 12px;
    cursor: pointer;
  }
  .adminActions button.primary {
    background: var(--accent);
    color: #0b0d14;
    border-color: var(--accent);
  }
  /* Password modal rows */
  .adminPwdRow { margin-top: 10px; }
  .adminPwdRow label { font-size: 11px; color: var(--muted); }
  .adminPwdRow input {
    width: 100%;
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 13px;
    margin-top: 4px;
  }
  /* Admin menu button — same row as other menu buttons but tinted muted
     so it doesn't compete with the main play actions. */
  .adminMenuBtn {
    opacity: 0.7;
  }
  .adminMenuBtn:hover { opacity: 1; }

  /* "Save for later" escape hatch at the bottom of the level-up picker.
     Visually subordinated — small, muted text-button — so it doesn't
     compete with the primary upgrade / tower cards above it. Clicking
     resumes the game with the queued level-ups preserved. */
  .saveForLaterRow {
    display: flex; justify-content: center;
    margin-top: 14px;
  }
  .saveForLaterBtn {
    background: transparent;
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 7px 14px;
    font-size: 12px; font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
  }
  .saveForLaterBtn:hover {
    color: var(--text);
    border-color: var(--accent);
    background: rgba(86,224,255,0.06);
  }

  /* =====================================================================
     SCOREBOARD & NAME-ENTRY OVERLAYS
     ===================================================================== */

  /* Full-screen backdrop */
  .sbOverlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(0,0,0,0.82);
    align-items: center;
    justify-content: center;
    padding: 16px;
  }
  .sbOverlay.show { display: flex; }

  /* Outer wrapper — positions the animated border canvas + inner content */
  .sbWrap {
    position: relative;
    width: min(640px, 100%);
    max-height: 90vh;
    border-radius: 4px;
  }
  .sbWrapSmall { width: min(420px, 100%); }

  /* The canvas sits behind everything and draws the animated border */
  .sbBorderCanvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border-radius: 4px;
    pointer-events: none;
  }

  /* The actual content sits on top of the border canvas */
  .sbInner {
    position: relative;
    z-index: 1;
    background: #0e1120ee;
    border-radius: 4px;
    /* Leave a 6px gap on all sides so the border canvas shows through */
    margin: 6px;
    padding: 24px 20px 20px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-height: calc(90vh - 12px);
    overflow: hidden;
  }

  .sbTitle {
    margin: 0;
    font-size: 22px;
    font-weight: 800;
    letter-spacing: 2px;
    text-align: center;
    color: var(--warn);
    text-shadow: 0 0 16px #ffd16688;
  }

  .sbBody {
    overflow-y: auto;
    flex: 1;
  }

  .sbLoading {
    text-align: center;
    color: var(--muted);
    padding: 32px 0;
  }

  /* Scoreboard table */
  .sbTable {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
  }
  .sbTable thead tr {
    border-bottom: 1px solid var(--line);
  }
  .sbTable th {
    color: var(--muted);
    font-weight: 600;
    padding: 6px 8px;
    text-align: left;
    font-size: 11px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
  }
  .sbTable td {
    padding: 8px 8px;
    border-bottom: 1px solid #1d223544;
  }
  .sbTable tr.sbSelf td { color: var(--accent); font-weight: 700; }
  .sbRank { width: 36px; color: var(--muted); text-align: center; }
  .sbRank.gold   { color: #ffd700; font-weight: 800; font-size: 16px; }
  .sbRank.silver { color: #c0c0c0; font-weight: 700; }
  .sbRank.bronze { color: #cd7f32; font-weight: 700; }
  .sbName  { font-weight: 700; letter-spacing: 2px; font-size: 15px; }
  .sbScore { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
  .sbDate  { text-align: right; color: var(--muted); font-size: 11px; }
  .sbEmpty { text-align: center; color: var(--muted); padding: 32px 0; }

  .sbFooter { display: flex; justify-content: center; padding-top: 4px; }

  .sbBtn {
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 10px 24px;
    font-size: 14px;
    cursor: pointer;
    transition: background 0.15s;
  }
  .sbBtn:hover { background: var(--panel); }
  .sbBtnPrimary {
    background: var(--warn);
    color: #0b0d14;
    border-color: var(--warn);
    font-weight: 700;
  }
  .sbBtnPrimary:hover { background: #ffe89a; }

  /* Name-entry specific */
  .neScoreDisplay {
    text-align: center;
    font-size: 36px;
    font-weight: 800;
    color: var(--warn);
    margin: 0;
    letter-spacing: 1px;
    text-shadow: 0 0 20px #ffd16666;
  }
  .nePrompt {
    text-align: center;
    color: var(--muted);
    margin: 0;
    font-size: 13px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
  }
  .neLetters {
    display: flex;
    gap: 12px;
    justify-content: center;
  }
  .neLetter {
    width: 64px;
    height: 80px;
    background: var(--panel-2);
    border: 2px solid var(--line);
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 40px;
    font-weight: 800;
    letter-spacing: 0;
    color: var(--accent);
    cursor: pointer;
    user-select: none;
    transition: border-color 0.15s, background 0.15s;
  }
  .neLetter:hover { background: var(--panel); border-color: var(--accent); }
  .neLetter.active {
    border-color: var(--warn);
    background: #1d1a10;
    color: var(--warn);
    box-shadow: 0 0 12px #ffd16644;
  }
  .neHint {
    text-align: center;
    color: var(--muted);
    font-size: 11px;
    margin: 0;
  }

  /* ---------- Build bar ----------
     The tower shop: a horizontal strip along the bottom of the play area,
     and the only way to acquire a tower. Scrolls sideways rather than
     wrapping, so adding tower types can never push the row into a second
     line that covers the map. */
  #buildBar {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    padding: 8px 10px;
    max-width: calc(100% - 24px);
    overflow-x: auto;
    overflow-y: hidden;
    background: rgba(11, 13, 20, 0.88);
    border: 1px solid var(--line);
    border-radius: 12px;
    z-index: 7;
    pointer-events: auto;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
  }
  .buildBtn {
    /* --tcol is set inline per button to the tower's own color, so the bar
       reads as the same palette the towers draw in. */
    flex: 0 0 auto;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    min-width: 66px;
    padding: 7px 10px;
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-left: 3px solid var(--tcol, var(--accent));
    border-radius: 8px;
    color: var(--text);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    transition: background 0.15s, border-color 0.15s, transform 0.1s;
  }
  .buildBtn:hover { background: rgba(255,255,255,0.06); }
  .buildBtn:active { transform: translateY(1px); }
  .buildName {
    font-size: 11px; font-weight: 700; white-space: nowrap;
  }
  .buildCost {
    font-size: 11px; font-weight: 800;
    color: var(--warn);
    font-variant-numeric: tabular-nums;
  }
  /* Armed — this is the tower the next tap on the map will build. */
  .buildBtn.armed {
    background: linear-gradient(180deg, rgba(86,224,255,0.16), var(--panel-2));
    border-color: var(--tcol, var(--accent));
    box-shadow: 0 0 10px rgba(86,224,255,0.35);
  }
  .buildBtn.armed .buildName { color: #fff; }
  /* Unaffordable. Still clickable (the handler explains why) — greying it
     out entirely would hide the price the player is saving toward. */
  .buildBtn.cant {
    opacity: 0.42;
    filter: grayscale(0.5);
  }
  .buildBtn.cant .buildCost { color: var(--bad); }

  /* ---------- Selected-tower panel ----------
     Sits directly above the build bar. Fixed width so upgrading (which
     changes the numbers but not the layout) doesn't make the panel jump
     around under the player's thumb. */
  #towerPanel {
    position: absolute;
    left: 50%;
    /* Clears #playBtnRow (bottom:84px, ~34px tall) rather than covering it —
       the panel has the higher z-index, so overlapping ate the pause and
       speed buttons for as long as a tower was selected. */
    bottom: 126px;
    transform: translateX(-50%);
    width: min(300px, calc(100% - 24px));
    background: rgba(11, 13, 20, 0.94);
    border: 1px solid var(--accent);
    border-radius: 12px;
    padding: 10px 12px;
    z-index: 8;
    pointer-events: auto;
    box-shadow: 0 0 18px rgba(0, 0, 0, 0.6);
  }
  .tpHead {
    display: flex; align-items: center; gap: 8px;
    margin-bottom: 6px;
  }
  .tpName { font-weight: 800; font-size: 13px; color: var(--accent); flex: 1 1 auto; }
  .tpTier {
    font-size: 11px; font-weight: 700; color: var(--warn);
    background: rgba(255, 209, 102, 0.12);
    border: 1px solid rgba(255, 209, 102, 0.35);
    border-radius: 6px; padding: 1px 6px;
  }
  .tpClose {
    flex: 0 0 auto;
    background: transparent; border: none; color: var(--muted);
    font-size: 14px; line-height: 1; cursor: pointer; padding: 2px 4px;
  }
  .tpClose:hover { color: var(--text); }
  .tpStats { display: grid; gap: 2px; }
  .tpRow {
    display: flex; justify-content: space-between; gap: 8px;
    font-size: 11px;
  }
  .tpKey { color: var(--muted); }
  .tpVal { color: var(--text); font-variant-numeric: tabular-nums; }
  /* The "→ 14" half of a stat row: what the next tier turns this number into.
     Green because it is the reason to spend, and it must read as a preview
     rather than as the current value. */
  .tpNext { color: var(--good); font-weight: 700; }
  .tpKills {
    margin-top: 6px; font-size: 10px; color: var(--muted);
    border-top: 1px solid var(--line); padding-top: 5px;
  }
  .tpActions { display: flex; gap: 6px; margin-top: 8px; }
  .tpBtn {
    flex: 1 1 0;
    padding: 7px 6px;
    border-radius: 8px;
    font-size: 11px; font-weight: 700;
    cursor: pointer;
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
  }
  .tpBtn.up { border-color: var(--good); color: var(--good); }
  .tpBtn.up:hover { background: rgba(102, 224, 140, 0.12); }
  .tpBtn.sell { border-color: var(--warn); color: var(--warn); }
  .tpBtn.sell:hover { background: rgba(255, 209, 102, 0.12); }
  /* Unaffordable upgrade: dimmed but still pressable, so tapping it explains
     why rather than doing nothing. */
  .tpBtn.cant { opacity: 0.45; }
  .tpBtn[disabled] { opacity: 0.4; cursor: default; color: var(--muted); border-color: var(--line); }

  /* On short screens the panel and the build bar together eat the map, so
     tighten both rather than letting the panel scroll off. */
  @media (max-height: 560px) {
    #towerPanel { bottom: 122px; padding: 7px 10px; }
    .tpKills { display: none; }
  }

  /* Level-picker style groups. The two map styles are two ways of playing, so
     the picker separates them with a heading and a one-line explanation rather
     than leaving the difference buried in a name prefix. */
  .styleGroup { margin: 10px 0 4px; }
  .styleTitle {
    font-size: 12px; font-weight: 800; color: var(--accent);
    letter-spacing: 0.04em;
  }
  .styleBlurb { font-size: 11px; color: var(--muted); margin-top: 1px; }

  /* ---------- Menu run setup ----------
     Two chip rows and one big PLAY. The chips answer "what am I about to
     play?" before the player commits, so the primary action can stay a single
     unambiguous button instead of a wall of near-identical menu entries. */
  .setup {
    display: grid;
    gap: 10px;
    margin: 10px 0 12px;
  }
  .chipRow { display: grid; gap: 5px; }
  .chipLabel {
    font-size: 10px; font-weight: 800; letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .chipGroup {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
  }
  .chip {
    padding: 10px 8px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: var(--panel-2);
    color: var(--muted);
    font-size: 13px; font-weight: 700;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .chip:hover { border-color: var(--muted); color: var(--text); }
  /* Selected. The fill is deliberately strong: with only two options per row,
     a subtle highlight leaves people unsure which one is live. */
  .chip.on {
    background: linear-gradient(180deg, rgba(86,224,255,0.18), var(--panel-2));
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: 0 0 10px rgba(86,224,255,0.22);
  }
  /* Reserved space, not conditional space: the hint is always present so
     switching chips never reflows the button below it under your thumb. */
  .chipHint {
    font-size: 11px;
    color: var(--muted);
    line-height: 1.3;
    min-height: 2.6em;
  }

  .playBtn {
    width: 100%;
    display: flex; align-items: center; justify-content: center; gap: 10px;
    padding: 15px 12px;
    border-radius: 12px;
    border: 1px solid var(--accent);
    background: linear-gradient(180deg, rgba(86,224,255,0.22), rgba(86,224,255,0.08));
    color: var(--accent);
    font-size: 17px; font-weight: 800; letter-spacing: 0.1em;
    cursor: pointer;
    box-shadow: 0 0 16px rgba(86,224,255,0.25);
  }
  .playBtn:hover { background: linear-gradient(180deg, rgba(86,224,255,0.30), rgba(86,224,255,0.12)); }
  .playBtn:active { transform: translateY(1px); }
  .playIco { font-size: 15px; }

  .pickMapBtn {
    width: 100%;
    margin-top: 6px;
    padding: 9px 12px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: transparent;
    color: var(--muted);
    font-size: 12px; font-weight: 600;
    cursor: pointer;
  }
  .pickMapBtn:hover { border-color: var(--muted); color: var(--text); }

  /* ================================================================
     PHONE COMPACTION  (narrow viewports)
     The HUD is the only chrome at the top of the map, so it has to earn
     its space: on a phone the two stats that are pure post-run trivia are
     dropped rather than wrapped onto a third row of pills.
     ================================================================ */
  @media (max-width: 599px) {
    #hudStats #statKills, #hudStats #statTowers { display: none; }
    #hudStats { max-width: calc(100% - 120px); }
    #help { max-width: 45%; }
  }

  /* ================================================================
     DESKTOP LAYOUT  (>= 900px)

     A different layout, not a bigger one. On a phone the map is full-bleed
     and every control floats over it, because the screen is too small to
     spend on permanent chrome. A desktop screen is not, so:

       - the tower shop, play buttons and selected-tower panel move OFF the
         map into #sideRail, a real column beside the stage;
       - the editor becomes a right-hand column instead of a bottom sheet
         that ate 45% of the map;
       - the menu goes two-up, with the run you're about to start on the
         left and everything else on the right.

     Width-based (not `pointer: fine`) on purpose: a touchscreen laptop
     reports a coarse pointer but still wants the desktop layout.
     ================================================================ */
  @media (min-width: 900px) {
    /* ---- Top bar ---- */
    #topbar { padding: calc(10px + env(safe-area-inset-top, 0px)) 16px 10px; gap: 14px; }
    #topbar .title { font-size: 18px; letter-spacing: 2px; }
    .topBtn { padding: 8px 14px; font-size: 13px; }
    .badge { font-size: 12px; padding: 5px 10px; }

    /* ---- HUD ---- */
    #hud { padding: 10px 12px; }
    #hudStats { gap: 6px; max-width: calc(100% - 40px); }
    #hudStats .stat { padding: 4px 10px; border-radius: 8px; gap: 7px; }
    #hudStats .k { font-size: 10px; }
    #hudStats .v { font-size: 14px; }

    /* The build hint has no bar "below" it any more — move it out of the
       top-right corner (where the editor column now lives) to the free
       bottom-left of the stage. */
    #help {
      top: auto; right: auto;
      left: 10px; bottom: 10px;
      font-size: 12px; padding: 6px 10px;
    }

    /* ---- Damage meter ---- */
    #dmgMeter { width: 200px; top: 88px; }
    #dmgMeter.minimized { width: 68px; }
    .dmgTitle { font-size: 10px; }
    .dmgRow { min-height: 22px; padding: 3px 10px; }
    .dmgName { font-size: 12px; max-width: 140px; }
    .dmgVal { font-size: 12px; }

    /* ---- Side rail ----
       Exists only during a run; `data-mode` on #app is set by setAppMode,
       so no JS has to remember to show or hide it. */
    #sideRail {
      display: none;
      flex: 0 0 300px; width: 300px;
      flex-direction: column; gap: 10px;
      min-height: 0;
      padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px));
      background: var(--panel);
      border-left: 1px solid var(--line);
    }
    #app[data-mode="play"] #sideRail { display: flex; }
    .railTitle {
      display: block;
      font-size: 10px; font-weight: 800; letter-spacing: 0.12em;
      text-transform: uppercase; color: var(--muted);
    }

    /* Play buttons: a static row across the top of the rail. */
    #sideRail #playBtnRow {
      position: static; transform: none;
      left: auto; bottom: auto;
      margin-bottom: 0;
      flex: 0 0 auto;
      gap: 6px;
      pointer-events: auto;
    }
    #sideRail #playBtnRow > button { flex: 1 1 0; justify-content: center; }
    #sideRail .speedBtn, #sideRail .demolishBtn { padding: 9px 8px; }
    #sideRail .pauseBtn, #sideRail .hideAttacksBtn { padding: 9px 8px; display: flex; align-items: center; justify-content: center; }

    /* Tower shop: a vertical list. Horizontal scrolling is a phone
       affordance — with a full column of height available, every tower
       should be visible and readable at once. */
    #sideRail #buildBar {
      position: static; transform: none;
      left: auto; bottom: auto;
      flex: 1 1 auto; min-height: 0;
      flex-direction: column;
      width: 100%; max-width: none;
      gap: 6px; padding: 0; margin-bottom: 0;
      background: transparent; border: none; border-radius: 0;
      overflow-x: hidden; overflow-y: auto;
      touch-action: pan-y;
    }
    #sideRail .buildBtn {
      flex: 0 0 auto;
      flex-direction: row; align-items: center; justify-content: space-between;
      gap: 10px;
      width: 100%; min-width: 0;
      padding: 11px 12px;
    }
    #sideRail .buildName { font-size: 13px; overflow: hidden; text-overflow: ellipsis; }
    #sideRail .buildCost { font-size: 13px; }

    /* Selected tower: pinned to the bottom of the rail, so selecting one
       never shifts the shop under the cursor. */
    #sideRail #towerPanel {
      position: static; transform: none;
      left: auto; bottom: auto;
      width: 100%; margin-bottom: 0;
      flex: 0 0 auto;
      box-shadow: none;
    }
    #sideRail .tpName { font-size: 14px; }
    #sideRail .tpRow { font-size: 12px; }
    #sideRail .tpKills { font-size: 11px; }
    #sideRail .tpBtn { padding: 9px 6px; font-size: 12px; }

    /* ---- Editor: right-hand column, not a bottom sheet ---- */
    #editorPanel {
      left: auto; right: 0; top: 0; bottom: 0;
      width: 340px;
      max-height: none;
      padding: 12px;
      border-top: none;
      border-left: 1px solid var(--line);
    }
    #toolRow { grid-template-columns: repeat(3, 1fr); }
    .actionRow { grid-template-columns: repeat(3, 1fr); }
    .actionRow.playRow { grid-template-columns: 1fr 1fr; }
    #mapSizeRow { grid-template-columns: 1fr 1fr auto; }
    #mapSizeRow .hint { grid-column: 1 / -1; text-align: left; }

    /* ---- Menu: two columns ---- */
    .menuInner { max-width: 860px; }
    .menuHead { text-align: left; }
    #menu h1 { font-size: 46px; }
    #menu .tagline { font-size: 13px; margin-bottom: 26px; }
    .menuCols { grid-template-columns: 1.05fr 1fr; gap: 28px; align-items: start; }
    /* Both chip questions fit on one line here, which puts PLAY directly
       under them instead of a scroll away. */
    .setup { grid-template-columns: 1fr 1fr; gap: 14px; margin: 0 0 14px; }
    .playBtn { padding: 18px 12px; font-size: 20px; }
    .pickMapBtn { margin-top: 8px; padding: 11px 12px; font-size: 13px; }
    /* The folders stay FOLDED here, same as on a phone. They were briefly
       forced open on desktop on the theory that a mouse shouldn't have to
       click twice — but Load Levels and Level Creator are creator tools, and
       since the game went public they are not what a visitor came for. Four
       expanded entries pushed the three things people actually want
       (bosses, scoreboard, admin) down the column. */
    #menuFooter { text-align: left; }

    /* ---- Modals ---- */
    .modalBox { max-width: 560px; padding: 20px; }
    .modalBox h2 { font-size: 18px; }
    .modalBox .choices.towerChoices { grid-template-columns: 1fr 1fr 1fr; }
  }
