/*
 * Infinite CEO -- desktop theme "Executive Terminal" (near-black, hairline rules, monospace
 * figures, amber signal color -- a Bloomberg-desk feel), reskinned to a touch-first "Modern
 * Tile" theme under the mobile breakpoint below. Self-contained: no CDN, no external fonts --
 * this app's CSP is style-src/font-src 'self' only, so both themes lean on system font stacks
 * rather than the webfonts used in the original design mockups.
 *
 * Most components (cards, buttons, inputs, panels) are themed entirely through the custom
 * properties below, so the mobile media query at the bottom only needs to redefine those
 * properties plus the handful of structural rules (sidebar vs. bottom tab bar, ticker,
 * radius) that actually differ between the two directions.
 */

:root {
    --bg: #0a0c0f;
    --bg-panel: #101317;
    --bg-panel-alt: #0d0f12;
    --border: #22262c;
    --text: #c7ccd1;
    --text-bright: #e8e9eb;
    --text-dim: #5d6369;
    --accent: #e8a33d;
    --accent-strong: #f4bc63;
    --good: #4caf6d;
    --bad: #e05252;
    --warn: #e8a33d;
    /* Every moment the game puts a CHOICE in front of the player: a crisis, an opportunity, a
       rival's move, an executive's proposal, a dilemma. Deliberately neither green nor red --
       a colour that announces the answer lets a player work the entire event system without
       reading a word of it, and reading it is the game. Distinct from the amber of routine
       controls too, so a decision still arrives as an event rather than as another button. */
    --event: #3fb9d4;
    --radius: 4px;
    --font: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-mono: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

html, body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    margin: 0;
}

a { color: var(--accent-strong); }

h1, h2, h3 { line-height: 1.2; }

.top-nav {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: 0.75rem 1.5rem;
    border-bottom: 1px solid var(--border);
    background: var(--bg-panel);
}

/* Matches DWO's .parent-link exactly (same Dons AI Lab convention every game in the lab uses) --
   font-size 0.72rem, uppercase, dim, no underline. Sits first in .top-nav's flex row so it reads
   left of the brand without any extra layout work. */
.parent-link {
    font-size: 0.72rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-dim);
    text-decoration: none;
    white-space: nowrap;
}
.parent-link:hover, .parent-link:focus-visible { color: var(--text-bright); }

.top-nav .brand {
    font-weight: 700;
    font-size: 1.1rem;
    font-family: var(--font-mono);
    letter-spacing: 0.02em;
    color: var(--text-bright);
    text-decoration: none;
}

.top-nav nav { display: flex; gap: 1rem; flex: 1; }
.top-nav nav a { text-decoration: none; color: var(--text-dim); }
.top-nav nav a:hover, .top-nav nav a:focus-visible { color: var(--text); }
.account { display: flex; gap: 0.75rem; align-items: center; color: var(--text-dim); }

main { max-width: 1200px; margin: 0 auto; padding: 1.5rem; }

.site-footer {
    text-align: center;
    padding: 2rem 1rem;
    color: var(--text-dim);
}

/* CSS-only portrait lock: invisible unless the viewport is both narrow AND landscape. */
.rotate-lock {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 999;
    background: var(--bg);
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2rem;
}
.rotate-lock-message { color: var(--text-bright); font-size: 1.1rem; max-width: 24rem; }
/*
 * Gate on (hover: none) and (pointer: coarse) -- the media features that reliably identify a
 * touch-primary device (phone or tablet) -- not just a width/height threshold. A phone or
 * tablet has no hover-capable pointer, but an ordinary desktop browser window does, even when
 * resized narrow (e.g. a split-screen desktop window is still "landscape" by aspect ratio).
 * Without this distinction, narrowing a desktop window below the threshold would incorrectly
 * trip the lock on a screen that can't be rotated at all.
 */
@media (orientation: landscape) and (hover: none) and (pointer: coarse) {
    .rotate-lock { display: flex; }
    .top-nav, main, .site-footer { display: none; }
}

.panel {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.5rem;
}

.landing { text-align: center; padding: 4rem 1rem; }
.landing .tagline { color: var(--text-dim); max-width: 40rem; margin: 1rem auto 2rem; }
.landing-links { margin-top: 1rem; display: flex; gap: 1.5rem; justify-content: center; }

.cta {
    display: inline-block;
    background: var(--accent);
    color: #14151d;
    padding: 0.75rem 1.5rem;
    border-radius: var(--radius);
    text-decoration: none;
    font-weight: 700;
}
.cta:hover, .cta:focus-visible { background: var(--accent-strong); }

/* Forms */
form label {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 1rem;
    font-size: 0.9rem;
    color: var(--text-dim);
}

input, select, textarea, button {
    font-family: inherit;
    font-size: 1rem;
}

input, select, textarea {
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 6px;
    padding: 0.5rem 0.6rem;
}

input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible {
    outline: 2px solid var(--accent-strong);
    outline-offset: 1px;
}

/* The optional product-name field sits above its description textarea inside .product-pitch (see PITCH_ACTIONS/renderDashboard in game.js) -- just needs a little breathing room between the two. */
.product-pitch input[type="text"] { display: block; width: 100%; margin-bottom: 0.5rem; }

.product-card button { margin-top: 0.5rem; margin-right: 0.4rem; }

