/* The play surface's shared vocabulary — the controls a player acts THROUGH.
   ================================================================================================
   GLOBAL ON PURPOSE, and the purpose is #261. These rules used to live in Play.razor.css, where
   Blazor scoped them to that one component. Splitting the play surface into panes moves the markup
   they style into child components, and a scoped rule never reaches a child — so every one of these
   would have gone silently unstyled the moment its markup moved. That is the same failure the
   .login-shell block in app.css was written to fix, for the same reason.

   ⚠️ EVERYTHING IS ANCHORED UNDER `.game-shell`, which is the play page's root <main> and NOT a
   layout detail — it is there in the tab layout, it will be there in the floating one, and it is
   nowhere else in the application. That anchoring is load-bearing rather than tidy: forge.css
   already defines `.eyebrow`, and this file loads after it, so a bare `.eyebrow` here would silently
   restyle every Forge page. Anchored, it cannot.

   The division of labour, for whoever adds the next rule:
     · How a pane's CONTENT looks  -> here (it must survive the markup moving between hosts)
     · How panes are ARRANGED      -> the host's own scoped sheet, with ::deep where it must reach in

   Specificity note: `.game-shell .strike-button` scores 0,2,0, exactly what the scoped original
   scored as `.strike-button[b-…]`. Nothing else in the application defines any of these names —
   checked, name by name, before they were promoted. */

/* The base every eyebrow in the play surface inherits. Play.razor.css keeps its own copy for the
   markup that has not moved; both are identical and the scoped one wins ties by loading last. */
