/* F14: the type scale. TEXT SIZE used to reach nine rules out of a hundred and ninety-five -
   166 fixed pixel sizes overrode it - so the setting moved almost nothing and the 8-to-11px
   text most of this game is set in had no working way to get bigger. Every font-size is now in
   rem against this root, so one number moves all of them. Non-font pixels are untouched: this
   scales type, not layout. */
html { font-size: calc(16px * var(--text-scale, 1)); }
/* The root is 16px so a rem is a round number, but body is monospace and a monospace default is
   13px - so everything that inherits rather than declaring a size was reading 13. Restated in
   rem, which keeps it 13 at scale 1 and lets the setting move it for the first time. */
body { font-size: 0.8125rem; }

body { background-color: #000; color: #c0c0c0; font-family: monospace; margin: 0; padding: 0; display: flex; justify-content: center; height: 100dvh; align-items: center; overflow: hidden; }
#engine { width: 100%; max-width: 900px; height: 100dvh; background: #000; position: relative; display: flex; flex-direction: column; overflow: hidden; }

#screen-title { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; background: linear-gradient(rgba(10, 10, 10, 0.25), rgba(0, 0, 0, 0.88)), repeating-linear-gradient(45deg, rgba(17, 17, 17, 0.35) 0, rgba(17, 17, 17, 0.35) 2px, transparent 2px, transparent 6px), url('bg_title.webp'); background-size: cover, auto, cover; background-position: center; text-align: center; padding: 20px; }
.title-header { font-size: clamp(1.875rem, calc(8vw * var(--text-scale, 1)), 3.75rem); color: #B8860B; font-weight: bold; text-transform: uppercase; letter-spacing: 4px; text-shadow: 3px 3px 0px #8B0000, 0 0 15px rgba(184, 134, 11, 0.5); margin-bottom: 10px; }
.title-sub { color: #666; font-size: clamp(0.75rem, calc(3vw * var(--text-scale, 1)), 1rem); letter-spacing: 8px; margin-bottom: 8vh; text-shadow: 1px 1px 2px #000; }
.title-menu { display: flex; flex-direction: column; gap: 20px; width: 100%; max-width: 300px; }
.title-btn { background: #111; color: #ccc; border: 2px solid #444; padding: 20px; font-family: monospace; font-size: clamp(0.875rem, calc(4vw * var(--text-scale, 1)), 1.125rem); font-weight: bold; cursor: pointer; text-transform: uppercase; transition: all 0.2s ease; box-shadow: 0 4px 6px rgba(0,0,0,0.5); }
.title-btn:hover { background: #8B0000; color: #fff; border-color: #ff0000; box-shadow: 0 0 15px rgba(255,0,0,0.4); transform: translateY(-2px); }
.title-btn:active { transform: translateY(2px); box-shadow: none; }
.btn-continue { border-color: #B8860B; color: #B8860B; }
.btn-meta { border-color: #4488ff; color: #4488ff; }
.btn-corrupt { border-color: #8B0000; color: #ff6666; border-style: dashed; }
.title-warning { text-align: center; font-size: 0.625rem; letter-spacing: 1px; color: #ff8844; border: 1px solid #8B0000; background: rgba(60,0,0,0.35); padding: 6px 8px; margin-bottom: 6px; line-height: 1.4; }

.settings-icon { position: absolute; top: 8px; right: 12px; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; background: none; border: none; padding: 0; font-size: 1.5rem; cursor: pointer; color: #888; transition: 0.2s; z-index: 100; text-shadow: 0 0 5px #000; }
.settings-icon:hover { color: #fff; transform: rotate(90deg); }
#screen-settings { display: none; flex-direction: column; justify-content: flex-start; align-items: center; padding: 20px; height: 100%; overflow-y: auto; box-sizing: border-box; background: rgba(0,0,0,0.95); z-index: 200; position: absolute; top:0; left:0; width:100%; }
.settings-menu { display: flex; flex-direction: column; gap: 12px; width: 100%; max-width: 300px; text-align: center; padding-bottom: 24px; }
/* The settings list outgrew a phone screen. It scrolls, and its own buttons are compact so the
   whole list is reachable without one - the title screen's buttons stay full size. */
.settings-menu .title-btn { padding: 13px 16px; }
.settings-title { color: #B8860B; font-size: 1.5rem; font-weight: bold; margin: 12px 0 16px; letter-spacing: 2px; flex-shrink: 0; }

/* The upgrade list scrolls inside its own container, with the header and the way out pinned.
   Scrolling the whole screen put RETURN TO TITLE below the fold once a fifth card existed. */
#screen-citadel { display: none; flex-direction: column; padding: 20px; height: 100%; box-sizing: border-box; background: linear-gradient(rgba(10, 10, 20, 0.9), rgba(0, 0, 0, 0.95)); overflow: hidden; }
#screen-citadel .outpost-roster { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-bottom: 4px; }
#screen-citadel .return-btn { flex-shrink: 0; }
.citadel-header { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: #4488ff; font-weight: bold; font-size: clamp(0.875rem, calc(4.4vw * var(--text-scale, 1)), 1.125rem); margin-bottom: 15px; flex-shrink: 0; padding-right: 56px; }
.skull-icon { color: #fff; text-shadow: 0 0 5px #ff4444; }

#screen-map { display: none; flex-direction: column; padding: 20px; height: 100%; box-sizing: border-box; position: relative;}
.map-header { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: #B8860B; font-weight: bold; font-size: clamp(0.875rem, calc(4.4vw * var(--text-scale, 1)), 1.125rem); margin-bottom: 15px; flex-shrink: 0; padding-right: 56px; }
.outpost-btn { background: #0a110a; border: 1px solid #6B8E23; color: #6B8E23; font-weight: bold; padding: 15px; margin-bottom: 15px; font-family: monospace; font-size: 0.875rem; cursor: pointer; text-align: center; border-radius: 4px; transition: 0.2s; flex-shrink: 0;}
.outpost-btn:hover { background: #6B8E23; color: #000; }

/* F13: this could not shrink and had no ceiling, so every relic a run picked up came off the
   route graph. Capped at a share of the screen with its own scroll, and allowed to shrink below
   that when the graph's floor needs the room - a header has to stay a header. */
.bounty-panel { background: #0a0a0a; border: 1px solid #4488ff; padding: 10px; margin-bottom: 15px; font-size: 0.6875rem; display: flex; flex-direction: column; gap: 5px; flex-shrink: 1; max-height: 38%; overflow-y: auto; }
/* A control has to be pressable: N13 set 44px as the floor for anything on this screen and the
   first draft of this line was 17px tall. The negative margin keeps the panel the height it was
   while the target reaches the floor - the row it replaced was a title, this one is a button. */
.relic-head { display: flex; justify-content: space-between; align-items: center; gap: 8px;
  cursor: pointer; user-select: none; min-height: 44px; padding: 0 2px; margin: -6px 0 -2px; }
.relic-head:hover, .relic-head:focus-visible { color: #7ab0f0; }
.relic-count { color: #ffaa00; font-weight: bold; letter-spacing: 1px; }
#relic-list:empty, #set-list:empty { display: none; }
.bounty-title { color: #4488ff; font-weight: bold; letter-spacing: 1px; border-bottom: 1px solid #222; padding-bottom: 3px; }
.bounty-item { display: flex; justify-content: space-between; color: #aaa; }
.bounty-complete { color: #6B8E23; text-decoration: line-through; }
/* F15: the front banner's idiom - a card that arrives, holds, and leaves - reused for the one
   moment the board never had a voice for. It rides above every screen rather than the map,
   because a CRAFT contract settles at the workbench and a SECTOR one at the crossing. */
.settle-banner { position: absolute; top: 12%; left: 50%; transform: translate(-50%, -50%);
  z-index: 130; pointer-events: none; opacity: 0; text-align: center; max-width: 88%;
  background: rgba(6, 12, 6, 0.95); border: 2px solid #6B8E23; padding: 12px 18px; }
.settle-banner-name { color: #9fd14a; font-size: 0.8125rem; font-weight: bold; letter-spacing: 2px; }
.settle-banner-desc { color: #b9c6a0; font-size: 0.6875rem; margin-top: 5px; letter-spacing: 1px; }
@keyframes settleBanner { 0% { opacity: 0; transform: translate(-50%, -70%); }
  10% { opacity: 1; transform: translate(-50%, -50%); } 78% { opacity: 1; } 100% { opacity: 0; } }
.settle-banner-show { animation: settleBanner 2.8s ease-out forwards; }
/* Shown rather than animated where motion is off, at a weight that beats the id above - the
   lesson F12 paid for. */
:root.motion-off #settle-banner.settle-banner-show { animation: none !important; opacity: 1; }

.relic-item { color: #ffaa00; font-weight: bold; display: flex; justify-content: space-between; margin-top: 2px;}
/* E15: what those relics add up to. Nine pairs upgrade each other and the engine read that
   state in seven places, but no screen ever said which pairs were up - or, the state worth
   playing toward, which one stood a single relic short. */
.set-list { display: flex; flex-direction: column; gap: 2px; margin-top: 7px; }
.set-list:empty { display: none; }
.set-head { color: #4488ff; font-weight: bold; letter-spacing: 1px; border-bottom: 1px solid #222; padding-bottom: 3px; margin-bottom: 2px; }
.set-item { display: flex; justify-content: space-between; gap: 8px; font-size: 0.625rem; letter-spacing: 0.5px; }
.set-tag { flex-shrink: 0; letter-spacing: 1px; font-size: 0.5625rem; }
.set-up { color: #c9a84a; font-weight: bold; font-size: 0.625rem; letter-spacing: 0.5px; line-height: 1.45; }
.set-near { color: #8a8578; }
.set-near .set-tag { color: #7ab0d0; }
.set-note { font-size: 0.5625rem; color: #6a655c; letter-spacing: 0; margin-top: 2px; }

/* H09: re-signing. Down and up are coloured apart because they are opposite bets - one banks
   what is left, the other spends it for a bigger promise. */
.camp-resign { display: flex; flex-direction: column; gap: 3px; text-align: left; }
.camp-resign-open { display: flex; flex-direction: column; gap: 3px; border-color: #7ab0d0; color: #7ab0d0; }
.resign-name { font-weight: bold; letter-spacing: 1px; }
.resign-terms { font-size: 0.625rem; color: #8a8578; letter-spacing: 0.5px; }
.resign-terms em { color: #c1440e; font-style: normal; }
.resign-down { border-color: #ffaa00; color: #ffaa00; }
.resign-up { border-color: #6B8E23; color: #6B8E23; }

/* H06: the sealed cache. The two doors are deliberately different colours - the clean one is
   the reason you brought that operator, the forced one is the price of not having. */
.cache-lock-line { color: #b8b3a8; font-size: 0.75rem; line-height: 1.6; margin-bottom: 4px; }
.cache-lock-line b { color: #e8e3d8; }
.cache-have { color: #6B8E23; font-weight: bold; }
.cache-lack { color: #c1440e; }
#cache-clean:not(:disabled) { border-color: #6B8E23; color: #6B8E23; }
#cache-clean:disabled { border-color: #444; color: #666; cursor: not-allowed; }
#cache-force { border-color: #ffaa00; color: #ffaa00; }
.map-node.cache-node { border-color: #7ab0d0; }

/* H04: the read on the eve of the commander. Three states because there are three different
   things to do about it - nothing, go and spend, or accept the shape you are taking up there -
   and the border carries the severity so the state is legible before the sentence is read. It
   sits in the map column's normal flow rather than over the graph: this is information the
   player is routing on, not an alert to dismiss. */
.march-read { border: 1px solid #4488ff; border-left-width: 4px; background: #0a0a0a; padding: 9px 11px; margin-bottom: 15px; font-size: 0.6875rem; line-height: 1.5; flex-shrink: 0; border-radius: 3px; }
.march-head { font-weight: bold; letter-spacing: 1px; font-size: 0.625rem; margin-bottom: 4px; }
.march-body { color: #b8b3a8; }
.march-body b { color: #e8e3d8; }
.march-ready { border-color: #6B8E23; }
.march-ready .march-head { color: #6B8E23; }
.march-patch { border-color: #ffaa00; }
.march-patch .march-head { color: #ffaa00; }
.march-grim { border-color: #8B0000; background: #120707; }
.march-grim .march-head { color: #c1440e; }
.march-grim .march-body b { color: #ff8a5c; }

/* F13: and the graph keeps a floor whatever the panel above it is doing. */
.map-viewport { flex-grow: 1; min-height: 45%; border: 1px solid #222; background: linear-gradient(rgba(15,15,15,0.9), rgba(5,5,5,0.9)), repeating-linear-gradient(0deg, transparent, transparent 19px, #1a1a1a 19px, #1a1a1a 20px), repeating-linear-gradient(90deg, transparent, transparent 19px, #1a1a1a 19px, #1a1a1a 20px); overflow-y: auto; display: flex; flex-direction: column; align-items: center; padding: 30px 0; position: relative; }
/* The route graph: nodes positioned absolutely on a tall canvas, edges drawn under them.
   Gold-dashed edges are where you can go, green where you have been, near-black what the
   routing has already cut off. */
/* flex-shrink 0 is load-bearing: the viewport is a column flexbox, and a flex item whose
   children are all absolutely positioned has a min-content height of zero - it collapses
   to the viewport and the SVG clips away every edge below the fold. */
.map-graph { position: relative; width: 100%; max-width: 340px; margin: 0 auto; flex-shrink: 0; }
.map-edges { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; }
.map-edges line { stroke-width: 3; }
.map-edges .edge-base { stroke: #4d4a40; }
.map-edges .edge-open { stroke: #B8860B; stroke-dasharray: 6 5; }
.map-edges .edge-traveled { stroke: #6B8E23; }
.map-edges .edge-dim { stroke: #26261f; }
.map-graph .map-node { position: absolute; transform: translateX(-50%); margin: 0; z-index: 2; }
.node-cutoff { opacity: 0.28; filter: grayscale(0.8); }
.node-weather { position: absolute; top: 2px; right: 2px; width: 11px; height: 11px;
  border-radius: 50%; border: 1px solid #000; z-index: 3; }
.wx-smog { background: #7fbf5f; box-shadow: 0 0 6px #7fbf5f; }
.wx-sand { background: #d8b25c; box-shadow: 0 0 6px #d8b25c; }
.wx-shrap { background: #9fb6c9; box-shadow: 0 0 6px #9fb6c9; }
.wx-ash { background: #b9b2a4; box-shadow: 0 0 6px #b9b2a4; }
.wx-ion { background: #6fd8e8; box-shadow: 0 0 6px #6fd8e8; }
.wx-haze { background: #c85a5a; box-shadow: 0 0 6px #c85a5a; }
.wx-blood { background: #ff4444; box-shadow: 0 0 6px #ff4444; }

.map-node { width: 75px; height: 75px; border-radius: 50%; background: #111; border: 2px solid #444; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s; font-family: monospace; color: #888; padding: 0; position: relative; }
.node-lbl { font-size: 0.5625rem; margin-top: 4px; letter-spacing: 1px; font-weight: bold; text-align: center; }
.node-icon { font-size: 1.375rem; }
.node-locked { opacity: 0.3; cursor: not-allowed; }
.node-cleared { border-color: #6B8E23; color: #555; background: #0a110a; }
.node-cleared .node-icon { filter: grayscale(100%); opacity: 0.5; }
.node-cleared::after { content: '✓'; position: absolute; color: #6B8E23; font-size: 1.875rem; font-weight: bold; opacity: 0.5; }

.elite-node { border-color: #8B0000; box-shadow: 0 0 10px #8B0000; }
.elite-node .node-lbl { color: #ff4444; }
.event-node { border-color: #4488ff; box-shadow: 0 0 10px rgba(68, 136, 255, 0.5); }
.event-node .node-lbl { color: #4488ff; }
.camp-node { border-color: #d2691e; box-shadow: 0 0 10px rgba(210, 105, 30, 0.5); }
.camp-node .node-lbl { color: #d2691e; }
.shop-node { border-color: #3aa17e; box-shadow: 0 0 10px rgba(58, 161, 126, 0.5); }
.shop-node .node-lbl { color: #3aa17e; }

/* F12: #screen-runover was missing from this list and had no rule of its own anywhere, so the
   screen a run actually ends on was an unstyled flex row with no padding and no ground behind
   it - while the screen a run ends WELL on had all of it. */
#screen-event, #screen-camp, #screen-victory, #screen-runover { display: none; flex-direction: column; justify-content: center; align-items: center; padding: 20px; height: 100%; box-sizing: border-box; background: linear-gradient(rgba(15,15,15,0.9), rgba(5,5,5,0.9)); }
/* F12: width 100% plus 25px of padding and a 2px border on a content box is 54px wider than
   whatever holds it, at every width - so this overflowed on every phone the game runs on. */
.event-box { box-sizing: border-box; background: #050505; border: 2px solid #B8860B; padding: 25px; width: 100%; max-width: 500px; display: flex; flex-direction: column; gap: 15px; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.8); }
.event-title { color: #B8860B; font-size: 1.375rem; font-weight: bold; text-transform: uppercase; letter-spacing: 2px; border-bottom: 1px solid #333; padding-bottom: 10px; }
.event-desc { color: #ccc; font-size: 0.875rem; line-height: 1.6; margin-bottom: 15px; }
/* Who this is and what they make of you, above their own words. The standing carries a word as
   well as a colour, for the same reason the status marks do. */
/* The standing contract is one of the board's rows but not one of its three: a rule above it
   and a warmer colour say it is a different kind of promise. */
/* A spot whose prerequisite is not standing yet: still visible on the hill, clearly not yours
   to buy. Dimmed rather than hidden, so the tree is legible before it is affordable. */
.upgrade-card.upg-sealed { opacity: 0.55; border-color: #3a3a3a; }
.upgrade-pitch { color: #8a8a8a; font-size: 0.6875rem; line-height: 1.4; margin-top: 4px; }
.bounty-standing { border-top: 1px solid #3a3a2a; margin-top: 6px; padding-top: 8px; color: #d2a24c; }
.event-cast { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0 12px; }
.event-cast .cast-name { color: #B8860B; font-size: 0.6875rem; letter-spacing: 1.5px; }
.event-cast .cast-band { font-size: 0.625rem; letter-spacing: 1px; padding: 3px 8px; border: 1px solid; }
.cast-band.cast-good { color: #6B8E23; border-color: #4a6b19; background: rgba(40,60,10,0.35); }
.cast-band.cast-bad  { color: #ff6b6b; border-color: #8B0000; background: rgba(70,10,10,0.35); }
.cast-band.cast-idle { color: #9a9a9a; border-color: #4a4a4a; background: rgba(255,255,255,0.03); }
.cast-band.cast-new  { color: #7fa7d4; border-color: #35506b; background: rgba(15,30,50,0.4); }
.event-choices { display: flex; flex-direction: column; gap: 10px; }
.event-btn { background: #111; color: #fff; border: 1px solid #444; padding: 12px; font-family: monospace; font-size: 0.8125rem; cursor: pointer; transition: 0.2s; text-transform: uppercase; }
.event-btn:hover:not(:disabled) { background: #B8860B; color: #000; border-color: #B8860B; }
.event-btn:disabled { opacity: 0.3; cursor: not-allowed; }

@keyframes pulseActive { 0% { box-shadow: 0 0 5px #8B0000; border-color: #8B0000; } 50% { box-shadow: 0 0 20px #ff4444; border-color: #ff4444; } 100% { box-shadow: 0 0 5px #8B0000; border-color: #8B0000; } }
.node-active { color: #fff; background: #1a0a0a; animation: pulseActive 2s infinite; }
.node-active:hover { transform: scale(1.1); }
.boss-node.node-active { animation: pulseActive 1s infinite; background: #2a0505; }
.event-node.node-active { animation: none; box-shadow: 0 0 15px #4488ff; border-color: #4488ff; background: #0a0a1a; }
.camp-node.node-active { animation: none; box-shadow: 0 0 15px #d2691e; border-color: #d2691e; background: #1a0f05; }
.shop-node.node-active { animation: none; box-shadow: 0 0 15px #3aa17e; border-color: #3aa17e; background: #05140f; }

#screen-outpost { display: none; flex-direction: column; gap: 15px; padding: 20px; height: 100%; box-sizing: border-box; overflow-y: auto;}

.op-tabs { display: flex; gap: 10px; margin-bottom: 5px; flex-wrap: wrap; }
.op-tab-btn { flex: 1; min-width: 30%; padding: 10px; background: #111; border: 1px solid #444; color: #888; cursor: pointer; font-weight: bold; font-family: monospace; font-size: 0.75rem; }
.op-tab-active { background: #B8860B; color: #000; border-color: #B8860B; }

.outpost-roster { display: grid; grid-template-columns: 1fr; gap: 15px; }
.upgrade-card { background: #0a0a0a; border: 1px solid #333; padding: 15px; display: flex; flex-direction: column; gap: 10px; }
.upgrade-header { display: flex; justify-content: space-between; align-items: center; color: #B8860B; font-weight: bold; border-bottom: 1px solid #222; padding-bottom: 5px;}
.upgrade-stats { display: flex; justify-content: space-between; font-size: 0.6875rem; color: #888; }
/* K04: what an operator shrugs off, on the screen where the trinket that buys one is equipped.
   Same green the traits line uses - it is a standing property of the body, not a warning. */
.upgrade-res { font-size: 0.625rem; color: #6B8E23; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 2px; }
.upgrade-btn-group { display: flex; gap: 10px; margin-top: 5px; flex-wrap: wrap;}
.upg-btn { flex: 1; min-width: 30%; padding: 10px; background: #111; color: #ccc; border: 1px solid #444; cursor: pointer; font-family: monospace; font-size: 0.75rem; transition: 0.2s;}
.upg-btn:hover:not(:disabled) { background: #333; border-color: #888; color: #fff;}
.upg-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* How many slots are left, next to what is already bolted on. The list used to only ever
   grow and could say neither. */
.aug-slots { color: #4488ff; font-size: 0.625rem; font-variant-numeric: tabular-nums; }
.aug-full { color: #6f7c8a; }
.sub-menu-btn { min-width: 45%; } 

.pos-btn-1 { border-color: #8B0000; color: #ff5555; background: #2a0a0a; } 
.pos-btn-2 { border-color: #B8860B; color: #ffaa00; background: #2a2a0a; } 
.pos-btn-3 { border-color: #6B8E23; color: #aaffaa; background: #0a2a0a; } 
.pos-btn-0 { border-color: #444; color: #888; background: #111; } 
.med-btn { border-color: #6B8E23; color: #6B8E23; }
.med-btn:hover:not(:disabled) { background: #6B8E23; color: #000; }
.revive-btn { border-color: #8B0000; color: #ff4444; }
.revive-btn:hover:not(:disabled) { background: #8B0000; color: #fff; }
.perk-btn { background: #1a1a2a; border-color: #4488ff; color: #4488ff; font-weight: bold; }
.perk-btn:hover:not(:disabled) { background: #2a2a4a; color: #fff; }
.cost-txt { color: #B8860B; }
.return-btn { background: #111; color: #ccc; border: 1px solid #444; padding: 15px; font-family: monospace; font-size: 0.875rem; cursor: pointer; text-transform: uppercase; margin-top: auto; transition: 0.2s; flex-shrink:0;}
.return-btn:hover { background: #222; border-color: #888; }

.inventory-sect { background: #111; border: 1px solid #444; padding: 10px; margin-top: 10px; }
.inventory-title { color: #B8860B; font-weight: bold; font-size: 0.8125rem; margin-bottom: 5px; display:flex; justify-content: space-between;}
.inventory-grid { display: flex; gap: 5px; flex-wrap: wrap; }
.inv-slot { background: #1a1a1a; border: 1px solid #333; padding: 6px; font-size: 0.6875rem; color: #aaa; cursor: pointer; flex: 1; min-width: 45%; text-align: center; }
.inv-slot:hover:not(:disabled) { background: #333; color: #fff; border-color: #888; }

#screen-combat { display: none; flex-direction: column; height: 100%; }
.sky-layer { background-size: cover; background-position: center bottom; flex-grow: 1; display: flex; flex-direction: column; position: relative; }
#queue-display { text-align: center; color: #ccc; padding: 8px; font-size: clamp(0.625rem, calc(3vw * var(--text-scale, 1)), 0.75rem); letter-spacing: 2px; font-weight: bold; background: rgba(0,0,0,0.7); text-shadow: 1px 1px 2px #000; border-bottom: 1px solid #222; }

.runover-score { font-family: monospace; font-size: clamp(2.375rem, calc(12vw * var(--text-scale, 1)), 3.75rem); font-weight: bold; color: #B8860B; text-align: center; letter-spacing: 2px; text-shadow: 2px 2px 0 #8B0000; margin: 6px 0 2px; }
.runover-best { text-align: center; font-size: 0.6875rem; letter-spacing: 2px; color: #6B8E23; text-transform: uppercase; margin-bottom: 14px; min-height: 14px; }
.runover-lines { display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: #aaa; border-top: 1px solid #222; border-bottom: 1px solid #222; padding: 10px 0; margin-bottom: 14px; }
.runover-line { display: flex; justify-content: space-between; gap: 10px; }
.runover-line span:last-child { color: #ccc; font-weight: bold; }
.title-wins { text-align: center; font-size: 0.6875rem; letter-spacing: 2px; color: #c9a84a; margin-bottom: 8px; }
/* The last sector counts the same as every other one; it just says so in gold. */
.sector-last { color: #c9a84a; }
/* The order picker sits above the contracts because it is the bigger decision: a contract
   changes how hard the road is, an order changes how much of it there is. */
.order-note { font-size: 0.6875rem; color: #8a8272; text-align: center; padding: 0 14px; line-height: 1.5; }
.order-list { display: flex; flex-direction: column; gap: 8px; padding: 0 14px; }
.order-card { display: flex; flex-direction: column; gap: 4px; text-align: left; background: #0a0a0a; border: 1px solid #333; color: #999; padding: 10px 12px; font-family: monospace; cursor: pointer; }
.order-card.order-on { border-color: #B8860B; background: #171308; color: #ddd; }
/* The longest order has the longest name and the longest reach line; on a narrow card they do
   not both fit, so the reach drops under the name rather than wrapping through it. */
.order-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.order-name { font-weight: bold; letter-spacing: 1px; font-size: 0.8125rem; white-space: nowrap; }
.order-card.order-on .order-name { color: #B8860B; }
.order-reach { font-size: 0.625rem; letter-spacing: 1px; color: #8a8272; white-space: nowrap; }
.order-desc { font-size: 0.6875rem; line-height: 1.45; }
/* The recall, rendered into the sector-secured panel rather than on a screen of its own. */
.recall-head { color: #6B8E23; text-align: center; letter-spacing: 2px; margin: 4px 0 8px; }
.recall-note { font-size: 0.6875rem; color: #aaa; line-height: 1.55; margin-bottom: 12px; }
.recall-lines { display: flex; flex-direction: column; gap: 4px; font-size: 0.75rem; color: #aaa; border-top: 1px solid #222; border-bottom: 1px solid #222; padding: 10px 0; margin-bottom: 14px; }
.return-btn.recall-home { border-color: #6B8E23; color: #6B8E23; margin-bottom: 10px; }
.runover-score.score-won { color: #c9a84a; text-shadow: 2px 2px 0 #4a3a10; }
#victory-title { font-size: clamp(1.5rem, calc(8vw * var(--text-scale, 1)), 2.125rem); text-shadow: 2px 2px 0 #4a3a10; }
.victory-sub { text-align: center; font-size: 0.6875rem; letter-spacing: 2px; color: #8a7a3a; text-transform: uppercase; margin-bottom: 10px; }
#weather-banner { text-align: center; font-size: 0.6875rem; font-weight: bold; padding: 4px; letter-spacing: 1px; display: none; text-shadow: 1px 1px 2px #000; box-shadow: 0 2px 4px rgba(0,0,0,0.5); }
/* The ground reads quieter than the sky: weather is an event happening to the fight, ground is
   the fight's own floor. Each terrain keeps one colour across the map dot and this line. */
#ground-banner { text-align: center; font-size: 0.625rem; font-weight: bold; padding: 3px; letter-spacing: 1px; display: none; text-shadow: 1px 1px 2px #000; border-top: 1px solid rgba(0,0,0,0.5); }
.tr-tunnel { background: #2a1f14; color: #c9a227; }
.tr-flats  { background: #1d2430; color: #8fb6d6; }
.tr-ruins  { background: #23241f; color: #a8b09a; }
/* On the map it is a single lettered chip beside the weather dot, so the two never merge into
   one indistinguishable mark. */
.node-ground { position: absolute; right: 3px; bottom: 3px; width: 13px; height: 13px; line-height: 13px;
  font-size: 0.5625rem; font-weight: bold; text-align: center; border: 1px solid rgba(0,0,0,0.6); }
.weather-smog { background: rgba(40, 60, 10, 0.8); color: #aaffaa; display: block !important; border-bottom: 1px solid #6B8E23; }
.weather-sand { background: rgba(80, 50, 10, 0.8); color: #ffcc66; display: block !important; border-bottom: 1px solid #B8860B; }
.weather-shrap { background: rgba(40, 40, 40, 0.8); color: #cccccc; display: block !important; border-bottom: 1px solid #888; }
.weather-blood { background: rgba(80, 10, 10, 0.8); color: #ff4444; display: block !important; border-bottom: 1px solid #ff0000; }
.weather-ash { background: rgba(48, 46, 42, 0.85); color: #ded7c8; display: block !important; border-bottom: 1px solid #b9b2a4; }
.weather-ion { background: rgba(10, 44, 52, 0.85); color: #9ff0fb; display: block !important; border-bottom: 1px solid #6fd8e8; }
.weather-haze { background: rgba(62, 16, 16, 0.85); color: #ff9d9d; display: block !important; border-bottom: 1px solid #c85a5a; }
/* A faction's own sky over its own ground. It gets its own line under the two banners rather
   than rewording either: both are still true, and the confluence is the third fact. */
.confluence-banner { background: rgba(30, 24, 40, 0.9); color: #d9b8ff; border-bottom: 1px solid #7d5fa8; font-size: 0.625rem; letter-spacing: 1px; padding: 3px 8px; text-align: center; }

.intent-icon { position: absolute; top: -25px; font-size: 0.875rem; background: #111; border: 1px solid #444; border-radius: 50%; width: 22px; height: 22px; display: flex; justify-content: center; align-items: center; z-index: 20; box-shadow: 0 2px 4px #000; }

.battlefield { position: relative; display: flex; justify-content: center; gap: clamp(18px, 9vw, 70px); flex-grow: 1; align-items: flex-end; padding: 0 11px; box-sizing: border-box; margin-bottom: 12vh; /* overridden per backdrop by applyCombatScenery; relative so tracer lines can anchor to it */ }
.team { display: flex; align-items: flex-end; justify-content: center; min-width: 0; }
/* Four-strong squads tighten up so oversized sprites stay inside the viewport. */
.team.crowded .entity { width: calc(clamp(42px, 11vw, 78px) * var(--field-fit, 1)); }
/* Sprites overlap on purpose, but the readouts ride on top of them - so a six-strong swarm
   painted every card over its neighbour. A crowded line stands its units apart instead.
   This used to be the hostile row alone, on the grounds that a squad never exceeds four and
   read fine as it was. It stopped reading fine the moment the field started fitting itself:
   at four operators on a 320px screen the slots are narrow enough that the health readouts
   printed straight over each other. Both rows now stand apart when they are crowded, and the
   fit absorbs the extra width. */
.team.crowded .entity { --overlap: 0px; margin: 0; }
.team.crowded .portrait { width: calc(100% * min(var(--sprite-scale, 1), 1.45) * var(--row-fit, 1)); }
/* A commander and its retinue share the line. They stand beside each other rather than
   stacked, the retinue reads in front, and fitEnemyRow narrows the whole row to the width it
   actually has - so the unit the fight wants shot first is never buried behind the one it
   does not. */
.team.retinue .entity { margin: 0; }
.team.retinue .entity:nth-child(1) { z-index: 1; }
.team.retinue .entity:nth-child(2) { z-index: 3; }
#player-team { flex-direction: row-reverse; } 
#enemy-team { flex-direction: row; }
.entity { width: calc(clamp(55px, 16vw, 110px) * var(--field-fit, 1)); display: flex; flex-direction: column; align-items: center; justify-content: flex-end; position: relative; transition: transform 0.2s; gap: 2px; --overlap: 1.5vw; margin: 0 calc(-1 * var(--overlap)); z-index: 1; }
.entity:nth-child(1) { z-index: 3; } .entity:nth-child(2) { z-index: 2; } .entity:nth-child(3) { z-index: 1; } .entity:nth-child(4) { z-index: 4; }
.entity.active .portrait { filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.9)); }
.entity.dead { opacity: 0.55; filter: grayscale(90%); }
/* The dead fall once and stay fallen: the first post-death render plays the fall, every
   render after holds the settled pose - renderField rebuilds constantly, so the split
   between .dying and .settled is what keeps corpses from re-dying on every repaint. */
.entity.dead .portrait { transform-origin: 50% 88%; }
.entity.dead.dying .portrait { animation: deathFall 0.55s ease-in forwards; }
.entity.dead.settled .portrait { transform: rotate(78deg) translateY(12%) translateX(5%); }
@keyframes deathFall {
  0% { transform: rotate(0) translateY(0); }
  62% { transform: rotate(85deg) translateY(13%) translateX(5%); }
  78% { transform: rotate(72deg) translateY(11%) translateX(5%); }
  100% { transform: rotate(78deg) translateY(12%) translateX(5%); }
}
.entity.targetable-enemy .portrait { filter: drop-shadow(0 0 12px #ff0000); cursor: pointer; }
.entity.targetable-ally .portrait { filter: drop-shadow(0 0 12px #6B8E23); cursor: pointer; }
.entity.targetable-enemy:active .portrait { filter: drop-shadow(0 0 20px #ff0000) brightness(1.5); }
.hp-text { font-size: clamp(0.5625rem, calc(2.5vw * var(--text-scale, 1)), 0.75rem); font-weight: bold; margin-bottom: 2px; color: #fff; text-shadow: 1px 1px 3px #000; text-align: center; white-space: nowrap; display: flex; align-items: center; justify-content: center; gap: 3px; }
/* What a slot too narrow for its own text stops paying for - see fitSlotText, which measures
   the ink rather than the box. The max is the bar underneath drawn again in digits; the
   letter-spacing on the chips and tags is tracking and nothing else. */
.team.slot-tight .hp-max { display: none; }
.team.slot-tight .rank-chip, .team.slot-tight .threat-tag, .team.slot-tight .sig-tag,
.team.slot-tight .soak-tag, .team.slot-tight .down-tag { letter-spacing: 0; }
.hp-container { background: #111; height: 6px; width: 85%; border: 1px solid #000; box-shadow: 0 0 4px #000; margin: 0 auto; }
.hp-fill { height: 100%; transition: width 0.3s ease; }
.player-hp { background-color: #8B0000; } .enemy-hp { background-color: #8B0000; }
.res-row { display: flex; justify-content: center; gap: 3px; margin-top: 2px; line-height: 1; }
.res { font-size: 0.625rem; font-weight: bold; line-height: 1; padding: 1px 2px; border-radius: 2px; text-shadow: 0 1px 2px #000; background: rgba(0,0,0,0.55); }
.res-weak { color: #ff9c3a; }
.res-strong { color: #7f8ea3; }
.res-immune { color: #55606e; text-decoration: line-through; }
/* Six statuses can stack on one unit, which will not fit on the HP line beside the numbers.
   They get their own row and wrap onto a second line rather than being squeezed and clipped. */
.status-badge { font-size: 0.625rem; line-height: 12px; display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center; gap: 1px; margin-bottom: 1px; text-shadow: 1px 1px 2px #000;
  max-width: calc(100% - 2 * var(--overlap)); margin-left: auto; margin-right: auto; }
/* Four cues per status, in this order of reliability: the letter, the border shape, the turn
   count, and only then the colour. A player who cannot separate the reds from the greens still
   reads B3 in a square from O2 in a dashed box. The legend is in the field manual. */
.st { display: inline-flex; align-items: baseline; gap: 1px; padding: 0 2px; min-width: 15px;
  justify-content: center; font-size: 0.5625rem; line-height: 13px; letter-spacing: 0;
  border: 1px solid currentColor; background: rgba(8,6,4,0.72); text-shadow: 0 0 2px #000; }
.st b { font-weight: 700; }
.st i { font-style: normal; opacity: 0.85; font-size: 0.5rem; }
.st-bleed   { color: #ff6b6b; border-radius: 0; }                          /* square */
.st-stun    { color: #ffd24a; border-radius: 0; border-style: double; border-width: 3px; }
.st-armor   { color: #9acd32; border-radius: 999px; padding: 0 4px; }      /* pill */
.st-oil     { color: #c9a227; border-style: dashed; border-radius: 0; }
.st-corrode { color: #7fd8a0; border-style: dotted; border-radius: 0; }
.st-mark    { color: #ff9d5c; border-radius: 0;
              clip-path: polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 0 100%); }
.portrait { width: calc(100% * var(--sprite-scale, 1) * var(--row-fit, 1)); height: auto; max-height: 25vh; object-fit: contain; object-position: bottom; margin-bottom: calc(-1 * var(--sprite-sink, 0px)); transition: all 0.2s; filter: drop-shadow(2px 4px 4px rgba(0,0,0,0.8)); }

@keyframes hoverSprite { 0% { transform: translateY(0); } 50% { transform: translateY(-15px); } 100% { transform: translateY(0); } }
.hovering { animation: hoverSprite 3s ease-in-out infinite; margin-bottom: 40px; }

@keyframes floatUp { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-50px) scale(1.3); } }
/* E02: the layer renderField never rebuilds. Entities live in two containers that are replaced
   wholesale on every render; this sits beside them, so floating text survives long enough to be
   painted. Above the sprites, and never in the way of a tap. */
.fx-layer { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 30; }
.fct { position: absolute; top: 20%; left: 50%; transform: translateX(-50%); font-weight: bold; font-size: clamp(0.875rem, calc(4vw * var(--text-scale, 1)), 1.25rem); text-shadow: 2px 2px 4px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; animation: floatUp 1s ease-out forwards; pointer-events: none; z-index: 20; white-space: nowrap; }
.fct-dmg { color: #ff4444; } .fct-heal { color: #6B8E23; } .fct-status { color: #B8860B; } .fct-weak { color: #ffaa00; font-size: clamp(1.125rem, calc(5vw * var(--text-scale, 1)), 1.5rem); } .fct-combo { color: #ff44ff; font-size: clamp(1.25rem, calc(6vw * var(--text-scale, 1)), 1.75rem); }

#momentum-container { background: #111; height: 12px; width: 100%; border-top: 2px solid #333; position: relative; border-bottom: 2px solid #000; }
#momentum-fill { background: linear-gradient(90deg, #4488ff, #ff44ff); height: 100%; width: 0%; transition: width 0.3s ease-in-out; }
#momentum-txt { position: absolute; top: 0px; left: 50%; transform: translateX(-50%); font-size: 0.5625rem; font-weight: bold; color: #fff; text-shadow: 1px 1px 1px #000; letter-spacing: 1px; }

.btn-overdrive { background: #2a002a; border-color: #ff44ff; color: #ff44ff; font-weight: bold; text-shadow: 0 0 5px #ff44ff; animation: pulseOD 1.5s infinite; }
@keyframes pulseOD { 0% { box-shadow: 0 0 5px #ff44ff; } 50% { box-shadow: 0 0 15px #ff44ff; } 100% { box-shadow: 0 0 5px #ff44ff; } }

@keyframes fxShake { 0% { transform: translate(2px, 2px) rotate(0deg); } 20% { transform: translate(-3px, 0px) rotate(1deg); } 40% { transform: translate(3px, -2px) rotate(-1deg); } 60% { transform: translate(-2px, 2px) rotate(0deg); } 80% { transform: translate(2px, -1px) rotate(1deg); } 100% { transform: translate(0px, 0px) rotate(0deg); } }
.fx-shake { animation: fxShake 0.4s cubic-bezier(.36,.07,.19,.97) both; }
@keyframes fxGlitch { 0% { filter: hue-rotate(0deg) invert(0%); transform: translate(0,0); } 10% { filter: hue-rotate(90deg) invert(30%); transform: translate(-5px, 5px); } 20% { filter: hue-rotate(-90deg) invert(0%); transform: translate(5px, -5px); } 30% { filter: hue-rotate(180deg) invert(30%); transform: translate(-5px, -5px); } 40% { filter: hue-rotate(0deg) invert(0%); transform: translate(0,0); } }
.fx-glitch { animation: fxGlitch 0.4s linear both; }
@keyframes fxFlash { 0% { filter: brightness(1) sepia(0); } 50% { filter: brightness(2.5) sepia(1) hue-rotate(-50deg) saturate(5); } 100% { filter: brightness(1) sepia(0); } }
.fx-flash { animation: fxFlash 0.3s ease-out both !important; }

.dashboard { height: 35vh; max-height: 250px; background: #050505; border: 2px solid #333; display: grid; grid-template-columns: 1fr 1fr; box-sizing: border-box; margin: 5px; }
#command-deck { border-right: 2px solid #333; padding: 10px; display: flex; flex-direction: column; justify-content: flex-start; gap: 8px; overflow-y: auto; min-width: 0; }
/* F14: measured against the dashboard's own #050505, the two commonest classes in the log
   failed 4.5:1 outright - the base text at 2.66 and, worse, every damage line in the game at
   1.98. The other three already cleared it and are untouched, so the log keeps its palette and
   only the two unreadable ones move: a lighter grey, and a red that still reads as blood. */
#log { padding: 10px; overflow-y: auto; font-size: clamp(0.625rem, calc(3vw * var(--text-scale, 1)), 0.75rem); color: #8a8a8a; display: flex; flex-direction: column; }
.log-turn { color: #aaa; margin-top: 5px; } .log-heal { color: #6B8E23; } .log-dmg { color: #e05a4a; } .log-status { color: #B8860B; } .log-combo { color: #ff44ff; font-weight: bold; }
button { background: #111; color: #ccc; border: 1px solid #444; padding: clamp(10px, 3vh, 15px); font-family: monospace; font-size: clamp(0.625rem, calc(3vw * var(--text-scale, 1)), 0.8125rem); cursor: pointer; text-transform: uppercase; transition: 0.2s; flex-shrink:0;}
button:hover:not(:disabled) { background: #222; border-color: #888; }
button:active { background: #000; }
button:disabled { opacity: 0.2; cursor: not-allowed; border-color: #222; }
.dash-msg { display: flex; align-items: center; justify-content: center; height: 100%; font-size: 0.875rem; color: #666; text-align: center; }

/* Keyboard users get no indication of position without this - there was none at all. */
:focus-visible { outline: 2px solid #B8860B; outline-offset: 2px; }
/* Leaving is a real option, not a failure state, so the button reads as one of the deck's
   choices rather than an alarm - until it is armed, when it says exactly what it will cost. */
.btn-withdraw { border-color: #6b6f5a; color: #a7ad8e; }
.btn-withdraw-back { border-color: #4a4a4a; color: #8a8a8a; }
/* Retreat is the paid way out, so it reads in the scrap colour rather than the leaving one. */
.btn-retreat { border-color: #7a6a3a; color: #d2a24c; }
.btn-retreat:disabled { border-color: #33302a; color: #6b6355; }
.withdraw-cost { font-size: 0.6875rem; line-height: 1.5; color: #d9b26a;
  border: 1px dashed #6b5a2a; background: rgba(30,22,6,0.6); padding: 8px 10px; margin-bottom: 6px; }

/* The touch floor. Measured rather than assumed: these six were the controls under 44px on a
   390px-wide phone - the ledger toggle at 29, the outpost tabs at 36, the upgrade and gear
   buttons at 33-36, the bag slots at 27, and the field prompt's two buttons at 34. Everything
   else already cleared it, and suite 46 keeps it that way across seven screens. */
.op-tab-btn, .upg-btn, .inv-slot,
.prompt-ok, .prompt-off { min-height: 44px; }
/* A tightened hostile line squeezed its slots to 43px wide, one short of the same floor.
   G08: every slot, not only a crowded one - a team of three is not crowded and had no floor at
   all. The size comes from fitField, which offers the full 44px to its search and drops the
   property entirely on a field that cannot take it; the fallback below is that case, and is
   what this rule has always been. */
.battlefield .team .entity { min-width: var(--touch-floor, calc(44px * var(--field-fit, 1))); }
.battlefield .team.crowded .entity { min-width: var(--touch-floor, calc(44px * var(--field-fit, 1))); }

.entity:focus-visible { outline-offset: -2px; }

/* Screen shake and the glitch pass are decorative and can be genuinely unpleasant. Damage
   numbers and the active-unit pulse carry information, so they stay. */
/* The animation setting is a class on the root, so effects can be turned off on a machine that
   never asked for that and back on for a player who wants them despite the system preference.
   Everything the media query below silences is silenced here too. */
:root.motion-off .fx-shake, :root.motion-off .fx-glitch { animation: none !important; }
:root.motion-off .hovering { animation: none; }
:root.motion-off .settings-icon:hover { transform: none; }
:root.motion-off .title-btn:hover, :root.motion-off .title-btn:active { transform: none; }
:root.motion-off .combo-flag { animation: none; }
/* And the game's own motion setting, which had no rule for the splash at all: the blanket
   0.01ms below runs the animation to its last keyframe, which is opacity 0. */
:root.motion-off #front-banner.front-banner-show { animation: none !important; opacity: 1; }
:root.motion-off * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }

@media (prefers-reduced-motion: reduce) {
    .fx-shake, .fx-glitch { animation: none !important; }
    .hovering { animation: none; }
    .settings-icon:hover { transform: none; }
    .title-btn:hover, .title-btn:active { transform: none; }
    * { transition-duration: 0.01ms !important; }
}

.outpost-notice { border: 1px solid #8B0000; background: rgba(60,0,0,0.35); color: #ff9c6b; font-size: 0.6875rem; line-height: 1.5; padding: 8px 10px; margin-bottom: 10px; letter-spacing: 0.5px; }
.btn-armed { background: #8B0000 !important; color: #fff !important; border-color: #ff4444 !important; }

/* Three values on a 400px header used to run together - 'SECTOR 1' beside '1,111 PTS' read as
   'SECTOR 11,111'. Each gets its own labelled cell, a divider, and tabular figures so digits
   line up and the columns stop moving as the numbers grow. */
.stat-bar { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: #262119; border: 1px solid #332c22; margin: 0 56px 14px 0; flex-shrink: 0; }
.stat { background: #0c0b09; padding: 5px 9px 6px; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.stat-label { font-size: 0.5rem; letter-spacing: 1.6px; text-transform: uppercase; color: #6b6459; line-height: 1; }
.stat-value { font-size: clamp(0.875rem, calc(4.6vw * var(--text-scale, 1)), 1.1875rem); font-weight: bold; line-height: 1.15; color: #B8860B; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stat-score .stat-value { color: #8fa0b5; }
.stat-scrap .stat-value { color: #c9a227; }

/* Screen name sits on its own line so the numbers below it get the full width, and so it
   never has to share a row with the settings gear. */
.screen-title-bar { color: #B8860B; font-weight: bold; font-size: clamp(0.9375rem, calc(4.6vw * var(--text-scale, 1)), 1.1875rem); letter-spacing: 1px; margin: 0 56px 8px 0; flex-shrink: 0; }
.stat-bar-4 { grid-template-columns: repeat(4, 1fr); margin-right: 0; }
.stat-bar-4 .stat { padding: 5px 6px 6px; }
.stat-bar-4 .stat-value { font-size: clamp(0.8125rem, calc(4vw * var(--text-scale, 1)), 1.0625rem); }
.stat-parts .stat-value { color: #a9a196; }
.stat-chems .stat-value { color: #7fae5e; }
.stat-tech .stat-value { color: #6aa6d8; }

#screen-dev { display: none; flex-direction: column; padding: 20px; height: 100%; box-sizing: border-box; overflow-y: auto; background: linear-gradient(rgba(8,10,14,0.96), rgba(0,0,0,0.98)); }
.dev-body { display: flex; flex-direction: column; gap: 12px; }
.dev-group { border: 1px solid #26303c; background: #0b0e12; padding: 9px 10px 11px; }
.dev-title { font-size: 0.625rem; letter-spacing: 1.4px; text-transform: uppercase; color: #6aa6d8; margin-bottom: 7px; }
.dev-row { display: flex; flex-wrap: wrap; gap: 6px; }
.dev-btn { flex: 1 1 auto; min-width: 78px; font-size: 0.6875rem; padding: 9px 6px; }

/* A pairing the player can see before spending the turn, rather than discovering afterwards. */
.combo-flag { position: absolute; top: -46px; left: 50%; transform: translateX(-50%); z-index: 25;
  font-size: 0.5625rem; font-weight: bold; letter-spacing: 1px; white-space: nowrap; padding: 2px 5px;
  color: #ff44ff; background: rgba(30,0,30,0.9); border: 1px solid #ff44ff; border-radius: 2px;
  text-shadow: 0 0 4px #ff44ff; animation: pulseOD 1.2s infinite; }
.entity.has-combo .portrait { filter: drop-shadow(0 0 14px #ff44ff); }
@media (prefers-reduced-motion: reduce) { .combo-flag { animation: none; } }

/* An ability whose pairing is already live on the field, so the player can find a combo
   without selecting each one in turn to see what lights up. */
button.combo-ready { border-color: #ff44ff; color: #ff9cff; box-shadow: 0 0 10px rgba(255,68,255,0.35) inset; }
.combo-tag { font-size: 0.5625rem; letter-spacing: 1px; color: #ff44ff; text-shadow: 0 0 4px #ff44ff; }

/* Formation feedback. Rank is stated on each deployed unit; a swing that will land short and a
   unit currently covering the line behind it are both called out before the turn is spent. */
.rank-chip { font-size: 0.5rem; letter-spacing: 1px; font-weight: bold; line-height: 11px;
  text-align: center; color: #0b0b0b; border-radius: 2px; margin-bottom: 2px; opacity: 0.9; }
.rank-1 { background: #c98b3a; }
.rank-2 { background: #8a9a5b; }
.rank-3 { background: #6a8fb5; }

.reach-flag, .guard-flag { position: absolute; left: 50%; transform: translateX(-50%); z-index: 25;
  font-size: 0.5625rem; font-weight: bold; letter-spacing: 1px; white-space: nowrap; padding: 2px 5px;
  border-radius: 2px; }
.reach-flag { top: -46px; color: #ffb347; background: rgba(40,22,0,0.9); border: 1px solid #ffb347;
  text-shadow: 0 0 4px #ffb347; }
.guard-flag { top: -46px; color: #7fd4ff; background: rgba(0,26,40,0.9); border: 1px solid #7fd4ff;
  text-shadow: 0 0 4px #7fd4ff; }
.entity.out-of-reach .portrait { filter: grayscale(0.5) brightness(0.75); }
.entity.covering .portrait { filter: drop-shadow(0 0 12px #7fd4ff); }

button.reach-short { border-color: #7a5a20; }
.reach-tag { font-size: 0.5625rem; letter-spacing: 1px; color: #ffb347; }
/* E12: what a move is made of, on the button that fires it. Deliberately quieter than the
   combo and reach tags - those are situational and worth interrupting for, where this is on
   every damaging button all the time and only has to be readable when looked for. The colour
   is the type, not the target's relationship to it: the resist badges on a card carry that. */
.dmg-tag { font-size: 0.5625rem; font-weight: bold; letter-spacing: 0; padding: 0 2px; border-radius: 2px;
           background: rgba(0,0,0,0.35); vertical-align: 1px; }
.dmg-phys   { color: #9aa3ad; }
.dmg-bio    { color: #7fbf5a; }
.dmg-energy { color: #5ab5d6; }

/* The commander's cache: three relics, one decision. */
#screen-relic { display: none; flex-direction: column; padding: 18px 14px; gap: 12px; overflow-y: auto; }
.relic-offer-note { text-align: center; font-size: 0.75rem; color: #8a8578; letter-spacing: 1px; margin-bottom: 4px; }
.relic-choices { display: flex; flex-direction: column; gap: 12px; }
/* Quieter than the cards it sits under - leaving is allowed, not encouraged - but a real
   control at the tap-target floor N13 set, not a line of text pretending to be one. */
.relic-decline { margin-top: 14px; width: 100%; min-height: 44px; background: transparent;
  border: 1px solid #4a4638; color: #8a8578; font-family: monospace; font-size: 0.75rem;
  letter-spacing: 2px; cursor: pointer; }
.relic-decline:hover, .relic-decline:focus-visible { border-color: #8a8578; color: #c0bba8; }
.relic-card { display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  text-align: left; padding: 12px 14px; line-height: 1.4; }
.relic-card-tier { font-size: 0.5625rem; letter-spacing: 2px; opacity: 0.75; }
.relic-card-name { font-size: 0.9375rem; font-weight: bold; }
.relic-card-desc { font-size: 0.6875rem; color: #b9b4a6; white-space: normal; }
.relic-common { border-color: #8a9a5b; color: #b7c98a; }
.relic-rare { border-color: #b07fd4; color: #d9b6f2; box-shadow: 0 0 10px rgba(176,127,212,0.25) inset; }
.relic-rare .relic-card-desc { color: #c8b6d8; }

/* Expedition contracts: optional conditions taken before deploying. */
/* The whole screen scrolls, the way the Outpost and the Citadel's settings do. It used to be a
   fixed-height column with ONE inner scroller - the optional conditions - and everything else
   flex-shrinking around it. Three order cards is most of a phone, so the conditions list was
   squeezed to 220px of its 418px on a tall handset, to 35px on a 400px one, and to exactly
   zero at 320px, where the DEPLOY button also sat below the fold behind `overflow: hidden`.
   The feature was not hidden by a z-index; it was allocated no height and then clipped. */
#screen-contracts { display: none; flex-direction: column; padding: 20px; height: 100%; box-sizing: border-box;
  overflow-y: auto; background: linear-gradient(rgba(14,12,8,0.95), rgba(0,0,0,0.98)); }
/* A guard rather than load-bearing: column flex items default to min-height:auto and so will
   not shrink below their content on their own. What collapsed the conditions list was the
   `min-height: 0` that used to sit on it, which switches that protection off - so this is here
   to stop the next `min-height: 0` from doing it again. Removing this line alone breaks
   nothing today, which is worth knowing before someone deletes it thinking it does. */
#screen-contracts > * { flex-shrink: 0; }
.contract-note { font-size: 0.6875rem; line-height: 1.5; color: #8a8578; margin-bottom: 12px; flex-shrink: 0; }
.contract-list { display: flex; flex-direction: column; gap: 10px; }
.contract-card { display: flex; flex-direction: column; gap: 4px; text-align: left; padding: 10px 12px;
  line-height: 1.4; border-color: #4a463c; color: #8a8578; }
.contract-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; width: 100%; }
.contract-name { font-size: 0.8125rem; font-weight: bold; letter-spacing: 1px; }
.contract-bonus { font-size: 0.6875rem; font-variant-numeric: tabular-nums; }
.contract-desc { font-size: 0.65625rem; white-space: normal; opacity: 0.85; }
.contract-on { border-color: #B8860B; color: #d9a93d; box-shadow: 0 0 10px rgba(184,134,11,0.2) inset; }
/* Pinned to the bottom of the scroll, because making the page scroll pushed DEPLOY off it on
   every handset - trading a feature you could not reach for an action you could not reach.
   The conditions scroll underneath it; the button never leaves. */
.contract-footer { flex-shrink: 0; padding: 12px 0 0; position: sticky; bottom: -20px;
  background: linear-gradient(rgba(0,0,0,0.86), rgba(0,0,0,0.98) 22%); margin: 0 -20px; padding-left: 20px;
  padding-right: 20px; padding-bottom: 20px; }
.contract-mult { text-align: center; font-size: 0.8125rem; font-weight: bold; letter-spacing: 1px;
  color: #B8860B; margin-bottom: 10px; font-variant-numeric: tabular-nums; }
.contract-back { border-color: #888; color: #888; margin-top: 8px; }

/* Field manual: what the game never explained. */
#screen-codex { display: none; flex-direction: column; padding: 20px; height: 100%; box-sizing: border-box;
  overflow: hidden; background: linear-gradient(rgba(12,11,8,0.96), rgba(0,0,0,0.98)); }
.codex-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; padding-bottom: 6px; }
.codex-entry { border-left: 2px solid #B8860B; padding-left: 10px; }
.codex-title { font-size: 0.75rem; font-weight: bold; letter-spacing: 2px; color: #B8860B; margin-bottom: 6px; }
.codex-line { font-size: 0.6875rem; line-height: 1.6; color: #a9a396; margin-bottom: 3px; }
#screen-codex .return-btn { flex-shrink: 0; margin-top: 12px; }

/* The momentum market: cheap spends that never cost the action. */
.tactic-row { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
/* min-width:0 is load-bearing on both the deck and these buttons. A grid item and a flex
   item both default to min-width:auto, which refuses to shrink below their own text - so
   the third tactic pushed past the deck's edge and got clipped on a narrow phone. The
   name and cost are two deliberate lines rather than an accidental wrap.
   The shelf went from three tactics to five, and the deck column is about 180px on a phone:
   five across gave each button 27px and clipped every five-letter name, the old two included.
   They wrap three-and-two now, with the basis picked so three fit a row rather than four. */
.tactic-btn { flex: 1 1 28%; min-width: 0; display: flex; flex-direction: column; align-items: center;
  gap: 2px; padding: 7px 2px; border-color: #7a5c14; color: #d9a93d; overflow: hidden; }
.tactic-name { font-size: 0.59375rem; letter-spacing: 0.5px; }
.tactic-cost { font-size: 0.5625rem; letter-spacing: 0; color: #b8912f; }
.tactic-btn:disabled { border-color: #222; }
.tactic-btn:disabled .tactic-cost { color: #555; }

/* ── Consumables that say what they do ──────────────────────────────────────────────────
   The workbench, the bag and the deck all read one table, so a recipe cannot ship without a
   line of effect. Each surface gets a different amount of room, so each gets a different cut:
   the bench has three lines, the bag and the deck have one. The full sentence is the tooltip
   everywhere, so nothing is hidden behind hover alone. */
/* Four schematics, two up: at 45% the three-line stack keeps its own column instead of
   splitting three-and-one across the card. min-width:0 so the emoji label can shrink. */
.craft-btn { flex: 1 1 45%; min-width: 0; display: flex; flex-direction: column; align-items: flex-start;
  gap: 3px; padding: 8px; text-transform: none; overflow: hidden; }
.craft-name { font-size: 0.71875rem; color: #ccc; }
.craft-what { font-size: 0.625rem; color: #8fa06a; letter-spacing: 0.3px; }
.craft-cost { font-size: 0.625rem; color: #B8860B; }
/* Deliberately NOT dimmed further when the recipe is unaffordable: what a thing does is
   exactly what a player needs to read while deciding what to save materials for. */
/* The bag reads as one line - name, then what it does, pushed to the far edge before [SELL]. */
.inv-what { color: #8fa06a; font-size: 0.625rem; margin-left: 6px; }
/* In combat the bag replaces the whole deck, which is one narrow column: side by side, the
   name and the effect fought for it and "EMP Charge" broke across two lines with the emoji
   stranded. Stacked, every item fits any deck width and it reads like the bench does.
   A full bag is four rows plus the way out, and the deck is about 250px tall on a phone: the
   single-line version already pushed BACK off the bottom, so both are tightened until five
   fit without a scroll. Nobody should have to scroll to cancel out of a menu mid-fight. */
.bag-btn { border-color: #B8860B; color: #B8860B; display: flex; flex-direction: column;
  align-items: flex-start; gap: 2px; min-width: 0; text-transform: none; padding: 6px 10px; }
.item-what { color: #8fa06a; font-size: 0.625rem; }
.bag-back { color: #8B0000; border-color: #8B0000; padding: 7px 10px; }

/* ── The Citadel, drawn ─────────────────────────────────────────────────────────────────
   A hillside at dusk: every meta-upgrade is a structure on it, and buying levels visibly
   lights them up. The card list survives underneath as the ledger view. */


/* The muster: who rolled what, who stands where, before the first node is taken. */
/* Scrolls as a page, for the same reason the contracts screen does. Three doctrine cards, a
   reroll line and a job line sat above a single inner scroller, and at 320px they left the
   squad list 24px of its 733px - the screen where you choose who deploys, with room for a
   quarter of one operator. Found while fixing the same shape on the contracts screen. */
#screen-muster { display: none; flex-direction: column; padding: 20px; height: 100%; box-sizing: border-box;
  overflow-y: auto; background: linear-gradient(rgba(10,12,8,0.95), rgba(0,0,0,0.98)); }
#screen-muster > * { flex-shrink: 0; }   /* the same guard, for the same reason */
/* Pinned, so the way out of the muster never scrolls off it. */
#muster-deploy { position: sticky; bottom: -20px; margin: 0 -20px; padding: 12px 20px 20px;
  background: linear-gradient(rgba(0,0,0,0.86), rgba(0,0,0,0.98) 22%); border-left: 0; border-right: 0;
  border-bottom: 0; }
.muster-head { display: flex; justify-content: flex-end; font-size: 0.6875rem; letter-spacing: 1px;
  color: #B8860B; margin-bottom: 8px; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.muster-list { display: flex; flex-direction: column; gap: 8px; }
.muster-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; align-items: center;
  border: 1px solid #33301f; padding: 8px 10px; opacity: 0.65; }
.muster-row.muster-deployed { border-color: #6B8E23; opacity: 1; }
.muster-who { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline; }
.muster-name { font-size: 0.8125rem; font-weight: bold; }
.muster-class { font-size: 0.5625rem; letter-spacing: 1px; color: #8a8578; }
.muster-quirk { font-size: 0.5625rem; letter-spacing: 1px; color: #ffaa00; }
.muster-quirk-desc { flex-basis: 100%; font-size: 0.59375rem; color: #8a8578; }
.muster-stats { grid-column: 1; font-size: 0.625rem; color: #b9b4a6; font-variant-numeric: tabular-nums; }
.muster-ctl { grid-column: 2; grid-row: 1 / 3; display: flex; flex-direction: column; gap: 5px; }
.muster-rank { font-size: 0.625rem; letter-spacing: 1px; padding: 7px 10px; min-width: 72px; }
.rank-btn-1 { border-color: #c98b3a; color: #c98b3a; }
.rank-btn-2 { border-color: #8a9a5b; color: #8a9a5b; }
.rank-btn-3 { border-color: #6a8fb5; color: #6a8fb5; }
.rank-btn-0 { border-color: #444; color: #666; }
.muster-reroll { font-size: 0.75rem; padding: 5px; border-color: #7a5c14; color: #d9a93d; }
.muster-note { flex-shrink: 0; font-size: 0.625rem; line-height: 1.5; color: #8a8578; margin: 10px 0; }

/* Dossiers: the title on the card, and the four-into-three loadout at the muster. */
.mastery-line { font-size: 0.5625rem; color: #c9a84a; text-transform: uppercase; margin-top: 2px; letter-spacing: 1px; }
.muster-title { font-size: 0.5625rem; letter-spacing: 1px; color: #c9a84a; }
.muster-loadout { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.loadout-chip { background: #111; border: 1px solid #4a5a3a; color: #9ab07a; font-family: monospace;
  font-size: 0.5625rem; padding: 5px 7px; cursor: pointer; text-transform: uppercase; letter-spacing: 0.5px; }
/* The bench's second axis. Chips rather than a menu: three jobs, one holder, and the row the
   holder is on is marked so the muster reads at a glance as a line and a bench that works. */
.muster-jobs { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.job-chip { background: #16150f; border: 1px solid #45412f; color: #8a8578; font-family: inherit;
            font-size: 0.5625rem; letter-spacing: 1px; padding: 4px 7px; cursor: pointer; min-height: 26px; }
.job-chip:hover { border-color: #6fb0d0; }
.job-on { border-color: #6fb0d0; color: #9fd8f0; background: #0e1a20; }
.muster-working { border-left: 2px solid #6fb0d0; }
.muster-jobline { flex-shrink: 0; font-size: 0.625rem; line-height: 1.5; color: #8a8578; letter-spacing: 0.5px;
                  border: 1px solid #33301f; padding: 6px 8px; }
.muster-jobline.job-taken { color: #9fd8f0; border-color: #45605f; }
.loadout-chip.chip-benched { border-color: #6b3a3a; color: #8a6a6a; opacity: 0.7; text-decoration: line-through; }

/* The Chronicle: the memory endless mode never had. */
#screen-chronicle { display: none; flex-direction: column; padding: 18px 14px; gap: 10px; height: 100%; box-sizing: border-box; }
.chronicle-career { flex-shrink: 0; border: 1px solid #4a4030; padding: 8px 10px; display: flex; flex-direction: column; gap: 4px; }
.chronicle-career:empty { display: none; }
.career-line { display: flex; justify-content: space-between; font-size: 0.625rem; letter-spacing: 1px; color: #b9b4a6; }
.career-line span:last-child { color: #d9a93d; }
.chronicle-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.chronicle-list { flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; }
.chronicle-entry { border: 1px solid #33301f; background: #0a0a08; padding: 8px 10px; }
.chronicle-epitaph { font-size: 0.75rem; color: #d5cfc0; font-style: italic; line-height: 1.4; }
.chronicle-facts { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 5px; font-size: 0.5625rem; letter-spacing: 1px; color: #8a8578; text-transform: uppercase; }
.chronicle-seed { color: #7ab0d0; }
.chronicle-empty { font-size: 0.6875rem; color: #8a8578; text-align: center; padding: 30px 10px; line-height: 1.6; }
.chronicle-rung { color: #c08ad0; }
.career-rung span:last-child { color: #c08ad0; }
/* The ladder: eight rows that have to stay readable at 320px, so the description wraps onto
   its own full-width line under the name rather than fighting the multiplier for room. */
/* E13: the dossiers, for every class rather than only the seven currently standing in the
   Outpost. Same column, same grid shape as the ladder above it - the Chronicle already stacks
   several blocks in a narrow column and this must not be the one that widens it. */
.chronicle-dossiers { flex-shrink: 0; border: 1px solid #4a4030; padding: 8px 10px; display: flex; flex-direction: column; gap: 3px; }
.chronicle-dossiers:empty { display: none; }
.dossier-head { font-size: 0.625rem; letter-spacing: 1px; color: #c08ad0; margin-bottom: 3px; }
.dossier-row { display: grid; grid-template-columns: 20px 1fr auto; gap: 1px 6px; font-size: 0.625rem; letter-spacing: 1px; align-items: baseline; }
.dossier-mark { color: #8a8578; }
.dossier-cls { color: #b9b4a6; }
.dossier-xp { color: #d9a93d; font-variant-numeric: tabular-nums; }
.dossier-next { grid-column: 2 / 4; font-size: 0.5625rem; color: #7d786c; letter-spacing: 0; line-height: 1.35; text-transform: none; }
.dossier-r1 .dossier-mark, .dossier-r1 .dossier-cls { color: #9a9488; }
.dossier-r2 .dossier-mark, .dossier-r2 .dossier-cls { color: #d9a93d; }
.dossier-r3 .dossier-mark, .dossier-r3 .dossier-cls { color: #c08ad0; }
.dossier-r0 .dossier-cls, .dossier-r0 .dossier-xp { color: #5c584e; }
.dossier-note { font-size: 0.5625rem; color: #7d786c; letter-spacing: 0; line-height: 1.4; text-transform: none; margin-top: 5px; border-top: 1px solid #3a3428; padding-top: 5px; }

/* E14: the roll of the dead. Permadeath had no memory outside the run that caused it - the
   record was built, narrowed on the way to disk, and never read back. */
/* E08b: the capstone card on a promotion screen - the one card above the fork. */
.perk-cap { border-color: #c08ad0 !important; }
.perk-cap .relic-card-tier { color: #c08ad0; }
.perk-cap .relic-card-name { color: #e0b8ee; }

.chronicle-roll { flex-shrink: 0; border: 1px solid #5a3030; padding: 8px 10px; display: flex; flex-direction: column; gap: 3px; }
.chronicle-roll:empty { display: none; }
.roll-head { font-size: 0.625rem; letter-spacing: 1px; color: #c86a5a; margin-bottom: 3px; }
.roll-row { display: grid; grid-template-columns: 14px auto 1fr auto; gap: 1px 6px; font-size: 0.625rem; letter-spacing: 1px; align-items: baseline; }
.roll-mark { color: #8b4a4a; }
.roll-name { color: #d5cfc0; }
.roll-cls { font-size: 0.5625rem; color: #8a8578; letter-spacing: 1px; }
.roll-where { color: #d9a93d; font-variant-numeric: tabular-nums; font-size: 0.5625rem; }
.roll-what { grid-column: 2 / 5; font-size: 0.5625rem; color: #7d786c; letter-spacing: 0; line-height: 1.35;
             text-transform: none; font-style: italic; padding-bottom: 3px; }
.roll-none, .roll-more { font-size: 0.5625rem; color: #7d786c; letter-spacing: 0; line-height: 1.4; text-transform: none; }
.roll-more { margin-top: 4px; border-top: 1px solid #3a2828; padding-top: 4px; }
.career-lost span:last-child { color: #c86a5a; }

.chronicle-ladder { flex-shrink: 0; border: 1px solid #4a4030; padding: 8px 10px; display: flex; flex-direction: column; gap: 3px; }
.chronicle-ladder:empty { display: none; }
.ladder-head { font-size: 0.625rem; letter-spacing: 1px; color: #c08ad0; margin-bottom: 3px; }
.ladder-sealed { font-size: 0.625rem; letter-spacing: 1px; color: #8a8578; line-height: 1.5; }
.ladder-rung { display: grid; grid-template-columns: 14px 1fr auto; gap: 2px 6px; font-size: 0.625rem; letter-spacing: 1px; align-items: baseline; }
.ladder-mark { color: #8a8578; }
.ladder-name { color: #b9b4a6; }
.ladder-mult { color: #d9a93d; font-variant-numeric: tabular-nums; }
.ladder-desc { grid-column: 2 / 4; font-size: 0.5625rem; color: #7d786c; letter-spacing: 0; line-height: 1.35; text-transform: none; }
.ladder-cleared .ladder-mark, .ladder-cleared .ladder-name { color: #c08ad0; }
.ladder-open .ladder-mark, .ladder-open .ladder-name { color: #d9a93d; }
.ladder-locked .ladder-name, .ladder-locked .ladder-mult { color: #5c584e; }
.title-epitaph { text-align: center; font-size: 0.625rem; font-style: italic; color: #8a8272; margin-bottom: 10px; line-height: 1.5; max-width: 300px; align-self: center; }

/* ── Combat that moves: all transforms, no new art. ────────────────────────────────── */
@keyframes lungeRight { 0% { transform: translateX(0); } 45% { transform: translateX(26px); } 100% { transform: translateX(0); } }
@keyframes lungeLeft { 0% { transform: translateX(0); } 45% { transform: translateX(-26px); } 100% { transform: translateX(0); } }
.anim-lunge-right { animation: lungeRight 0.42s ease-in-out; }
.anim-lunge-left { animation: lungeLeft 0.42s ease-in-out; }
@keyframes recoilLeft { 0% { transform: translateX(0); } 35% { transform: translateX(-9px) rotate(-2deg); } 100% { transform: translateX(0); } }
@keyframes recoilRight { 0% { transform: translateX(0); } 35% { transform: translateX(9px) rotate(2deg); } 100% { transform: translateX(0); } }
.anim-recoil-left { animation: recoilLeft 0.3s ease-out; }
.anim-recoil-right { animation: recoilRight 0.3s ease-out; }
.muzzle-flash { position: absolute; top: 40%; width: 16px; height: 16px; border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, #ffb347 45%, transparent 70%);
  z-index: 30; pointer-events: none; animation: muzzle 0.22s ease-out forwards; }
.flash-right { right: -6px; } .flash-left { left: -6px; }
@keyframes muzzle { 0% { opacity: 1; transform: scale(0.6); } 100% { opacity: 0; transform: scale(1.7); } }
.tracer-line { position: absolute; height: 2px; transform-origin: 0 50%;
  background: linear-gradient(90deg, rgba(255, 220, 150, 0.15), #ffd76a);
  z-index: 25; pointer-events: none; animation: tracer 0.2s linear forwards; }
@keyframes tracer { 0% { opacity: 1; } 100% { opacity: 0; } }
.intent-pulse { animation: intentPulse 0.5s ease-in-out infinite; }
@keyframes intentPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.35); } }
/* The reduced-motion audit: every effect above, stilled - the dead simply lie settled. */
@media (prefers-reduced-motion: reduce) {
  .anim-lunge-right, .anim-lunge-left, .anim-recoil-left, .anim-recoil-right,
  .muzzle-flash, .tracer-line, .intent-pulse, .entity.dead.dying .portrait { animation: none !important; }
  .entity.dead.dying .portrait { transform: rotate(78deg) translateY(12%) translateX(5%); }
  /* F12: this was written as a class, and #front-banner { opacity: 0 } is an id - so the rule
     lost the cascade and the splash stayed invisible under the very setting it was written
     for. Same selector weight as the thing it is overriding. */
  #front-banner.front-banner-show { animation: none !important; opacity: 1; }
  #settle-banner.settle-banner-show { animation: none !important; opacity: 1; }
}

/* A cursed card is unmistakable: violet heat and a warning where the tier goes. */
.relic-cursed { border-color: #8a3fbf; color: #c98aff; box-shadow: 0 0 12px rgba(138, 63, 191, 0.35) inset; }
.relic-cursed .relic-card-tier { color: #ff5a5a; }
.relic-cursed .relic-card-desc { color: #a98ac9; }

/* The seed panel on the contract board: type a wasteland, or take today's. */
.seed-panel { flex-shrink: 0; border: 1px dashed #3a4a5a; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.seed-row { display: flex; gap: 6px; }
.seed-input { flex: 1; min-width: 0; background: #0a0a08; border: 1px solid #444; color: #d5cfc0;
    font-family: monospace; font-size: 0.6875rem; padding: 8px; letter-spacing: 1px; text-transform: uppercase; }
.seed-input:focus { outline: none; border-color: #7ab0d0; }
.seed-daily-btn { flex-shrink: 0; background: #111; border: 1px solid #7ab0d0; color: #7ab0d0;
    font-family: monospace; font-size: 0.625rem; padding: 8px; cursor: pointer; text-transform: uppercase; }
.seed-daily-btn:hover { background: #7ab0d0; color: #000; }
.seed-note { font-size: 0.5625rem; color: #8a8578; line-height: 1.5; }
.ascension-btn { background: #140a14; border: 1px solid #8a3fbf; color: #c98aff; font-family: monospace;
  font-size: 0.625rem; padding: 8px; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; }
.ascension-btn:hover { background: #8a3fbf; color: #000; }

/* The sector front: a badge that rides the map header, and a splash on sector entry. */
#front-badge { display: flex; align-items: center; justify-content: center; gap: 8px; flex-shrink: 0;
    border: 1px solid #6b3a3a; background: #140808; color: #c96a5a; font-size: 0.6875rem;
    letter-spacing: 2px; padding: 5px 8px; }
.front-icon { font-size: 0.75rem; }
#front-banner { position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%);
    z-index: 40; pointer-events: none; opacity: 0; text-align: center;
    background: rgba(10, 4, 4, 0.92); border: 2px solid #8B0000; padding: 18px 26px; }
.front-banner-name { color: #ff6a4a; font-size: 1.25rem; font-weight: bold; letter-spacing: 3px; }
.front-banner-desc { color: #b9a68a; font-size: 0.6875rem; margin-top: 8px; max-width: 260px; line-height: 1.5; }
@keyframes frontBanner { 0% { opacity: 0; transform: translate(-50%, -60%); }
    12% { opacity: 1; transform: translate(-50%, -50%); } 80% { opacity: 1; } 100% { opacity: 0; } }
.front-banner-show { animation: frontBanner 3.2s ease-out forwards; }

/* The tie between two operators, on the roster card. */
.bond-line { font-size: 0.5625rem; color: #7ab0d0; text-transform: uppercase; margin-top: 2px; }
/* Earned, not rolled - so it reads in the wound colour rather than the quirk colour. */
.scar-line { font-size: 0.5625rem; color: #c96a5a; text-transform: uppercase; margin-top: 2px; letter-spacing: 1px; }
.upg-btn.scar-btn { border-color: #8B4A3A; color: #d98a78; }

/* Gear slots on the Outpost card. */
.gear-row { border-top: 1px dashed #33301f; padding-top: 6px; margin-top: 6px; }
.gear-slot { border-color: #4a5a6b; color: #8fb0c9; font-size: 0.625rem; }
.gear-pick { border-color: #B8860B; color: #d9a93d; }
.gear-none { font-size: 0.625rem; color: #8a8578; padding: 4px 0; }

/* The Armory: a stall of rows, price on the right, the stock list scrolls inside. */
#screen-shop { display: none; flex-direction: column; padding: 18px 14px; gap: 10px; height: 100%; box-sizing: border-box; }
#shop-scrap { flex-shrink: 0; font-size: 0.75rem; color: #d9a93d; letter-spacing: 1px; }
#shop-stock { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.shop-row { display: flex; align-items: center; gap: 10px; border: 1px solid #333; background: #0a0a08; padding: 10px; }
.shop-info { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.shop-name { font-size: 0.75rem; font-weight: bold; color: #d5cfc0; text-transform: uppercase; letter-spacing: 1px; }
.shop-desc { font-size: 0.625rem; color: #8a8578; line-height: 1.4; }
.shop-buy { flex-shrink: 0; background: #111; border: 1px solid #3aa17e; color: #3aa17e; font-family: monospace; font-size: 0.75rem; padding: 8px 10px; cursor: pointer; }
.shop-buy:hover:not(:disabled) { background: #3aa17e; color: #000; }
.shop-buy:disabled { opacity: 0.3; cursor: not-allowed; }

/* A survivor on the route. The portrait carries the screen, so it gets the room to do it. */
#screen-recruit { display: none; flex-direction: column; padding: 18px 14px; gap: 10px; height: 100%; box-sizing: border-box; }
#screen-cache { display: none; flex-direction: column; padding: 18px 14px; gap: 10px; height: 100%; box-sizing: border-box; }
#recruit-body { flex: 1; overflow-y: auto; min-height: 0; display: flex; flex-direction: column; justify-content: center; }
.recruit-card { display: flex; flex-direction: column; align-items: center; gap: 10px; border: 1px solid #333; background: #0a0a08; padding: 12px; }
.recruit-portrait { width: auto; max-width: 62%; max-height: 190px; object-fit: contain; filter: drop-shadow(0 6px 10px rgba(0,0,0,0.7)); }
.recruit-info { width: 100%; display: flex; flex-direction: column; gap: 4px; }
.recruit-name { font-size: 0.9375rem; font-weight: bold; color: #d5cfc0; text-transform: uppercase; letter-spacing: 2px; text-align: center; }
.recruit-class { font-size: 0.625rem; color: #B8860B; text-transform: uppercase; letter-spacing: 1px; text-align: center; }
.recruit-pitch { font-size: 0.6875rem; color: #8a8578; line-height: 1.5; font-style: italic; text-align: center; margin: 4px 0; }
.recruit-stats { font-size: 0.6875rem; color: #d5cfc0; text-align: center; letter-spacing: 1px; }
.recruit-res { font-size: 0.625rem; color: #6B8E23; text-align: center; text-transform: uppercase; letter-spacing: 1px; }
/* K01: what walks in, and what it is for. The arrival line is the quieter of the two - it
   corrects the stat line above it rather than competing with it - and the answer line is the
   one the decision turns on, so it carries the card's accent. */
.recruit-arrival { font-size: 0.625rem; color: #8a8272; text-align: center; letter-spacing: 0.5px; margin-top: 2px; }
.recruit-why { font-size: 0.625rem; color: #c9a227; text-align: center; letter-spacing: 0.5px;
               margin-top: 4px; padding-top: 4px; border-top: 1px solid #3a3730; line-height: 1.5; }
.recruit-verbs { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.recruit-verbs li { font-size: 0.625rem; color: #8a8578; border-left: 2px solid #333; padding-left: 7px; }
.recruit-verbs b { color: #d5cfc0; font-weight: bold; }
#recruit-sign { border-color: #3aa17e; color: #3aa17e; }
#recruit-sign:disabled { opacity: 0.35; cursor: not-allowed; }
.map-node.recruit-node .node-icon { color: #3aa17e; }
.shop-tag { flex-shrink: 0; font-size: 0.625rem; color: #666; letter-spacing: 1px; }
/* K07: the shelf sells one of three, so the three have to read as one block with a rule on
   it rather than as three separate offers. The heading carries the rule; the dimmed row is
   what the other two become once the choice is made. */
.shop-head { font-size: 0.625rem; color: #3aa17e; letter-spacing: 1px; margin-top: 2px; }
.shop-row-gone { opacity: 0.45; }
.shop-pick { display: flex; flex-direction: column; gap: 6px; border: 1px dashed #3aa17e; padding: 10px; }
.shop-pick-title { font-size: 0.625rem; color: #3aa17e; letter-spacing: 1px; }
.shop-pick-btn { background: #111; border: 1px solid #444; color: #ccc; font-family: monospace; font-size: 0.6875rem; padding: 8px; cursor: pointer; text-align: left; }
.shop-pick-btn:hover { border-color: #3aa17e; color: #3aa17e; }
.shop-pick-cancel { color: #8a8578; text-align: center; }
#shop-leave { flex-shrink: 0; }

/* Field promotions reuse the cache card language: signatures glow, training is plain. */
#screen-perk { display: none; flex-direction: column; padding: 18px 14px; gap: 12px; overflow-y: auto; }
.perk-sig { border-color: #d9a93d; color: #ffd76a; box-shadow: 0 0 10px rgba(217,169,61,0.25) inset; }
.perk-sig .relic-card-desc { color: #cbb98a; }
.perk-stat { border-color: #667; color: #99a; }
.perk-bank { border-color: #888; color: #888; margin-top: 6px; }

/* Enemy signatures: the name of the thing a hostile does, on the hostile that does it. */
/* The live state - plate remaining, LIVE, LOCKED - is the half that matters mid-fight, so the
   badge wraps rather than truncating: an ellipsis ate exactly the part worth reading. */
.sig-tag { font-size: 0.46875rem; line-height: 1.25; letter-spacing: .5px; text-transform: uppercase;
  text-align: center; color: #d98a5a; border: 1px solid #6b3a22; background: rgba(20,8,4,0.78);
/* No overflow-wrap at all, which is the default and is the point: it wraps at the spaces and
   nowhere else. It used to say "anywhere", which breaks INSIDE a word as soon as the line is
   short, so RANGING SHOT came out as RANG / ING / SHOT in a slot with room for RANGING on a
   line of its own. A name that will not fit on any line now overhangs its slot by a few pixels
   instead of being cut in half - the badge is still a name, which is the whole reason C11
   chose wrapping over an ellipsis. */
  padding: 1px 2px; margin-bottom: 2px; }
/* A repeat of a tag already printed in this line. Held in the layout so every card in the row
   is the same height, but not painted twice. */
.sig-tag.sig-echo { visibility: hidden; }
.sig-tag.sig-spent { color: #7a7266; border-color: #3a352c; text-decoration: line-through; }

/* The dossier: what a hostile does, what it resists, and what it has cost you. Opened by
   tapping any hostile while you are not mid-aim, so it never competes with targeting. */
.dossier { display: none; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(300px, 88%); max-height: 76%; overflow-y: auto; z-index: 60;
  flex-direction: column; gap: 8px; padding: 14px;
  background: rgba(8, 7, 5, 0.97); border: 1px solid #6b5a2a; box-shadow: 0 8px 28px rgba(0,0,0,0.85); }
.dossier-body { display: flex; flex-direction: column; gap: 8px; }
.dossier-name { font-size: 0.9375rem; font-weight: bold; color: #d9a93d; letter-spacing: 1px; text-transform: uppercase; }
.dossier-sub { font-size: 0.5625rem; letter-spacing: 1.5px; color: #8a8578; text-transform: uppercase; margin-top: -4px; }
.dossier-sig { display: flex; flex-direction: column; gap: 3px; border-left: 2px solid #d98a5a;
  padding: 6px 0 6px 8px; background: rgba(30,14,6,0.5); }
.dossier-sig-name { font-size: 0.75rem; font-weight: bold; color: #d98a5a; text-transform: uppercase; letter-spacing: 1px; }
.dossier-sig-kind { font-size: 0.5rem; letter-spacing: 1.5px; color: #8a8578; }
.dossier-sig-desc { font-size: 0.6875rem; line-height: 1.5; color: #c9c0ab; }
/* What was bolted onto this particular hostile, under the species file rather than inside it:
   an affix belongs to the unit standing there, and the file behind it is the same either way.
   Deep red against the file's amber, matching the glow an affixed hostile already carries. */
.dossier-affix { margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(139,0,0,0.55); }
.dossier-affix .dossier-sig { border-left-color: #8B0000; background: rgba(40,8,8,0.5); }
.dossier-affix .dossier-sig-name { color: #c86a5a; }
.dos-res-row { display: flex; gap: 6px; }
.dos-res { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 2px; align-items: center;
  border: 1px solid #333; padding: 5px 2px; font-size: 0.5rem; letter-spacing: 1px; color: #7a7266; }
.dos-res.dos-strong { border-color: #4a6b3a; color: #9ab07a; }
.dos-res.dos-weak { border-color: #6b3a3a; color: #d98a8a; }
.dos-res.dos-immune { border-color: #3a5a6b; color: #7fb0c9; }
.dossier-tally { display: flex; justify-content: space-between; gap: 6px; font-size: 0.5625rem;
  letter-spacing: 1px; color: #8a8578; border-top: 1px solid #2a2620; padding-top: 8px; }
.dossier-tally b { color: #d5cfc0; font-size: 0.75rem; }
.dossier-tally .dos-cost b { color: #d98a8a; }
.dossier-none { font-size: 0.6875rem; color: #8a8578; text-align: center; padding: 16px 0; }
.dossier-close { background: #111; border: 1px solid #444; color: #999; font-family: monospace;
  font-size: 0.6875rem; padding: 9px; cursor: pointer; letter-spacing: 1px; }
.dossier-close:hover { border-color: #d9a93d; color: #d9a93d; }
/* An idle hostile invites a tap without pretending to be a target. */
.entity.inspectable { cursor: help; }
.entity.inspectable .portrait { transition: filter .2s; }
.entity.inspectable:hover .portrait { filter: drop-shadow(0 0 8px rgba(217,169,61,0.55)); }

/* First contact: the smallest true thing about a system, the first time it matters. The
   container never eats a click - only the card does - so a prompt can never block the fight
   underneath it. */
.prompt { display: none; position: absolute; left: 0; right: 0; bottom: 0; z-index: 120;
  justify-content: center; padding: 0 10px 10px; pointer-events: none; }
.prompt-card { pointer-events: auto; width: 100%; max-width: 460px;
  background: rgba(10, 9, 6, 0.97); border: 1px solid #B8860B;
  box-shadow: 0 -6px 24px rgba(0,0,0,0.8); padding: 12px 14px;
  display: flex; flex-direction: column; gap: 7px; }
.prompt-title { font-size: 0.75rem; font-weight: bold; letter-spacing: 2px; color: #d9a93d; text-transform: uppercase; }
.prompt-body { font-size: 0.71875rem; line-height: 1.6; color: #c9c0ab; }
.prompt-row { display: flex; gap: 8px; align-items: stretch; margin-top: 2px; }
.prompt-ok { flex: 1 1 auto; background: #111; border: 1px solid #B8860B; color: #d9a93d;
  font-family: monospace; font-size: 0.75rem; letter-spacing: 1px; padding: 9px; cursor: pointer; }
.prompt-ok:hover { background: #B8860B; color: #000; }
.prompt-off { flex: 0 0 auto; background: none; border: 1px solid #3a352c; color: #7a7266;
  font-family: monospace; font-size: 0.5625rem; letter-spacing: 1px; padding: 9px 8px; cursor: pointer; }
.prompt-off:hover { border-color: #8a8578; color: #a9a396; }

/* Read the room: what is coming, what a target will absorb, and where a number came from. */
.threat-tag { font-size: 0.5625rem; font-weight: bold; letter-spacing: 1px; text-align: center;
  color: #e0a95a; border: 1px solid #6b4a1a; background: rgba(24,14,2,0.8); padding: 1px 3px; margin-bottom: 2px; }
.threat-tag.threat-fatal { color: #ff8a7a; border-color: #8B0000; background: rgba(40,4,4,0.9);
  animation: threatPulse 1.4s ease-in-out infinite; }
@keyframes threatPulse { 0%,100% { box-shadow: 0 0 0 rgba(255,68,68,0); } 50% { box-shadow: 0 0 9px rgba(255,68,68,0.65); } }
.soak-tag { font-size: 0.5625rem; letter-spacing: 1px; text-align: center; color: #9fb6c9;
  border: 1px solid #3a5a6b; background: rgba(6,16,22,0.8); padding: 1px 3px; margin-bottom: 2px; }
/* F14: these carry the arithmetic behind a blow and were plain divs - no tab stop, no role,
   so the one surface that explains a number was mouse-only. */
.log-explainable { cursor: help; }
.log-explainable:hover, .log-explainable:focus-visible { text-decoration: underline dotted; }
.log-explainable:focus-visible, .dossier:focus-visible, .explain:focus-visible,
.prompt-card:focus-visible { outline: 2px solid #B8860B; outline-offset: 2px; }
/* F14: what an intent icon means, on the screen rather than in the manual. */
.intent-legend { display: flex; flex-wrap: wrap; gap: 4px 10px; padding: 6px 8px; margin: 0 5px 4px;
  border: 1px solid #2a2820; background: #0a0a08; font-size: 0.5625rem; letter-spacing: 0.06em; }
.intent-legend-item { display: flex; align-items: center; gap: 4px; color: #8a8578; white-space: nowrap; }
.intent-legend-item b { color: #c9a84a; font-weight: normal; }
/* F14: the overdrive fork is a one-time, run-locking choice and explained itself only in a
   title tooltip, which does not exist on a touch screen. */
.od-choice-note { color: #b9a68a; font-size: 0.625rem; line-height: 1.45; padding: 4px 6px;
  border-left: 2px solid #8B0000; margin: 2px 0 4px; }
.explain { display: none; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  width: min(300px, 88%); max-height: 80%; overflow-y: auto; z-index: 70; justify-content: center; }
.explain-card { width: 100%; background: rgba(8,7,5,0.97); border: 1px solid #6b5a2a;
  box-shadow: 0 8px 28px rgba(0,0,0,0.85); padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.ex-head { font-size: 0.8125rem; font-weight: bold; color: #d9a93d; letter-spacing: 1px; text-transform: uppercase; }
.ex-sub { font-size: 0.5625rem; letter-spacing: 1.5px; color: #8a8578; text-transform: uppercase; margin-top: -8px; }
.ex-rows { display: flex; flex-direction: column; }
.ex-row { display: flex; justify-content: space-between; gap: 10px; font-size: 0.6875rem;
  padding: 5px 0; border-bottom: 1px solid #1f1c15; color: #c9c0ab; }
.ex-row span:last-child { font-weight: bold; font-variant-numeric: tabular-nums; }
.ex-row.ex-base { color: #8a8578; }
.ex-row.ex-none { color: #6a6558; font-style: italic; }
.ex-up span:last-child, .ex-row .ex-up { color: #9ab07a; }
.ex-down span:last-child, .ex-row .ex-down { color: #d98a8a; }
.ex-row.ex-soak { color: #9fb6c9; }
.ex-row.ex-total { border-bottom: 0; border-top: 1px solid #6b5a2a; margin-top: 3px; padding-top: 7px;
  color: #d9a93d; font-size: 0.8125rem; font-weight: bold; }

/* An operator on the floor with a clock over them. It reads like the threat tag because it is
   the same question asked from the other side: who is about to be lost. */
.down-tag { background: rgba(20, 0, 0, 0.92); border: 1px solid #B22222; color: #ff6b6b; font-size: 0.6875rem;
  font-weight: bold; padding: 2px 7px; letter-spacing: 1px; white-space: nowrap; animation: down-pulse 1s ease-in-out infinite; }
.down-tag.down-lost { border-color: #4a4a4a; color: #6e6e6e; animation: none; letter-spacing: 2px; }
@keyframes down-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) { .down-tag { animation: none; } }
/* Somebody still savable is not a corpse. The .dead dimming above (0.55 opacity, 90% grey) is
   right for a body and wrong for the thing the player is supposed to act on this turn - it took
   the tag down with it and left an empty patch of rubble where an operator was. */
.entity.dead.bleeding-out { opacity: 1; filter: none; }
.entity.bleeding-out .portrait { filter: grayscale(0.35) drop-shadow(0 0 10px rgba(200, 30, 30, 0.9)); }
.down-tag { font-size: 0.75rem; }
:root.motion-off .down-tag { animation: none; }

/* Walking out: the offer is a priced decision, so both halves of it are on the panel rather
   than described in prose. Green because it is the one ending that is not a defeat. */
.camp-walk { border-color: #6B8E23; color: #6B8E23; }
.camp-walk-locked { font-size: 0.6875rem; color: #6a6a5a; text-align: center; padding: 10px 6px; font-style: italic; }
/* The collector's cache: the one camp option that is not on the menu every time. Violet
   rather than the camp's greens and golds, because it is not a service - it is a bargain, and
   it should not read as another way to feel better. Both halves are on the button: a curse
   whose cost is behind a hover is a trick, not a decision. */
.camp-cache { border-color: #7a3f7a; background: #150a15; display: flex; flex-direction: column;
  align-items: flex-start; gap: 4px; text-transform: none; padding: 10px 12px; }
.camp-cache:hover:not(:disabled) { background: #241024; border-color: #a4508b; }
.cache-head { color: #c07ac0; font-weight: bold; letter-spacing: 1px; font-size: 0.75rem;
  text-transform: uppercase; }
.cache-gain { color: #8fa06a; font-size: 0.6875rem; }
.cache-cost { color: #b05a5a; font-size: 0.6875rem; }
.cache-price { color: #6a6a5a; font-size: 0.625rem; font-style: italic; }
.camp-extract-panel { border: 1px solid #6B8E23; background: #0b0f08; padding: 12px 14px; margin-bottom: 12px; display: flex; flex-direction: column; gap: 6px; }
.camp-extract-head { color: #6B8E23; font-weight: bold; letter-spacing: 2px; font-size: 0.8125rem; }
.camp-extract-line { font-size: 0.6875rem; color: #8a8578; line-height: 1.5; }
.camp-extract-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 0.6875rem; color: #8a8578; letter-spacing: 1px; }
.camp-extract-row b { color: #d5cfc0; font-size: 0.75rem; text-align: right; }
.camp-extract-row em { color: #6B8E23; font-style: normal; }
.camp-extract-warn { font-size: 0.625rem; color: #B22222; letter-spacing: 1px; text-transform: uppercase; margin-top: 2px; }
.camp-walk-go { border-color: #6B8E23; color: #6B8E23; font-weight: bold; }
.chronicle-walked { border-color: #3d5216; }
.chronicle-walked .chronicle-epitaph b { color: #6B8E23; letter-spacing: 1px; }
.chronicle-won { border-color: #6a5720; background: linear-gradient(90deg, rgba(201,168,74,0.07), transparent); }
.chronicle-won .chronicle-epitaph b { color: #c9a84a; letter-spacing: 1px; }
.career-won span:last-child { color: #c9a84a; }

/* A node holding a known composition rather than a loose patrol. The name in the label is the
   information; the colour is what makes it scannable against a map of plain faction nodes.
   A border tint would not read - the node is a circle. */
.map-node.formation-node .node-lbl { color: #B8860B; }
.map-node.formation-node.node-locked .node-lbl { color: #8a6a2a; }

/* Doctrines: a promise about the shape of the line, made at the muster and carried all run.
   The card shows the rule and the edge together, because a rule with no edge is just a tax. */
.doctrine-list { flex-shrink: 0; display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.doctrine-card { display: flex; flex-direction: column; gap: 3px; text-align: left; padding: 8px 10px;
    border: 1px solid #3a3a2a; background: #0c0c08; color: #b9b4a6; font-family: inherit; cursor: pointer; }
.doctrine-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.doctrine-name { font-size: 0.6875rem; font-weight: bold; letter-spacing: 2px; color: #d5cfc0; }
.doctrine-bonus { font-size: 0.6875rem; font-weight: bold; color: #B8860B; font-variant-numeric: tabular-nums; }
.doctrine-rule { font-size: 0.625rem; color: #b9b4a6; line-height: 1.4; }
.doctrine-edge { font-size: 0.625rem; color: #6B8E23; line-height: 1.4; }
.doctrine-no { font-size: 0.59375rem; color: #B22222; letter-spacing: 1px; }
.doctrine-card.doctrine-on { border-color: #B8860B; background: #17130a; }
.doctrine-card.doctrine-on .doctrine-name { color: #B8860B; }
.doctrine-card.doctrine-unmet { opacity: 0.6; }

/* Live on the map header, the way the front is: still keeping it, or visibly not. */
#doctrine-badge { display: flex; align-items: center; justify-content: center; gap: 8px; flex-shrink: 0;
    border: 1px solid #6b5a20; background: #121006; color: #B8860B; font-size: 0.6875rem;
    letter-spacing: 2px; padding: 5px 8px; }
#doctrine-badge.doctrine-badge-broken { border-color: #6b2020; background: #140808; color: #B22222; }
.doctrine-badge-mult { font-variant-numeric: tabular-nums; opacity: 0.85; }
/* Two factions that had no ground of their own now have one each. */
.tr-flooded { background: #16242a; color: #7fc4cf; }
.tr-nest    { background: #241a22; color: #c98fb0; }
/* Something an event promised, and how far off it is. Amber: not a threat yet, but coming. */
.bounty-owed { color: #d9a93d; border-left: 2px solid #7a5c14; padding-left: 6px; }

/* ── F04: the shelf above the Citadel's cap, on the contract board ─────────────────── */
.req-list { display: flex; flex-direction: column; gap: 8px; margin: 14px 0 4px; }
.req-head { display: flex; justify-content: space-between; align-items: baseline;
            font-size: 0.78rem; letter-spacing: 0.12em; color: #8a7f5c; }
.req-purse { color: #d8c98a; font-weight: bold; letter-spacing: 0; }
.req-card { border: 1px solid #3a3427; background: rgba(20, 18, 12, 0.55); padding: 8px 10px; }
.req-name { font-size: 0.82rem; letter-spacing: 0.08em; color: #c9b978; }
.req-desc { font-size: 0.74rem; color: #8f8672; margin: 3px 0 6px; line-height: 1.35; }
.req-none { font-size: 0.72rem; color: #6f6858; font-style: italic; }
.req-row { display: flex; flex-wrap: wrap; gap: 6px; }
.req-buy { background: rgba(0, 0, 0, 0.35); border: 1px solid #4a4230; color: #b8ac86;
           font: inherit; font-size: 0.74rem; letter-spacing: 0.04em; padding: 5px 9px;
           cursor: pointer; }
.req-buy:hover:not(:disabled) { border-color: #8a7a45; color: #e2d6a4; }
.req-buy:disabled { opacity: 0.38; cursor: default; }
.req-buy.req-on { border-color: #6B8E23; color: #b9d47a; }
.req-price { color: #d8c98a; margin-left: 6px; }
.req-clear { background: none; border: 1px solid #6b3a3a; color: #c98a8a; font: inherit;
             font-size: 0.72rem; letter-spacing: 0.06em; padding: 6px; cursor: pointer; }
.req-clear:hover { border-color: #8b4a4a; color: #e0a8a8; }

/* F10: the muster's loadout chips are now also drawn on the Outpost operator card and on the
   recruit card, neither of which is the muster's grid - so the row stands on its own there
   rather than spanning a column that does not exist. */
.upgrade-header .muster-loadout,
.recruit-info .muster-loadout { grid-column: auto; margin-top: 6px; }