button {
    background: var(--accent);
    color: #14151d;
    border: none;
    border-radius: 6px;
    padding: 0.6rem 1.2rem;
    cursor: pointer;
    font-weight: 700;
}
button:hover { background: var(--accent-strong); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.secondary { background: var(--bg-panel-alt); border: 1px solid var(--border); color: var(--text); }

.hint { color: var(--text-dim); font-size: 0.8rem; }

/*
 * A cost badge sitting INSIDE a filled button.
 *
 * .hint is dim grey, which is correct everywhere it normally lives -- on a panel, next to body
 * text. Inside a filled button it is grey on a bright accent fill, and at 0.8rem that is genuinely
 * hard to read. The event banners are the worst case, because their fill is the most saturated
 * colour on the screen and the cost is the one part of the button a player actually has to
 * READ -- the label tells them what it does, the badge tells them whether they can afford it.
 *
 * Inheriting the button's own foreground restores the contrast the button was designed with,
 * whatever colour that button happens to be, and the opacity keeps the cost subordinate to the
 * label rather than competing with it. Monospace because it is a figure: it lines the digits up
 * and stops the price changing width as it changes value, which is the same reason the action
 * list has used it all along.
 *
 * Secondary buttons sit on a dark panel rather than a bright fill, so they take the accent the
 * action list has always used for the same badge -- which, measured, is 8.9:1 against the dim
 * grey's 3.2:1. The grey was under the threshold there too; it was simply less obvious than on
 * cyan, and a price the player cannot read is no more useful on a dark button than a bright one.
 *
 * The opacity was measured rather than eyeballed, across every button fill in BOTH themes. The
 * grey it replaces was 2.63:1 on the event fill. 0.74 gave 4.28:1 and 0.82 gave 5.08:1, both of
 * which looked fine on the cyan and still left the second theme's violet accent at 4.30:1 --
 * under the 4.5:1 that text this small wants. 0.90 is the first value where the worst fill in
 * either theme clears it (4.86:1) and the rest sit between 6:1 and 7.3:1.
 */
button .hint { font-family: var(--font-mono); color: inherit; opacity: 0.90; }
button.secondary .hint { color: var(--accent); opacity: 1; }
.error { color: var(--bad); font-weight: 600; }
.gameover-epitaph { font-style: italic; font-size: 1.05rem; max-width: 42rem; }

/* The one place a colour is allowed to carry a verdict. These are not choices -- they are the
   game telling a CEO that the board is close to firing them, that the company is close to
   running out of money, that someone is close to buying it out from under them. That is how
   such news reaches a real chief executive, and the urgency is part of the information. */
.warning-banner {
    background: color-mix(in srgb, var(--bad) 12%, var(--bg-panel-alt));
    border: 1px solid var(--bad);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
}
.warning-banner strong { color: var(--bad); }
.warning-banner ul { margin: 0.4rem 0 0; padding-left: 1.2rem; }
.warning-banner li { margin-bottom: 0.15rem; }

/*
 * Every moment that puts a choice to the player wears the same clothes: a crisis, an
 * opportunity, a rival's move, an executive's proposal, a dilemma. It is still a live event
 * with a countdown rather than a slow-building consequence of trending stats -- that is what
 * .warning-banner is for -- and it still carries its own button rather than making the player
 * hunt for the response in the Strategic Actions list.
 *
 * What changed is the colour. Crises were red and opportunities green, down to a filled button
 * in the matching colour, which meant a player could work the entire event system without
 * reading any of it: press the green one, brace for the red one. Whether a choice turns out
 * well is the question being asked; it cannot also be the answer on display. Some of these are
 * ruinous and some are the best thing that happens all year, and telling them apart is the
 * skill the game is actually testing.
 *
 * So the treatment is vivid rather than muted -- the accent bar, the filled button, a colour
 * used nowhere else in the interface. These are the moments a run turns on and they should
 * feel like it. They just must not feel like a verdict.
 */
.event-banner {
    background: color-mix(in srgb, var(--event) 13%, var(--bg-panel-alt));
    border: 1px solid var(--event);
    border-left: 3px solid var(--event);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    display: grid;
    gap: 0.4rem;
}
.event-banner-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.75rem; }
.event-banner-head strong { color: var(--event); }
.event-action-btn {
    justify-self: start;
    background: var(--event);
    color: #07222a;
    border: none;
}
.event-action-btn:hover { background: color-mix(in srgb, var(--event) 85%, #fff); }

/* A dilemma offers two real options and neither of them is the default. They share one row, at
   the same size and in the same colour, so the layout gives away no more than the palette
   does -- there is no button that is obviously the safe one to press. */
.event-options { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.event-options .event-action-btn { justify-self: auto; }

/* Feedback overlay: every action result, centered over the viewport regardless of
   where the dashboard is scrolled to. */
.feedback-overlay {
    position: fixed;
    inset: 0;
    background: rgba(4, 6, 12, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
    padding: 1rem;
}
/* Same equal-specificity trap as .tab-panel: without this, [hidden] loses to the rule
   above and the overlay stays visible even while the hidden attribute is true. */
.feedback-overlay[hidden] { display: none; }
.feedback-box {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.5rem;
    max-width: 26rem;
    width: 100%;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
#feedback-message { font-size: 1rem; line-height: 1.4; }
.feedback-overlay.is-error #feedback-message { color: var(--bad); font-weight: 600; }
.feedback-cost {
    margin-top: 0.75rem;
    padding: 0.6rem 0.75rem;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 0.85rem;
    font-family: var(--font-mono);
    color: var(--text-dim);
}
.feedback-box button { margin-top: 1.1rem; width: 100%; }

/* Game shell */
/* Desktop only: pins the shell to the viewport below .top-nav (measured at 46px) so only
   .game-main scrolls internally -- the ticker, topbar, and sidebar stay put while a player
   scrolls a long tab (e.g. News). Reset back to natural document flow on mobile below, which
   keeps its own sticky-topbar/fixed-bottom-tabs approach instead (see the comment on
   .game-shell's overflow: visible in the mobile media query for why the two techniques don't mix). */
.game-shell { padding: 0; overflow: hidden; height: calc(100vh - 46px); display: flex; flex-direction: column; }

/* Scrolling market ticker -- desktop only (see the mobile override at the bottom). Built by
   game.js from the live competitor/cycle/news data in the current GameView. */
.ticker-strip {
    height: 28px;
    background: var(--bg-panel-alt);
    border-bottom: 1px solid var(--border);
    overflow: hidden;
    display: flex;
    align-items: center;
}
.ticker-strip:empty { display: none; }
.ticker-track {
    display: flex;
    gap: 40px;
    white-space: nowrap;
    padding-left: 16px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-dim);
    animation: ticker-scroll 32s linear infinite;
}
.ticker-track .delta-up, .ticker-track .delta-down { font-weight: 600; }
@keyframes ticker-scroll {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
}

.topbar {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    flex-wrap: wrap;
    padding: 0.9rem 1.25rem;
    border-bottom: 1px solid var(--border);
}
#hdr-company-name { font-family: var(--font-mono); font-size: 1rem; color: var(--text-bright); letter-spacing: 0.02em; }
#hdr-week { color: var(--text-dim); margin-left: 0.6rem; font-size: 0.85rem; }
/* EP is the number a player needs to check before every single click -- bigger than the other
   topbar stats on purpose, not just another figure in the row. */
/* Never wraps. It is the scarcest resource in the game and the one figure a player checks
   before every decision -- at phone width it was breaking across a line and rendering as
   "EP 4 /" then "4", which reads as two numbers. */
.hdr-ep { white-space: nowrap; }
#hdr-ep { font-size: 1.3rem; }
.topbar-stats { display: flex; gap: 1.5rem; flex: 1; color: var(--text-dim); font-family: var(--font-mono); font-size: 0.9rem; }
.topbar-stats b { color: var(--text-bright); font-weight: 600; }

/* Sidebar (desktop) / bottom tab bar (mobile, see override below). */
.game-body { display: flex; align-items: stretch; flex: 1; min-height: 0; }
.game-main { flex: 1; min-width: 0; padding: 1.25rem; overflow-y: auto; }

/* Desktop only -- the tab list plus the persistent Company Detail tiles below it (see
   .sidebar-detail and #company-detail-sidebar in Play.cshtml), sharing one bordered column so
   they read as a single sidebar rather than two unrelated pieces. */
.sidebar-col { display: flex; flex-direction: column; width: 210px; flex-shrink: 0; border-right: 1px solid var(--border); }

.tabs {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    padding: 0.75rem 0;
    gap: 0.1rem;
}

/* A second, compact rendering of the same Company Detail tiles renderDashboard() already builds
   -- single column, smaller type, no section heading (the tab list right above it already says
   where this is). Hidden on mobile (see the media query below), where the Dashboard tab's own
   copy (.dashboard-company-detail) is what shows instead -- there's no sidebar column on a phone
   to hang this off of. */
.sidebar-detail { padding: 0.6rem 0.7rem; overflow-y: auto; border-top: 1px solid var(--border); flex: 1; min-height: 0; }
.sidebar-detail h2 { display: none; }
.sidebar-detail .card-grid { display: flex; flex-direction: column; gap: 1px; }
.sidebar-detail .card { padding: 0.4rem 0.55rem; }
.sidebar-detail .card h3 { font-size: 0.58rem; margin-bottom: 0.15rem; }
.sidebar-detail .card .value { font-size: 0.8rem; }

/* Hidden on desktop by default -- the sidebar copy above is what shows there; this is the
   mobile-only fallback (no sidebar column exists in the bottom-tab-bar layout), toggled back on
   in the mobile media query below. */
.dashboard-company-detail { display: none; }

/* Mobile-only copy of the binding-constraint sentence -- on desktop the sidebar already shows
   it, permanently, above the same numbers it is describing. */
.dashboard-coach { display: none; }

/* The cost of a hiring wave, while it is being paid. Wears the event colour because it is the
   game telling the player something is happening to them, not a warning. */
/* ---- The workbench: what is being built, and how crowded it is -------------------- */
.build-list { display: grid; gap: 0.4rem; margin-bottom: 1.4rem; }
.build-row {
    display: grid;
    grid-template-columns: 1fr 5rem 8rem;
    gap: 0.75rem;
    align-items: center;
    padding: 0.5rem 0.75rem;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border);
    border-radius: var(--radius);
}
.build-flagship { border-left-color: var(--accent); }
.build-name { font-weight: 600; }
.build-weeks { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-dim); }
.build-bar { height: 6px; background: var(--bg-panel); border-radius: 3px; overflow: hidden; }
.build-fill { display: block; height: 100%; background: var(--accent); }
/* Width by class rather than an inline style -- this app's CSP blocks the style attribute. */
.build-fill-0 { width: 0; } .build-fill-1 { width: 10%; } .build-fill-2 { width: 20%; }
.build-fill-3 { width: 30%; } .build-fill-4 { width: 40%; } .build-fill-5 { width: 50%; }
.build-fill-6 { width: 60%; } .build-fill-7 { width: 70%; } .build-fill-8 { width: 80%; }
.build-fill-9 { width: 90%; } .build-fill-10 { width: 100%; }

@media (max-width: 760px) {
    .build-row { grid-template-columns: 1fr auto; }
    .build-bar { grid-column: 1 / -1; }
}

.onboarding-note {
    background: color-mix(in srgb, var(--event) 11%, var(--bg-panel-alt));
    border-left: 3px solid var(--event);
    border-radius: var(--radius);
    padding: 0.6rem 0.8rem;
    margin: 0 0 0.9rem;
    font-size: 0.85rem;
    line-height: 1.45;
}

/* ---- Market outlook board (Markets tab) ---------------------------------------- */
/* The career is played on this board: a sector warming up somewhere you are NOT is the
   opening, and the year boundary is the only moment you can act on it. */
/*
 * A short shock, above the long outlook rows and deliberately not shaped like them. The rows
 * are a table of where nine markets are heading over months; this is one thing happening to
 * this player now, and it wants to read as a notice rather than as another row.
 */
.outlook-shock {
    border-left: 3px solid var(--event);
    background: color-mix(in srgb, var(--event) 10%, var(--bg-panel-alt));
    border-radius: var(--radius);
    padding: 0.7rem 0.9rem;
    margin-bottom: 0.9rem;
    line-height: 1.4;
}
.outlook-shock-good { border-left-color: var(--good); background: color-mix(in srgb, var(--good) 10%, var(--bg-panel-alt)); }
.outlook-shock-bad { border-left-color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, var(--bg-panel-alt)); }

.outlook-list { display: grid; gap: 0.4rem; margin-bottom: 1.4rem; }
.outlook-row {
    display: grid;
    grid-template-columns: 12rem 6rem 1fr;
    gap: 0.75rem;
    align-items: baseline;
    padding: 0.55rem 0.75rem;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border);
    border-radius: var(--radius);
}
.outlook-name { font-weight: 600; }
.outlook-phase { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); }
.outlook-note { color: var(--text-dim); font-size: 0.85rem; }
.outlook-yours { border-left-color: var(--accent); }