.game-shell .eyebrow{font-size:.7rem;font-weight:800;letter-spacing:.14em;color:#8b96b6;margin:0 0 .4rem}

/* Target-and-strike. Sits above the tracker because on your turn it is the thing you came here to use. */
.game-shell .strike-card{padding:.8rem .9rem;border:1px solid #3a4a6e;border-radius:14px;background:#131a2bd9}
.game-shell .strike-card .eyebrow{margin:0 0 .55rem}
.game-shell .strike-hint{margin:.5rem 0 0;font-size:.72rem;color:#8e9ab8;font-style:italic}

.game-shell .target-row{display:flex;flex-wrap:wrap;gap:.35rem}
.game-shell .target-chip{display:flex;align-items:center;gap:.4rem;border:1px solid #5c3238;background:#231518;color:#e6c3c7;border-radius:999px;padding:.3rem .6rem;font-size:.74rem;cursor:pointer}
.game-shell .target-chip span{font-size:.62rem;color:#a8848a;font-variant-numeric:tabular-nums}
.game-shell .target-chip:hover:not(:disabled){border-color:#a8434f}
.game-shell .target-chip.selected{background:#7c2a30;border-color:#c76b74;color:#ffe6e7}
.game-shell .target-chip.selected span{color:#f0c3c7}
.game-shell .target-chip:disabled{opacity:.5;cursor:default}

.game-shell .strike-row{display:flex;flex-wrap:wrap;gap:.35rem;margin-top:.6rem}
.game-shell .strike-button{display:flex;align-items:center;gap:.4rem;border:1px solid #8a6f37;background:#241d10;color:#f4d58d;border-radius:10px;padding:.4rem .7rem;font-size:.76rem;font-weight:700;cursor:pointer}
.game-shell .strike-button:hover:not(:disabled){background:#33280f;border-color:#c69446}
.game-shell .strike-button:disabled{opacity:.45;cursor:default}
.game-shell .strike-button.spell{border-color:#5c4a8d;background:#1d1830;color:#cbb8f0}
.game-shell .strike-button.spell:hover:not(:disabled){background:#282047;border-color:#8b74c9}
.game-shell .strike-button.action{border-color:#3f6b53;background:#152218;color:#a8dcbb}
.game-shell .strike-button.action:hover:not(:disabled){background:#1c3023;border-color:#5f9d78}
/* An unavailable action stays visible with its reason on hover: "you aren't holding a shield" is
   useful, a button that silently vanished is not. */
.game-shell .strike-button.action:disabled{opacity:.4}
.game-shell .action-row{margin-top:.35rem}
.game-shell .strike-button .glyph{font-size:.9rem;line-height:1}

/* An action greyed only because nothing is aimed at yet. Distinguished from the ordinary disabled state:
   that one means "you cannot do this", and this one means "you have not said what to do it TO" — which
   the player can fix in one click, so it should not look equally final. */
.game-shell .strike-button.action.needs-target{border-style:dashed;opacity:.5}

/* Which skill an action is rolled with, for the few whose rules leave the choice open — Escape's
   Athletics or Acrobatics (issue #65). Sits against its own button and stays out of the way. */
/* Two selectors, one look: both are a chooser butted against the right edge of the button it qualifies,
   and a cost picker that looked different from a skill picker would read as a different kind of control. */
.game-shell .skill-pick,.game-shell .cost-pick{margin-left:-.2rem;margin-right:.3rem;border:1px solid #3f6b53;border-left:none;
    border-radius:0 8px 8px 0;padding:.34rem .3rem;background:#152218;color:#a8dcbb;
    font-size:.72rem;cursor:pointer}
.game-shell .skill-pick:disabled,.game-shell .cost-pick:disabled{opacity:.4;cursor:not-allowed}

/* A rogue can do sixty things. The six that matter in a fight stay out; the rest fold away, because a
   panel full of buttons is the same as a panel with none. */
.game-shell .more-actions{margin-top:.5rem;border-top:1px dashed #3a3020;padding-top:.4rem}
.game-shell .more-actions summary{cursor:pointer;font-size:.72rem;font-weight:700;letter-spacing:.06em;
    text-transform:uppercase;color:#9a8b6a;list-style:none;padding:.2rem 0}
.game-shell .more-actions summary::-webkit-details-marker{display:none}
.game-shell .more-actions summary::before{content:"▸ ";display:inline-block;transition:transform .12s ease}
.game-shell .more-actions[open] summary::before{content:"▾ "}
.game-shell .more-actions summary:hover{color:#e2c98f}
.game-shell .more-actions summary span{margin-left:.4rem;padding:.05rem .35rem;border-radius:6px;
    background:#241d10;color:#c9b072;font-size:.68rem}

/* The attack and its repeat sit together: one swing, or the rest of the turn. */
.game-shell .strike-pair{display:inline-flex;align-items:stretch}
.game-shell .strike-pair .strike-button{border-top-right-radius:0;border-bottom-right-radius:0}
.game-shell .strike-repeat{border:1px solid #3a3020;border-left:none;border-radius:0 8px 8px 0;background:#241d10;
    color:#c9b072;font-size:.72rem;font-weight:700;letter-spacing:.04em;padding:0 .45rem;cursor:pointer}
.game-shell .strike-repeat:hover:not(:disabled){background:#2e2514;color:#e2c98f}
.game-shell .strike-repeat:disabled{opacity:.4;cursor:default}

/* End Turn. Rendered in two places that are now on opposite sides of a component boundary — under the
   composer, and on the combat surface — which is precisely why it cannot be scoped to either. */
.game-shell .end-turn{display:flex;align-items:center;gap:.5rem;border:1px solid #8a6f37;background:#241d10;color:#f4d58d;border-radius:10px;padding:.5rem .85rem;font-weight:700;cursor:pointer;white-space:nowrap}
.game-shell .end-turn:hover:not(:disabled){background:#33280f;border-color:#c69446}
.game-shell .end-turn:disabled{opacity:.5;cursor:default}
.game-shell .end-turn .left{font-weight:600;font-size:.7rem;color:#c9b47a;padding:.1rem .4rem;border-radius:999px;background:#3a2f14}

/* Every action spent: ending the turn is the only legal move, so the button stops being furniture and
   starts asking. Dave missed it several times in one session, and he wrote the thing. Deliberately the
   ONLY animation on the narration surface, so a pulse here means "you", never decoration. */
.game-shell .end-turn.spent{border-color:#c2453a;background:#3a1512;color:#ffd9d2;
    animation:end-turn-spent 1.5s ease-in-out infinite}
.game-shell .end-turn.spent .left{color:#ffcfc7;background:#5c1f19}
.game-shell .end-turn.spent:hover:not(:disabled){background:#4d1a16;border-color:#e0655a}
@keyframes end-turn-spent{
    0%,100%{background:#3a1512;border-color:#c2453a;box-shadow:0 0 0 0 rgba(194,69,58,0)}
    50%{background:#6d211a;border-color:#ff7a6c;box-shadow:0 0 0 4px rgba(194,69,58,.28)}
}
/* Honour a reader who has asked the platform for less movement: keep the alarm, drop the motion. */
@media (prefers-reduced-motion:reduce){
    .game-shell .end-turn.spent{animation:none;background:#6d211a;border-color:#ff7a6c}
}

/* The same End Turn control, on the surface where the turn is actually being spent. */
.game-shell .end-turn.combat-end{width:100%;margin-top:.6rem}

/* What the engine said about the last thing asked of it. */
.game-shell .command-feedback{margin:.2rem 0;padding:.65rem;border-radius:8px;font-size:.74rem}
.game-shell .command-feedback.accepted{background:#143528;color:#a5e5c6;border:1px solid #2e7656}
.game-shell .command-feedback.rejected{background:#422326;color:#ffb2b6;border:1px solid #8d4046}

/* The conversation bubble, and everything that wears one. Promoted in #261 phase 3.

   Shared by THREE surfaces and that is why it is here rather than scoped to any of them: TableLogView
   draws the narration and the rolls/audit lines, JournalPane draws the adventure's own notes in the same
   shape, and the thinking indicator on the narration surface is another bubble again. A copy per pane
   would be three renderings of one visual idea, drifting apart a colour at a time.

   ⚠️ `.token` rides along in the avatar rule deliberately, exactly as it did before the move. It is the
   map's token, not a log avatar, and it happens to want the same box — separating them here would mean
   RESTATING a rule rather than moving it, which is how a declaration goes missing. It stays inside
   .game-shell either way. */
.game-shell .message{display:flex;gap:.75rem;max-width:88%}
.game-shell .message.player{align-self:flex-end;flex-direction:row-reverse}
.game-shell .avatar,.game-shell .token{width:2.3rem;height:2.3rem;flex:0 0 auto;display:grid;place-items:center;border-radius:10px;background:#242d44;color:#bfcaf0}
.game-shell .message-body{background:#1a2235;border:1px solid #2c3957;border-radius:4px 14px 14px 14px;padding:.75rem 1rem}
.game-shell .message.player .message-body{background:linear-gradient(135deg,#2d4b70,#233c5c);border-color:#3b6394;border-radius:14px 4px 14px 14px}
.game-shell .message.engine .message-body{background:#151b28;border-color:#414c68}
.game-shell .message.system .message-body{background:#422326;border-color:#8d4046}
.game-shell .message-meta{display:flex;gap:.55rem;align-items:center}
.game-shell .message-meta strong{font-size:.83rem}
.game-shell .message-meta span{font-size:.62rem;letter-spacing:.1em;color:#9da8c5}
.game-shell .message p{margin:.35rem 0 0;line-height:1.55;color:#d9deea;white-space:pre-wrap}
/* Moved out of the page's own @media block with the rule it narrows. A responsive rule left behind is
   the worst kind of orphan: correct at every width anyone tests at, and wrong on a phone. */
@media(max-width:560px){.game-shell .message{max-width:96%}}
/* A companion's bubble. Found LEFT BEHIND in the page's scoped sheet by reading the compiled bundle,
   not the source — where it had become `.message.npc .message-body[b-fdu3vypdan]`, an attribute the
   pane's markup can never carry. Nothing would have errored; a companion's purple would have gone grey. */
.game-shell .message.npc .message-body{background:#25203a;border-color:#5d4d82}

/* Issue #226's readable measure. Roughly 90 characters: a column can be as wide as the monitor allows,
   a LINE cannot, and the wide-shell change raised the cap without this and traded one unreadable
   narration for another. LAST in this file on purpose — it must beat both the 88% base above and the
   small-screen 96%, which is the order it won in when all three were scoped to the page. */
.game-shell .message{max-width:min(88%,52rem)}

/* The scrolling log region. Shared: NarrationPane and JournalPane each own one, because in a floating
   layout each is a window with a log in it and there is no host container to borrow. */
.game-shell .journal{flex:1;overflow:auto;padding:1.2rem;display:flex;flex-direction:column;gap:1rem;min-height:0}
.game-shell.expanded .journal{flex:1}

/* Kept whole rather than split, exactly as .avatar,.token was. `.composer-row` moved into a pane and the
   other three did not — restating the rule to separate them is how a declaration goes missing, and all
   four are inside .game-shell either way. */
.game-shell .game-header,.game-shell .panel-heading,.game-shell .combatant-top,.game-shell .composer-row{display:flex;align-items:center}
/* Same reasoning: the composer moved, the combatant card has not (it is PartyPane's, still to come). */
.game-shell .action-composer p,.game-shell .combatant p{font-size:.72rem;color:#8e9ab8;margin:.55rem 0 0}

/* ⤳ The map's SHARED vocabulary, promoted in #261 phase 3. These style markup drawn by BOTH MapPane's
   docked board and the shell's full-screen layer — the ⤢ toggle, the zoom row, the screen lift, the
   wind compass, the admin's debug switch. The two are never mounted together (the one-board rule), but
   they are in different components, and a scoped rule reaches only one of them. */
.game-shell .expand-toggle{margin-left:.4rem;border:1px solid #303b58;background:#151b2b;color:#bec9e7;border-radius:8px;padding:.2rem .45rem;cursor:pointer;line-height:1}
.game-shell .expand-toggle:hover{background:#243050;border-color:#4a5c86}
/* Zoom. BattleMap has taken a DisplayScale since M10c with nothing to drive it. Sits ABOVE the board
   rather than floating over it: a control over the map is a control that covers a square.
   Reset's `margin-left:auto` used to push it up against the ⤢ that closed this row; with ⤢ moved to the
   panel header the row ends at Reset, which is where that rule was always aiming. */
.game-shell .map-zoom{flex:0 0 auto;display:flex;align-items:center;gap:.35rem}
.game-shell .map-zoom button{border:1px solid #34405c;background:#161d2c;color:#c3cbe6;border-radius:8px;
    padding:.2rem .6rem;font-weight:800;line-height:1.4;cursor:pointer}
.game-shell .map-zoom button:hover:not(:disabled){background:#212a40;border-color:#5a6a94}
.game-shell .map-zoom button:disabled{opacity:.35;cursor:default}
.game-shell .map-zoom .zoom-level{min-width:3.2rem;text-align:center;font-size:.74rem;font-weight:700;
    color:#9fb0dc;font-variant-numeric:tabular-nums}
.game-shell .map-zoom .zoom-reset{margin-left:auto;font-weight:600;font-size:.72rem;color:#8e9ab8}
/* The wind sits at the right end, as it does above the docked board — same reading, same place. */
.game-shell .map-fullscreen-bar .wind-compass{margin-left:auto}
.game-shell .map-fullscreen-bar .expand-toggle{flex:0 0 auto}
.game-shell .map-fullscreen-bar .map-zoom{flex:0 0 auto;margin:0}
.game-shell .map-fullscreen-bar .map-lift{flex:0 1 12rem;min-width:7rem}
.game-shell .map-fullscreen-bar .map-debug-toggle{flex:0 0 auto}
/* Fit is a state, not an action — lit when it is on, like the Searching and Sneaking chips. */
.game-shell .map-zoom .fit-toggle.on{background:#1d2740;color:#dcb763;border-color:#6c7ba6;font-weight:700}
/* The wind (issue #229 layer 3). Player-visible because everybody feels it, and because the mechanic it
   drives — how far a nose reaches — would look arbitrary without it. */
.game-shell .wind-compass{display:inline-flex;align-items:center;gap:.3rem;font-size:.68rem;color:#9fb0dc;cursor:help}
.game-shell .wind-label{font-weight:700;color:#b9c6e6;letter-spacing:.02em}
.game-shell .wind-arrow{display:inline-block;font-size:.95rem;line-height:1;color:#7fa8e8}
.game-shell .wind-word{font-variant-numeric:tabular-nums;letter-spacing:.02em;text-transform:capitalize}
/* The screen lift (issue #228). Under the zoom row rather than beside it: it is a comfort setting, not a
   navigation control, and putting it in the same row would imply it does something to the map. */
.game-shell .map-lift{flex:0 0 auto;display:flex;align-items:center;gap:.5rem;font-size:.7rem;color:#8e9ab8}
.game-shell .map-lift input[type=range]{flex:1;min-width:0;accent-color:#dcb763;background:transparent}
.game-shell .map-lift .lift-value{min-width:2.6rem;text-align:right;font-variant-numeric:tabular-nums}
/* ⚠️ GOD MODE (issue #250) — admin-only, and deliberately styled as instrumentation rather than as part
   of the game. Green-on-dark and monospaced because it is a readout: it must be obvious in any
   screenshot that this is a debugging session and not what a player sees, or a screenshot of it gets
   reported as a leak. */
.game-shell .map-debug-toggle{flex:0 0 auto;display:flex;align-items:center;gap:.4rem;font-size:.7rem;
    color:#5ce1a6;cursor:pointer}
.game-shell .map-debug-toggle input{accent-color:#5ce1a6}
/* ⭐ THE RIGHT-CLICK MENU FAMILY, promoted because TWO components draw one: FloatHost's window menu and
   WorldClock's clock-format menu. Scoped to either, it would reach that one only — the exact fault that
   left the whole floating chrome unstyled for a phase. Anchored under .game-shell like everything else
   here, so it cannot leak onto the Forge or the lobby.

   The dismissal is an ELEMENT, not a document-level listener: a listener has to reason about whether the
   click that opened the menu is the one closing it, and a backdrop cannot get that wrong. It also
   swallows the click rather than letting it fall through to whatever is underneath. */
.game-shell .dm-menu-backdrop{position:fixed;inset:0;z-index:700;background:transparent}
/* ⚠️ The BOX is shared; the POSITIONING is not, and conflating them was a bug caught before it shipped.
   A window's menu opens at a pointer, so it takes viewport coordinates and must be `fixed`. The clock's
   opens from a fixed piece of chrome, so it is `absolute` against the clock (set in WorldClock's own
   sheet) — given `fixed` there, `top:100%` would resolve against the viewport and drop the menu off the
   bottom of the screen. Above the backdrop and above the panes' top stacking band either way. */
.game-shell .dm-menu,.game-shell .clock-menu{z-index:701;min-width:12rem;padding:.3rem;
    display:flex;flex-direction:column;gap:.1rem;
    border:1px solid #303b58;border-radius:10px;background:#0e1320f7;
    box-shadow:0 22px 70px #03060d90;backdrop-filter:blur(2px)}
.game-shell .dm-menu{position:fixed}
.game-shell .dm-menu-head{margin:0;padding:.25rem .5rem .35rem;border-bottom:1px solid #27334f;
    font-size:.66rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#8b96b6}
.game-shell .dm-menu button,.game-shell .clock-menu button{display:flex;align-items:center;gap:.4rem;
    width:100%;text-align:left;border:0;border-radius:6px;background:transparent;color:#bec9e7;
    padding:.35rem .5rem;font-size:.75rem;cursor:pointer}
.game-shell .dm-menu button:hover,.game-shell .clock-menu button:hover{background:#1d2540;color:#dfe6f6}
/* Fixed width so a label does not step sideways as its tick appears and disappears. */
.game-shell .dm-menu-tick{display:inline-block;width:.8rem;color:#e8c36a;font-weight:800}
.game-shell .dm-menu-close{margin-top:.15rem;border-top:1px solid #27334f;border-radius:0 0 6px 6px}
.game-shell .dm-menu-close:hover{background:#42232699;color:#ffb8be}
.game-shell .dm-menu-opacity{display:flex;align-items:center;gap:.4rem;padding:.35rem .5rem;
    font-size:.75rem;color:#bec9e7}
.game-shell .dm-menu-opacity input[type=range]{flex:1;min-width:0;accent-color:#dcb763;cursor:pointer}
.game-shell .dm-menu-opacity-value{min-width:2.5rem;text-align:right;font-variant-numeric:tabular-nums;color:#8e9ab8}

/* ⤳ The tab BUTTON, promoted out of TabHost.razor.css (#261 phase 6 fix). Two hosts draw it now — the
   tab strip and FloatHost's record-mode switcher — and a rule scoped to one host reaches only that host's
   markup, so the floating switcher rendered as bare browser buttons. The STRIP (.chat-tabs) stays with
   TabHost, which is still the only thing that draws one. Anchored under .game-shell like every other
   promoted rule here, so it cannot leak past the play surface. */
.game-shell .chat-tab{border:1px solid #303b57;border-radius:999px;background:#141a2a;color:#aeb8d4;padding:.45rem .85rem;font-size:.74rem;font-weight:700;cursor:pointer}
.game-shell .chat-tab.active{background:linear-gradient(135deg,#2d4b70,#233c5c);border-color:#3b6394;color:#eef2fb}
/* ⤳ Shared with the map pane and the narration surface (#261 phase 3, PartyPane). Promoted rather
   than scoped for the usual reason: more than one component renders markup wearing these. */
.game-shell .party-stack .march-hint,.game-shell .party-stack .march-feedback{margin:0}
.game-shell .member-tab .who{font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;color:#8e9ab8}
/* The chip family. Kept WHOLE rather than split: .condition moved into PartyPane and the other
   three did not, and restating a rule to separate them is how a declaration goes missing. All
   four are inside .game-shell either way. */
.game-shell .session-chip,.game-shell .round-badge,.game-shell .ai-note,.game-shell .condition{border:1px solid #303b58;background:#151b2b;border-radius:999px;padding:.42rem .7rem;font-size:.72rem;color:#bec9e7}

/* ⤳ The serif heading rule, kept WHOLE (#261 phase 4). Its .game-header h1 half is the shell's and
   its .panel-heading h2 half is TabHost's — restating it to separate them is how a declaration goes
   missing, and both are inside .game-shell either way. */
/* ⛔ THIS RULE WAS MISSING ITS OPENING BRACE, and it took the rule below it with it — found while
   building #347, by reading the browser's own parsed rules rather than the file. `h2margin:0;…}` is not
   a syntax error the browser reports: at the top level it consumes everything up to the next `{` as a
   selector and the following block as that selector's body, then throws the pair away. So the casualty
   was `body.dm-panes-gesturing{user-select:none}` — the text-selection suppression the whole pane
   gesture depends on — while `body.dm-panes-gesturing *{cursor:inherit}` one line further down survived
   and made the family look present. Exactly the swallowing described in StylesheetCommentGuardTests,
   from a different cause, so that class now guards this shape too. */
.game-shell .game-header h1,.game-shell .panel-heading h2{margin:0;font-family:Georgia,serif;letter-spacing:.01em}

/* ⛔ Suppresses text selection for the LIFETIME OF A PANE GESTURE (#261 phase 6). Not on .game-shell,
   because the class goes on <body>: a pointer that leaves the shell mid-drag must still not start
   selecting the page. A real mouse drag begins a selection which then fights the gesture for the pointer
   — autoscroll included — and it is invisible to a synthetic pointer, which is why the spike measured a
   clean resize while a hand reported a seized one. */
body.dm-panes-gesturing{user-select:none;-webkit-user-select:none}
body.dm-panes-gesturing *{cursor:inherit!important}

/* ⛔ THE DRAGGED-TAB LABEL (#347) LIVES HERE AND NOWHERE ELSE, and the reason is structural rather than
   stylistic: dm-panes.js CREATES this element, so it carries no `b-…` scope attribute and NO scoped rule
   can ever reach it — the same mechanism that left every window's chrome unstyled for a whole phase, and
   the reason an injected DOM probe comes back unstyled too. Anchored under .game-shell like every other
   rule in this sheet — the module appends the ghost INSIDE the pane host for exactly that reason, and an
   unanchored selector here reaches pages that are not the play surface (a guard says so, and caught this
   one). `position:fixed` still resolves against the viewport: nothing above it has a transform. */
.game-shell .dm-tab-ghost{position:fixed;top:0;left:0;z-index:900;pointer-events:none;
    border:1px solid #dcb763;border-radius:8px;background:#1b2438f2;color:#e8c36a;
    padding:.25rem .55rem;font-size:.66rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
    box-shadow:0 12px 40px #03060d90;will-change:transform}