/* Warming is the one worth noticing, so it is the one that is coloured. It is also the one
   where nothing has happened yet -- which is the entire point of it existing. */
.outlook-warming { border-left-color: var(--event); }
.outlook-warming .outlook-phase { color: var(--event); }
.outlook-boom { border-left-color: var(--good); }
.outlook-boom .outlook-phase { color: var(--good); }
.outlook-slump { border-left-color: var(--bad); }
.outlook-slump .outlook-phase { color: var(--bad); }
.outlook-cooling .outlook-phase { color: var(--text-dim); }

/* ---- The outlook on a career offer --------------------------------------------- */
.offer-outlook {
    margin-top: 0.6rem;
    padding: 0.5rem 0.7rem;
    border-radius: var(--radius);
    background: var(--bg-panel-alt);
    border-left: 3px solid var(--border);
    font-size: 0.85rem;
}
.offer-outlook-up { border-left-color: var(--good); }
.offer-outlook-down { border-left-color: var(--bad); }

/* ---- Breakthrough readiness ---------------------------------------------------- */
/* A checklist, never a percentage: the player should know what they are building toward,
   not what their odds are this week. */
.readiness-intro { margin: 0 0 0.6rem; max-width: 46rem; }
.ready-list { display: grid; gap: 0.35rem; margin-bottom: 1.4rem; }
.ready-item {
    display: grid;
    grid-template-columns: 1.4rem 14rem 1fr;
    gap: 0.5rem;
    align-items: baseline;
    padding: 0.45rem 0.7rem;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-dim);
}
.ready-item.ready-yes { color: var(--text); border-color: var(--good); }
.ready-mark { font-family: var(--font-mono); }
.ready-item.ready-yes .ready-mark { color: var(--good); }
.ready-label { font-weight: 600; }

@media (max-width: 760px) {
    .outlook-row { grid-template-columns: 1fr auto; }
    .outlook-note { grid-column: 1 / -1; }
    .ready-item { grid-template-columns: 1.4rem 1fr; }
    .ready-detail { grid-column: 2 / -1; }
}

/* The phone filters the action list to one discipline at a time; the desktop shows all of
   them at once and has no use for the chips. */
.action-chips { display: none; }

/* Short tab labels are a phone concession -- there is room for the real ones on a desktop. */
.tab-short { display: none; }

/* Leaving a run, which is a decision ABOUT the run rather than a move within it -- so it sits
   under the sidebar rather than among the Strategic Actions, in nobody's way and one click from
   anywhere. Deliberately quiet: no accent, no event cyan, no warning red. It is not an
   opportunity, it is not an emergency, and it must never be the brightest thing on screen. The
   confirm step is what stops a mis-click, not the styling. */
.sidebar-restart { padding: 0.55rem 0.7rem; border-top: 1px solid var(--border); flex-shrink: 0; }
.dashboard-restart { display: none; margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.restart-block button { width: 100%; font-size: 0.78rem; padding: 0.4rem 0.6rem; }
.restart-block .hint { margin: 0.35rem 0 0; line-height: 1.35; }
.restart-block .restart-confirm { margin-bottom: 0.3rem; }

/* The way back out of the founding form when it was reached mid-run. */
.create-return {
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    margin-bottom: 1.25rem;
}
.create-return .hint { margin: 0 0 0.6rem; }
.tab-btn {
    background: transparent;
    color: var(--text-dim);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    border: none;
    border-left: 2px solid transparent;
    border-radius: 0;
    text-align: left;
    padding: 0.55rem 1.25rem;
}
.tab-btn.active { color: var(--accent); border-left-color: var(--accent); background: var(--bg-panel-alt); }
.tab-btn:hover { color: var(--text); background: var(--bg-panel-alt); }

.tab-panel { display: grid; gap: 1rem; }
/* Equal specificity to the UA stylesheet's [hidden]{display:none}, and later in source order,
   so without this a hidden tab-panel would still render as a grid. */
.tab-panel[hidden] { display: none; }

/* Dense hairline-divided stat grid: the container's border color shows through a 1px gap
   between cells, so it reads as a ruled table without an actual <table>. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
}

.card {
    background: var(--bg-panel-alt);
    padding: 0.85rem 1rem;
}
.card h3 {
    margin: 0 0 0.4rem;
    font-size: 0.7rem;
    font-family: var(--font-mono);
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}
.card .value { font-size: 1.15rem; font-family: var(--font-mono); font-weight: 600; color: var(--text-bright); }

/* Labeled progress bars -- Board Approval / Ethics / CEO Stress. A flat reading of "how close
   to trouble" that works the same as a compact bar on desktop or a rounded tile on mobile. */
.gauge-row { display: flex; gap: 1px; background: var(--border); border: 1px solid var(--border); margin-bottom: 0.9rem; }
.gauge { flex: 1; background: var(--bg-panel-alt); padding: 0.45rem 0.7rem; }
.gauge-head { display: flex; justify-content: space-between; font-size: 0.7rem; font-family: var(--font-mono); color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; }
.gauge-head span:last-child { color: var(--text-bright); font-size: 0.8rem; }
/* An inline SVG bar (width set via the `width` attribute in game.js), not a div sized with
   inline CSS -- this app's CSP (style-src 'self') blocks the `style="..."` attribute outright,
   but SVG presentation attributes like `width`/`fill` aren't CSS and are unaffected. */
.gauge-track { width: 100%; height: 4px; display: block; margin-top: 0.3rem; border-radius: 2px; overflow: hidden; }
.gauge-bg { fill: var(--bg); }
.gauge-fill.good { fill: var(--good); }
.gauge-fill.warn { fill: var(--warn); }
.gauge-fill.bad { fill: var(--bad); }

.action-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.6rem; }
.action-list button {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    text-align: left;
    padding: 0.6rem 0.85rem;
}
.action-list button .hint { font-family: var(--font-mono); color: var(--accent); font-size: 0.75rem; }

.chart-container {
    position: relative;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem;
}
.price-chart-svg { width: 100%; height: 280px; display: block; }
.chart-gridline { stroke: var(--border); stroke-width: 1; }
.chart-axis-label { fill: var(--text-dim); font-size: 10px; }

/* Sits inside the chart card, top-right, so the toggle doesn't cost its own row of vertical space. */
.chart-toggle { position: absolute; top: 0.75rem; right: 0.75rem; display: flex; gap: 0.4rem; z-index: 1; }
.chart-toggle-btn { padding: 0.25rem 0.6rem; font-size: 0.8rem; }
.chart-toggle-btn.active { background: var(--accent); color: var(--bg); border-color: var(--accent); }

.chart-legend { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.5rem; }
.chart-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.8rem; color: var(--text-dim); }
.chart-swatch { width: 10px; height: 10px; display: inline-block; flex-shrink: 0; }

/* Explicit up/down text markers alongside color, for colorblind accessibility. */
.delta-up { color: var(--good); }
.delta-down { color: var(--bad); }
.delta-up::before { content: "▲ "; }
.delta-down::before { content: "▼ "; }

.data-table { width: 100%; border-collapse: collapse; }
.data-table th, .data-table td {
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--border);
}
.data-table th { color: var(--text-dim); font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; }
.data-table td { font-family: var(--font-mono); font-size: 0.85rem; }

/*
 * Markets, desktop: one dense hairline-divided grid (same technique as .card-grid) instead of
 * the old separate Competitors tab + tile grid + trading table -- all three used to show the
 * same handful of companies. Every ticker's trade controls stay inline in its own row, matching
 * the terminal's "everything visible, nothing to select first" density -- there's room for it.
 */
.markets-list { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); }
.market-row {
    display: grid;
    grid-template-columns: 1.8fr 1.2fr 0.8fr 0.8fr 0.6fr 0.6fr 0.9fr 2.6fr;
    gap: 0.5rem;
    align-items: center;
    background: var(--bg-panel-alt);
    padding: 0.6rem 0.85rem;
    font-family: var(--font-mono);
    font-size: 0.85rem;
}
.market-header {
    font-size: 0.7rem;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.5rem 0.85rem;
}
.market-swatch { margin-right: 0.5rem; vertical-align: middle; }
.market-col-industry { color: var(--text-dim); font-size: 0.8rem; }
.market-col-shares { color: var(--text-dim); font-size: 0.75rem; }
.market-col-trade { display: flex; align-items: center; gap: 0.4rem; }
.trade-qty-inline, .trade-panel-qty { width: 3.5rem; padding: 0.35rem 0.4rem; }
.trade-btn-inline, .trade-btn-panel { padding: 0.35rem 0.6rem; font-size: 0.78rem; }

/* A clicked row gets a left accent border -- only meaningful on mobile (it targets the shared
   trade panel), but harmless and gently informative on desktop too. */
.market-row.selected { border-left: 2px solid var(--accent); padding-left: calc(0.85rem - 2px); }

/* Hidden on desktop; only mobile shows a persistent trade panel (see the mobile override below). */
.trade-panel { display: none; }

/* Desktop already has explicit Long/Short columns -- the compact "10L" badge is a mobile-only
   stand-in for those columns (which get hidden there), so it stays off here to avoid showing the
   same holding twice. */
.market-holdings-badge { display: none; }

/* The board seat that comes with a controlling stake. A full-width strip under the row it
   belongs to, rather than another column: it is about that one company, not about any one
   number in the table. */
.board-seat-row {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.55rem;
    padding-top: 0.55rem;
    border-top: 1px solid var(--border);
    font-size: 0.78rem;
}
.board-seat-badge {
    background: var(--accent);
    color: var(--bg);
    padding: 0.15rem 0.45rem;
    border-radius: 2px;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}
.directive-btn, .replace-ceo-btn { padding: 0.3rem 0.55rem; font-size: 0.75rem; }
.directive-btn.active { border-color: var(--accent); color: var(--accent); }

.news-week-heading { margin: 1.1rem 0 0.4rem; font-size: 0.8rem; color: var(--text-dim); font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.05em; }
.news-week-heading:first-child { margin-top: 0; }
.news-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; }
.news-list li { background: var(--bg-panel-alt); border: 1px solid var(--border); border-radius: 6px; padding: 0.6rem 0.8rem; }

.help-panel dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.4rem 1rem; }
.help-panel dt { font-weight: 600; }
.help-panel dd { margin: 0; color: var(--text-dim); }

/*
 * A display equation -- set apart from the prose, the way a textbook sets one apart, because
 * the identity it carries is the one the whole revenue section is built on.
 *
 * It exists as a class because this app's CSP is style-src 'self', which blocks the style="..."
 * attribute outright: the browser refuses to parse it at all (p.style.length comes back 0), so
 * the markup asked for centring, got none, and logged a violation on every view of the page.
 * Nothing warns you -- the paragraph just quietly renders like any other.
 */
.formula {
    margin: 1.2rem auto;
    max-width: 34rem;
    padding: 0.85rem 1rem;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--accent);
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.formula strong { font-weight: 600; }

/* Company-creation dice buttons: pick a random value for the field beside them. */
.field-with-dice { display: flex; gap: 0.5rem; align-items: center; }
.field-with-dice input { flex: 1; }
.dice-btn {
    flex-shrink: 0;
    width: 2.4rem;
    height: 2.4rem;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
}
.dice-btn:hover { background: var(--border); }
.dice-btn { color: var(--text); }
.dice-btn svg { width: 1.3rem; height: 1.3rem; }

.icon-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
    gap: 0.5rem;
    margin-top: 0.4rem;
    max-height: 15rem;
    overflow-y: auto;
    padding: 0.25rem;
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.icon-swatch { padding: 0.35rem; background: transparent; border: 2px solid transparent; border-radius: 8px; }
.icon-swatch svg { width: 100%; height: 100%; display: block; }
.icon-swatch.selected { border-color: var(--accent); }
.icon-swatch:hover { background: var(--border); }

/* Header icon badge, next to the company name. */
#hdr-icon { display: inline-flex; width: 1.6rem; height: 1.6rem; vertical-align: middle; margin-right: 0.5rem; }
#hdr-icon svg { width: 100%; height: 100%; display: block; }
#hdr-icon[hidden] { display: none; }
.hdr-identity { display: flex; flex-direction: column; }
.hdr-identity-row { display: flex; align-items: center; }
/* Tight against the company name on purpose -- its own row, same subdued register as #hdr-week
   (lowercase, dim, small), just directly below rather than sharing the identity line. */
.hdr-industry { color: var(--text-dim); font-size: 0.78rem; text-transform: lowercase; margin-top: 0.1rem; }

/*
 * Mobile / tablet portrait -- "Modern Tile" theme (Direction B). Redefining the custom
 * properties above re-themes every component that already reads them (cards, buttons,
 * inputs, panels, the gauge bars, the chart); everything below this line is the handful of
 * structural rules that genuinely differ: bottom tab bar instead of a sidebar, rounded
 * tiles instead of hairline-divided grids, no ticker, and plain system fonts instead of the
 * desktop's monospace figures.
 */
@media (max-width: 700px) {
    :root {
        --bg: #121319;
        --bg-panel: #16171f;
        --bg-panel-alt: #1d1f2b;
        --border: #2a2d3d;
        --text: #e7e6ef;
        --text-bright: #ffffff;
        --text-dim: #9a98ac;
        --accent: #8a7bff;
        --accent-strong: #a396ff;
        --good: #63d47a;
        --bad: #ff9b6a;
        --warn: #e8a33d;
        --event: #4fd0e0;
        --radius: 14px;
    }

    /*
     * One row, not three. At 375px the header wrapped into "« Don's AI Lab / Infinite CEO",
     * then five links, then "Sign in" -- 138px, seventeen per cent of the viewport, spent on
     * navigation a player uses once a session and placed above everything they use every week.
     *
     * The links stay visible rather than going behind a menu button: in-game navigation is the
     * bottom tab bar, these are the occasional ones, and a scrolling strip costs no new
     * interaction model and no site-wide script. The lab back-link goes, since the browser
     * already has a back button and the wordmark still links home.
     */
    .top-nav {
        flex-wrap: nowrap;
        gap: 0.9rem;
        padding: 0.45rem 0.9rem;
        overflow-x: auto;
        scrollbar-width: none;
    }
    .top-nav::-webkit-scrollbar { display: none; }
    .top-nav .parent-link { display: none; }
    .top-nav .brand { font-size: 0.95rem; white-space: nowrap; }
    .top-nav nav { gap: 0.9rem; white-space: nowrap; }
    .top-nav nav a, .top-nav .account a, .top-nav .account span { font-size: 0.78rem; white-space: nowrap; }
    .top-nav .account { margin-left: auto; padding-left: 0.5rem; }

    main { padding: 1rem; }

    .top-nav .brand,
    #hdr-company-name, .topbar-stats, .tab-btn, .card h3, .card .value,
    .gauge-head, .data-table th, .data-table td {
        font-family: var(--font);
    }

    .ticker-strip { display: none; }

    /*
     * .game-shell's overflow: hidden (desktop-only need: it clips the ticker-strip's square
     * corners inside the panel's rounded top corners) makes it the nearest ancestor a sticky
     * descendant sticks relative to -- and since #game-shell itself never scrolls (the page's
     * <body> does), a sticky child inside it just behaves like position: relative instead of
     * actually sticking. The ticker is hidden entirely on mobile anyway, so there's nothing left
     * to clip here.
     */
    .game-shell { overflow: visible; height: auto; display: block; }

    /*
     * Sticky, not scroll-away: Cash / Company Value / EP / Advance Week are the numbers a
     * player needs while scanning Strategic Actions to decide what they can afford -- playtesting
     * on a phone-sized viewport showed the topbar scrolling out of view within the first couple
     * of stat cards, well before reaching the action buttons it exists to inform.
     */
    .topbar {
        padding: 0.6rem 1rem;
        border-radius: var(--radius) var(--radius) 0 0;
        position: sticky;
        top: 0;
        z-index: 40;
        background: var(--bg-panel);
        flex-wrap: wrap;
        row-gap: 0.3rem;
        column-gap: 0.75rem;
    }
    /* Two rows instead of one overflowing one: identity gets its own full-width row (with an
       ellipsis so a long company name can't push the layout wider than the screen), stats and
       the Advance Week button share the row below. */
    .hdr-identity { flex: 1 1 100%; order: 1; min-width: 0; }
    #hdr-company-name { font-size: 0.95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 55vw; display: inline-block; vertical-align: middle; }
    #hdr-week { display: inline; font-size: 0.72rem; }
    .topbar-stats { order: 2; font-size: 0.78rem; gap: 0.7rem; flex: 1 1 auto; }
    #advance-week-btn { order: 3; flex: 0 0 auto; padding: 0.5rem 0.7rem; font-size: 0.8rem; white-space: nowrap; }

    /* Bottom tab bar instead of the desktop sidebar; leave room for it below the content. */
    .game-body { flex-direction: column; }
    /* No sidebar column on mobile -- .tabs becomes the fixed bottom bar (its own position:fixed
       rule below escapes .sidebar-col's flow regardless), and the persistent Company Detail
       sidebar has nowhere to live, so the Dashboard tab's own copy takes over instead. */
    .sidebar-col { width: auto; border-right: none; }
    .sidebar-detail { display: none; }
    .dashboard-company-detail { display: block; }

    /* Wears the event colour because it is the game putting something to the player, and it
       sits directly above Strategic Actions because that is what it is advising on. */
    .dashboard-coach {
        display: block;
        background: color-mix(in srgb, var(--event) 11%, var(--bg-panel-alt));
        border-left: 3px solid var(--event);
        border-radius: var(--radius);
        padding: 0.55rem 0.75rem;
        margin: 0.85rem 0 0;
        font-size: 0.8rem;
        line-height: 1.4;
    }

    /* A pill, so it cannot break across a line however long the cap gets. */
    .hdr-ep {
        display: inline-flex;
        align-items: baseline;
        gap: 0.25rem;
        border: 1px solid var(--accent);
        border-radius: 999px;
        padding: 0.1rem 0.55rem;
        font-size: 0.68rem;
        line-height: 1.5;
    }
    #hdr-ep { font-size: 0.95rem; }

    .sidebar-restart { display: none; }
    .dashboard-restart { display: block; }

    /*
     * One discipline at a time. Nine one-word headings cost about 830px of heading and margin
     * against 1,252px of actual buttons -- roughly forty per cent of the action area was labels
     * and the air around them. The chip doubles as the heading, so the h3 goes with it.
     */
    /*
     * Wraps to two rows rather than scrolling sideways. The first version of this strip scrolled,
     * and playtesting caught it putting Political, People, Philanthropy and Operations past the
     * right edge -- 603px of chips into a 309px strip. That is precisely the bug that had just been
     * fixed on the tab bar one commit earlier: a horizontal scroller with nothing about it that
     * looks scrollable hides half its contents. A second row costs 37px and hides nothing.
     */
    .action-chips {
        display: flex;
        flex-wrap: wrap;
        gap: 0.3rem;
        margin: 0.7rem 0 0.6rem;
    }
    /* Sized so all eight fit two rows rather than three -- each extra row is 38px of a phone
       screen, and the tap target stays above 34px. */
    .action-chip {
        flex: 0 0 auto;
        min-height: 34px;
        padding: 0.3rem 0.5rem;
        border-radius: 999px;
        border: 1px solid var(--border);
        background: transparent;
        color: var(--text-dim);
        font-family: var(--font-mono);
        font-size: 0.58rem;
        font-weight: 500;
        letter-spacing: 0.05em;
        text-transform: uppercase;
    }
    .action-chip.is-on { background: var(--accent); border-color: var(--accent); color: #14151d; font-weight: 700; }
    .action-block { display: none; }
    .action-block.is-open { display: block; }
    .action-block h3 { display: none; }

    /* Two columns: each button measured 309px wide carrying 119px of text, so three fifths of
       every row was empty. Six actions now fit where one did. */
    .action-list { grid-template-columns: 1fr 1fr; gap: 0.5rem; }
    /* Spans both columns, and its controls fill it. A textarea sizes itself from its cols
       attribute unless told otherwise, so inside a grid cell it sat at its own intrinsic width and
       spilled its placeholder out of the card. */
    /* Both answers to a dilemma, exactly the same width. They wrap to their own lines at this
       width anyway, and at 258px against 237px the longer label was quietly reading as the main
       one -- which is the single thing the two-option design exists to prevent. */
    .event-options .event-action-btn { flex: 1 1 100%; justify-content: flex-start; }

    .action-list .product-pitch { grid-column: 1 / -1; min-width: 0; }
    .action-list .product-pitch textarea,
    .action-list .product-pitch input[type="text"] { width: 100%; box-sizing: border-box; }

    /*
     * One rhythm for the whole column. Every block on the Dashboard arrived carrying its own
     * desktop margins, and stacked on a phone they added up to 163px of gap between the topbar
     * and the first button -- more than a fifth of the screen spent on air. A flex gap replaces
     * all of them with one number that cannot compound or collapse.
     */
    #tab-dashboard { display: flex; flex-direction: column; gap: 0.6rem; }
    #tab-dashboard > * { margin-top: 0; margin-bottom: 0; }

    /* The count lives in the topbar pill now, so this heading only has to label the section --
       and on one line, not two. */
    #tab-dashboard > h2 { font-size: 1.05rem; }
    #tab-dashboard > h2 .hint { display: none; }

    .game-main { padding: 1rem; padding-bottom: 5rem; }
    .tabs {
        flex-direction: row;
        width: auto;
        border-right: none;
        border-top: 1px solid var(--border);
        background: var(--bg-panel);
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 50;
        padding: 0.35rem 0.15rem calc(0.5rem + env(safe-area-inset-bottom));
        gap: 0;
        /*
         * Seven equal columns that fit, rather than a strip that scrolls. It used to measure
         * 463px of tabs into a 375px screen, which put Financials and News off the right edge --
         * and nothing about a bottom bar suggests it can be swiped, so a player could finish a
         * whole run without finding the News feed, which is where every crisis, opportunity and
         * delayed consequence is reported. Short labels (see .tab-short) are what buy the room.
         */
        overflow-x: hidden;
    }
    .tab-btn {
        flex: 1 1 0;
        min-width: 0;
        min-height: 44px;
        text-align: center;
        border-left: none;
        border-radius: 10px;
        font-size: 0.6rem;
        letter-spacing: 0;
        padding: 0.4rem 0.1rem;
        white-space: nowrap;
        overflow: hidden;
    }
    .tab-name { display: none; }
    .tab-short { display: inline; }
    .tab-btn.active { border-left: none; background: color-mix(in srgb, var(--accent) 18%, transparent); }

    /*
     * Two dense columns instead of one full-width card per stat -- each card previously cost
     * ~110-140px of scroll for a single label+value line. This is the "Company Detail" grid
     * only (see renderDashboard's reordering): it now sits below Strategic Actions, so it's
     * fine for it to be denser and less prominent -- it's reference info, browsed occasionally,
     * not the thing a player scans every week.
     */
    .card-grid { grid-template-columns: repeat(2, 1fr); gap: 0.5rem; background: none; border: none; }
    .card { border-radius: var(--radius); border: 1px solid var(--border); padding: 0.55rem 0.7rem; }
    .card h3 { font-size: 0.62rem; margin-bottom: 0.15rem; }
    .card .value { font-size: 0.95rem; }
    .stat-detail-heading { color: var(--text-dim); font-size: 0.95rem; margin-top: 1.5rem; }

    /* Three across on one row rather than two-then-one wrapping to a second: these are a glance
       before the week is spent, not a read, and the wrapped version cost 109px of a 812px screen. */
    .gauge-row { gap: 0.4rem; background: none; border: none; flex-wrap: nowrap; margin-bottom: 0; }
    .gauge { border-radius: var(--radius); border: 1px solid var(--border); min-width: 0; flex: 1 1 0; padding: 0.45rem 0.5rem; }
    /* The label and its value share one flex row; without a gap "SECURITY" and "STEADY" touch. */
    .gauge-head { font-size: 0.55rem; gap: 0.35rem; align-items: baseline; }
    .gauge-head span:last-child { font-size: 0.72rem; }

    /*
     * Label above its cost badge instead of side-by-side -- squeezing both onto one row on a
     * 375px screen was forcing "International Expansion" and its cost badge to wrap unpredictably
     * across two ragged lines. Stacked, each is one clean line and the button height is smaller
     * and more consistent, not larger.
     */
    .action-list button { border-radius: var(--radius); min-height: 44px; flex-direction: column; align-items: flex-start; gap: 0.15rem; }
    .action-list button .hint { color: var(--text-dim); }

    /*
     * The Workforce table is the second-most-scrolled screen (8 departments + 5 executives),
     * and a plain HTML <table> just makes its columns awkwardly narrow at this width rather
     * than reflowing -- Hire/Lay Off ended up stacking to ~270px per row. This turns each row
     * into a compact two-line flex block instead: department + staff count on top, action
     * buttons filling a row underneath.
     */
    #tab-workforce table, #tab-workforce tbody, #tab-workforce tr, #tab-workforce td { display: block; width: 100%; }
    #tab-workforce thead { display: none; }
    #tab-workforce tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        padding: 0.6rem 0;
        border-bottom: 1px solid var(--border);
        gap: 0.4rem;
    }
    #tab-workforce td { border: none; padding: 0; }
    #tab-workforce td:first-child { flex: 1 1 auto; font-weight: 600; }
    #tab-workforce td:nth-child(2) { flex: 0 0 auto; color: var(--text-dim); font-size: 0.85rem; }
    #tab-workforce td:last-child { flex: 1 1 100%; display: flex; gap: 0.5rem; }
    #tab-workforce td:last-child button { flex: 1; min-height: 42px; font-size: 0.78rem; padding: 0.4rem 0.5rem; flex-direction: column; gap: 0.1rem; }

    /*
     * Markets, mobile: no room for a 7-column terminal row, let alone a fourth trade button per
     * row -- each ticker collapses to one compact price line (name, price, change, a tiny
     * holdings badge if any), and the header row/industry/explicit long-short columns/inline
     * trade controls that only make sense at desktop width are dropped entirely. Tapping a row
     * aims the single shared trade panel below the list at it instead of repeating four buttons
     * per row.
     *
     * The chart is also shorter here than on desktop -- with a fixed trade panel now permanently
     * docked over the bottom ~200px of the screen, a full-height chart pushed the ticker list
     * (the thing a player is actually here to look at and act on) out from under the fold
     * entirely.
     */
    .price-chart-svg { height: 140px; }
    .markets-list { background: none; border: none; gap: 0.5rem; }
    .market-header { display: none; }
    .market-row {
        grid-template-columns: 1fr auto auto;
        border-radius: var(--radius);
        border: 1px solid var(--border);
        min-height: 44px;
        font-size: 0.85rem;
    }
    .market-row.selected { border-color: var(--accent); padding-left: 0.85rem; }
    .market-col-industry, .market-col-held, .market-col-trade, .market-col-shares { display: none; }
    .market-holdings-badge {
        display: inline-block;
        background: var(--bg-panel);
        color: var(--accent-strong);
        font-family: var(--font);
        font-size: 0.65rem;
        font-weight: 700;
        padding: 0.1rem 0.4rem;
        border-radius: 999px;
        margin-left: 0.4rem;
    }

    /*
     * Docked just above the bottom tab bar, like the tab bar itself -- position: fixed, not
     * sticky. Sticky's "stuck" position is relative to how much room is left in its own normal
     * flow, and on a short list (3-4 tickers) that math can resolve to the panel floating up
     * over the list instead of sitting below it; fixed always anchors to the viewport itself.
     */
    .trade-panel {
        display: block;
        position: fixed;
        left: 1rem;
        right: 1rem;
        bottom: calc(3.5rem + env(safe-area-inset-bottom));
        z-index: 45;
        margin-top: 0;
        background: var(--bg-panel-alt);
        border: 1px solid var(--border);
        border-radius: var(--radius);
        padding: 0.75rem 0.85rem;
        box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.25);
    }
    .trade-panel-head { font-weight: 700; margin-bottom: 0.5rem; }
    .trade-panel-controls { display: flex; flex-wrap: wrap; gap: 0.4rem; }
    .trade-panel-controls button { flex: 1; min-width: 4rem; min-height: 42px; }
    .trade-panel-qty { width: 100%; flex: 1 1 100%; }

    /* Room for the fixed trade panel + tab bar so the last ticker row isn't hidden behind them. */
    #tab-markets { padding-bottom: 11rem; }
}
