/* WAR-SLG prototype client. Direction B: faceted low-poly art (assets/), flat interface, system fonts only.
   No permanent animation, no blur: the page is idle when nothing changes. */
:root {
  --bg: #14181a; --panel: #1b2225; --card: #242d31; --line: #3a4747; --text: #e8ebe4; --dim: #a9b3ae;
  --self: #2bb5a0; --ally: #4d8fe0; --enemy: #e2603a; --neutral: #9aa0a0; --gold: #e0b23a;
  --food: #d9b85f; --steel: #8fa3b8; --credits: #a78bfa;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  background: var(--bg); color: var(--text); font: 15px/1.35 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  overflow: hidden; overflow-wrap: anywhere; -webkit-text-size-adjust: 100%; touch-action: manipulation;
}
[hidden] { display: none !important; }
h1, h2, .hero-name { font-family: "Avenir Next Condensed", "Roboto Condensed", "Arial Narrow", system-ui, sans-serif; text-transform: uppercase; letter-spacing: .04em; }
h1 { margin: 0 0 4px; font-size: 34px; line-height: 1; }
h2 { margin: 4px 0 8px; font-size: 22px; }
h3 { margin: 16px 0 6px; font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }
h4 { margin: 10px 0 4px; font-size: 15px; }
p { margin: 6px 0; }

button {
  min-height: 44px; min-width: 44px; padding: 0 14px; border-radius: 4px; border: 1px solid var(--line);
  border-bottom-width: 3px; border-bottom-color: #1a2224; background: linear-gradient(160deg, #36444a 0 55%, #2e3a3e 55%); color: var(--text); font: inherit; font-weight: 600; cursor: pointer;
}
button:not(:disabled):active { transform: translateY(2px); border-bottom-width: 1px; margin-bottom: 2px; }
button:disabled { opacity: .45; cursor: default; }
button.primary { background: linear-gradient(160deg, #3fd0b9 0 55%, #2bb5a0 55%); color: #04211c; border-color: transparent; border-bottom-color: #14705f; }
button.primary:disabled { background: linear-gradient(160deg, #3b494c 0 55%, #343f42 55%); color: #93a4a6; border-color: var(--line); border-bottom-color: #1a2224; opacity: .7; }
button.credit { background: linear-gradient(160deg, #47397a 0 55%, #3b2f63 55%); border-color: #7a63d6; border-bottom-color: #2a2148; }
button.danger { border-color: #9a4f38; color: #ffc9b6; }
button.small { padding: 0 10px; font-weight: 500; }
button.on { outline: 2px solid #fff; outline-offset: 1px; }
button:focus-visible, input:focus-visible, select:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
input, select {
  min-height: 44px; min-width: 0; font: inherit; font-size: 16px; background: #10151a; color: var(--text);
  border: 1px solid var(--line); border-radius: 8px; padding: 0 10px;
}
input[type=number] { width: 84px; }
input.tag-input { width: 84px; text-transform: uppercase; }

.dim { color: var(--dim); }
.small-text { font-size: 13px; }
.big { font-size: 18px; }
.err-text { color: #ffb39c; min-height: 1.4em; }
.short { color: #ffcf9c; }
.rel-self { color: var(--self); } .rel-ally { color: #8db9f2; } .rel-enemy { color: #ff9d7c; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.grow { flex: 1 1 auto; min-width: 0; }
.right { text-align: right; }
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 8px; }
/* PUBLIC-STRIPE 15/11: the credit bundles sit as an even 2-column grid (3 on a wide screen), price on the right of each */
.store-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.store-row > button { justify-content: flex-start; white-space: nowrap; gap: 5px; padding-left: 10px; padding-right: 10px; }
.store-row > button > :first-child { display: none; }  /* the + glyph: the price says it is a purchase */
.store-row .price { margin-left: auto; font-variant-numeric: tabular-nums; }
.card { background: linear-gradient(155deg, #29343a 0 38%, var(--card) 38%); border: 1px solid #2f3b3f; border-radius: 4px; padding: 10px 12px; margin: 8px 0; }
.card.hostile { border-color: var(--enemy); }
.card.objective { border-color: #6f5a1f; }
.cd { font-variant-numeric: tabular-nums; font-weight: 700; }
.lvl { font-weight: 700; background: #10151a; border-radius: 6px; padding: 2px 8px; margin-left: auto; }
.state, .role { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; background: #10151a; border-radius: 6px; padding: 2px 6px; }
.role.leader { color: var(--gold); } .role.officer { color: #8db9f2; }
.bar { height: 8px; background: #10151a; border-radius: 4px; overflow: hidden; margin-top: 8px; }
.bar i { display: block; height: 100%; background: var(--self); }
.swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; flex: none; background: var(--neutral); }
.swatch.food { background: var(--food); } .swatch.steel { background: var(--steel); }
.swatch.credits { background: var(--credits); border-radius: 50%; } .swatch.objective { background: var(--gold); }
.credit-text { color: #cdbcff; font-weight: 700; }
/* Troop types differ by shape first: block, wedge, tall bar. */
.glyph { width: 16px; height: 16px; background: #d5dbd3; flex: none; }
.glyph.strider { clip-path: polygon(0 100%, 100% 50%, 0 0); }
.glyph.slinger { width: 6px; height: 18px; }
.notice { padding: 8px 12px; border-radius: 8px; margin: 6px 10px; font-size: 14px; }
.notice.warn { background: #45370f; border: 1px solid #b08a2a; color: #ffe9ad; }
.notice.danger { background: #47211a; border: 1px solid var(--enemy); color: #ffd3c6; }
.view > .notice { margin: 8px 0; }
ul.plain { list-style: none; padding: 0; margin: 0; }
ul.plain li { padding: 7px 0; border-bottom: 1px solid #2a3437; font-size: 13px; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 13px; }
.legend-item { display: inline-flex; gap: 6px; align-items: center; }
table.grid { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 4px; }
table.grid td, table.grid th { text-align: right; padding: 2px 4px; font-weight: 400; }
table.grid th { color: var(--dim); }
table.grid td:first-child, table.grid th:first-child { text-align: left; }
.part { border-top: 1px solid #323d40; margin-top: 8px; padding-top: 6px; }
button.report { display: block; width: 100%; text-align: left; font-weight: 400; padding: 10px 12px; }
button.report.victory { border-color: #1f7f70; border-bottom-color: #14564c; } button.report.defeat { border-color: #9a4f38; border-bottom-color: #6b3524; }
/* A notice line is a div, not a button: it needs the same red edge (shield ended, rally that did not leave). */
.report.note.defeat { border-color: #9a4f38; border-bottom-color: #6b3524; }
button.report > span { display: flex; } button.report > span.small-text { display: block; margin-top: 2px; }
button.report .dim { margin-left: auto; }
strong.victory { color: var(--self); } strong.defeat { color: #ff9d7c; }
.wounded { border-top: 1px solid #323d40; padding-top: 8px; }
.heal-range { flex: 1 1 90px; min-width: 70px; height: 32px; accent-color: #39b8a6; touch-action: pan-y; }
.heal-live { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pick-row { display: flex; gap: 8px; align-items: center; margin: 6px 0; }
.pick-name { flex: 1 1 auto; min-width: 0; }
.chat-log { height: 200px; overflow-y: auto; background: #10151a; border-radius: 8px; padding: 8px 10px; }
.msg { margin: 3px 0; } .msg.mine strong { color: var(--self); }
.chat-form { display: flex; gap: 8px; margin-top: 8px; } .chat-form input { flex: 1 1 auto; }
.form input[type=text] { flex: 1 1 140px; }

.center-card { max-width: 420px; margin: 0 auto; padding: 10vh 20px 20px; display: flex; flex-direction: column; gap: 10px; }
.center-card.login { padding-top: 0; max-width: 520px; max-height: 100vh; max-height: 100dvh; overflow-y: auto; }
.key-art { margin: 0 -20px 6px; aspect-ratio: 16 / 8; background: #0d1112 url(assets/backdrop-key-m.webp) center / cover; -webkit-mask-image: linear-gradient(#000 70%, transparent); mask-image: linear-gradient(#000 70%, transparent); }
.login h1 { margin-top: -34px; position: relative; font-size: 44px; text-shadow: 0 2px 0 #0b0f10; }
@media (orientation: landscape) and (max-height: 520px) { .key-art { aspect-ratio: 16 / 3.2; } }
.tagline { color: var(--dim); margin: 0; }
.login-btns { margin-top: 10px; } .login-btns button { flex: 1; }
.google-btn { display: flex; justify-content: center; min-height: 44px; margin: 8px 0 2px; }
.google-btn[hidden] { display: none; }
#login input[type=password] { width: 100%; }
.dev-label { background: #45370f; border: 1px solid #b08a2a; color: #ffe9ad; border-radius: 8px; padding: 8px 12px; }

/* ---- layout of record: phone portrait. top / notices / map (World tab only) / panel / tab bar */
#app {
  height: 100vh; height: 100dvh; display: grid; grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto minmax(0, 1fr) auto auto;
  grid-template-areas: "top" "notices" "map" "panel" "tabs" "dock";
}
#top { grid-area: top; background: var(--panel); padding: 4px 10px 6px; border-bottom: 1px solid var(--line); padding-top: max(4px, env(safe-area-inset-top)); }
.who { display: flex; align-items: center; gap: 8px; }
.who strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.who #logout { margin-left: auto; flex: none; }
.who .dim { white-space: nowrap; font-size: 13px; }
.res-bar { display: flex; gap: 6px; margin-top: 4px; }
/* header chips take the width of what they hold (12/11: three equal thirds cut the food chip, "+888/h" read "+888/l" at
   390 px) and share what is left; a rate is never cut: if the row ever runs short, the stock number is the part that yields */
.chip { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 5px; background: #10151a; border-radius: 4px; padding: 4px 8px; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; }
.chip-label { color: var(--dim); font-size: 12px; }
.chip b { margin-left: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.chip small { flex: none; }
.chip .ico { flex: none; }
#notices { grid-area: notices; }
#panel { grid-area: panel; overflow-y: auto; overflow-x: hidden; padding: 8px 12px 28px; -webkit-overflow-scrolling: touch; }
#map-pane { grid-area: map; position: relative; display: flex; flex-direction: column; background: #101413; overflow: hidden; height: 50vh; height: 50dvh; min-height: 200px; border-bottom: 1px solid var(--line); }
body:not([data-tab="world"]) #map-pane { display: none; }
#map { display: block; width: 100%; flex: 1 1 0; min-height: 0; touch-action: none; cursor: grab; }
/* Phones: the zoom buttons sit in their own strip so they never cover a tile. */
.map-btns { display: flex; gap: 6px; padding: 4px 8px; justify-content: flex-end; background: var(--panel); }
.map-btns button { padding: 0; width: 56px; }
#tabs { grid-area: tabs; display: flex; background: var(--panel); border-top: 1px solid var(--line); padding-bottom: env(safe-area-inset-bottom); }
#tabs button {
  flex: 1 1 0; min-width: 0; min-height: 52px; padding: 0 2px; border: 0; border-radius: 0; background: transparent;
  font-size: 12px; font-weight: 500; color: var(--dim); position: relative; border-top: 3px solid transparent;
}
#tabs button[aria-current="page"] { color: #fff; font-weight: 700; border-top-color: var(--self); background: #232c30; }
.badge { position: absolute; top: 1px; right: 2px; min-width: 16px; height: 16px; line-height: 16px; border-radius: 8px; background: var(--enemy); color: #fff; font-size: 11px; font-weight: 700; padding: 0 4px; }

.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; background: #20282c; border-top: 3px solid var(--gold);
  border-radius: 14px 14px 0 0; padding: 10px 14px calc(14px + env(safe-area-inset-bottom)); max-height: 60vh; max-height: 60dvh;
  overflow-y: auto; box-shadow: 0 -8px 30px rgba(0, 0, 0, .55);
}
.sheet-head { justify-content: space-between; flex-wrap: nowrap; }
.sheet-head strong { font-size: 17px; }
.sheet-info > div { margin: 3px 0; }
/* a resource site: what is left / full stands beside its glyph in the title line; the troops sent stand at the right of the action line */
.sheet-head .node-left { font-size: 16px; font-weight: 700; margin-left: 2px; font-variant-numeric: tabular-nums; }
.sheet-est { margin-left: auto; } .sheet-est > p { margin: 0; }
#toasts { position: fixed; left: 50%; transform: translateX(-50%); top: 8px; z-index: 50; display: flex; flex-direction: column; gap: 6px; width: min(92vw, 420px); align-items: center; pointer-events: none; }
.toast { background: #1f3040; border: 1px solid var(--ally); padding: 6px 12px; border-radius: 16px; max-width: 100%; font-size: 13px; line-height: 1.25; pointer-events: auto; cursor: pointer; box-shadow: 0 4px 16px rgba(0, 0, 0, .5); }
.toast.err { background: #47211a; border-color: var(--enemy); }
.toast.warn { background: #45370f; border-color: #b08a2a; color: #ffe9ad; }

/* ---- phone landscape: tab rail on the left, panel and map side by side */
@media (orientation: landscape) and (max-height: 520px) {
  #app {
    grid-template-columns: 84px minmax(0, 1fr) minmax(0, 1fr); grid-template-rows: auto auto minmax(0, 1fr) auto;
    grid-template-areas: "tabs top top" "tabs notices notices" "tabs panel map" "tabs dock map";
  }
  body:not([data-tab="world"]):not([data-tab="city"]) #app { grid-template-areas: "tabs top top" "tabs notices notices" "tabs panel panel" "tabs dock dock"; }
  .dock { padding: 3px 8px; } .sq-face { width: 40px; height: 40px; }
  #city-pane { height: auto; min-height: 0; border-bottom: 0; border-left: 1px solid var(--line); }
  #top { display: flex; align-items: center; gap: 10px; padding: 2px 10px; }
  .who { flex: 0 1 auto; min-width: 0; } .who #logout { margin-left: 0; }
  .res-bar { flex: 1 1 auto; margin-top: 0; }
  #tabs { flex-direction: column; border-top: 0; border-right: 1px solid var(--line); padding-bottom: 0; padding-left: env(safe-area-inset-left); overflow-y: auto; }
  #tabs button { min-height: 44px; flex: 1 0 44px; border-top: 0; border-left: 3px solid transparent; font-size: 13px; }
  #tabs button[aria-current="page"] { border-left-color: var(--self); }
  #map-pane { height: auto; min-height: 0; border-bottom: 0; border-left: 1px solid var(--line); }
  .sheet { left: 84px; right: auto; top: 0; bottom: 0; width: min(430px, 52vw); max-height: none; border-radius: 0; border-top: 0; border-right: 3px solid var(--gold); }
  .chat-log { height: 130px; }
}

/* ---- desktop: panel on the left, map always visible on the right (same features, more columns) */
@media (min-width: 900px) and (min-height: 521px) {
  #app {
    grid-template-columns: 460px minmax(0, 1fr); grid-template-rows: auto auto auto minmax(0, 1fr) auto;
    grid-template-areas: "top map" "notices map" "tabs map" "panel map" "dock map";
  }
  body #app #map-pane { display: flex; height: auto; border-bottom: 0; border-left: 1px solid var(--line); }
  body[data-tab="city"] #app #map-pane { display: none; }
  body[data-tab="city"] #app #city-pane { display: flex; height: auto; border-bottom: 0; border-left: 1px solid var(--line); }
  .city-bar { position: absolute; left: 0; top: 0; right: 64px; border-right: 2px solid var(--gold); border-radius: 0 0 8px 0; }
  .map-btns { position: absolute; right: 8px; top: 8px; flex-direction: column; padding: 0; background: none; }
  .map-btns button { width: 44px; background: rgba(27, 34, 37, .92); }
  #tabs { border-top: 0; border-bottom: 1px solid var(--line); }
  #tabs button { border-top: 0; border-bottom: 3px solid transparent; font-size: 13px; min-height: 46px; }
  #tabs button[aria-current="page"] { border-bottom-color: var(--self); }
  .sheet { left: auto; right: 70px; bottom: 16px; width: 400px; max-height: 80vh; border: 2px solid var(--gold); border-radius: 14px; }
}

/* ---- art (assets/ are generated at development time; sizes are fixed so nothing shifts while they load) */
.ico { width: 20px; height: 20px; object-fit: contain; flex: none; }
.face { width: 34px; height: 34px; border-radius: 4px; object-fit: cover; flex: none; border: 1px solid #55666a; background: #10151a; }
.face.big { width: 52px; height: 52px; }
.face.tiny { width: 22px; height: 22px; vertical-align: middle; }
.part-cmd { gap: 3px; margin-right: 6px; }
.with-thumb { display: grid; grid-template-columns: 64px minmax(0, 1fr); column-gap: 12px; align-items: start; }
.with-thumb > .thumb { grid-row: 1 / span 6; width: 64px; height: 64px; object-fit: contain; align-self: center; filter: drop-shadow(0 3px 0 rgba(0, 0, 0, .35)); }
.with-thumb > .thumb.tall { height: 84px; }
.with-thumb > :not(.thumb) { grid-column: 2; }
.troop.locked > .thumb { filter: grayscale(1) brightness(.6); }
.spire-thumb { width: 26px; height: 40px; object-fit: contain; flex: none; }
.hero {
  position: relative; display: grid; --hero-w: clamp(140px, 44%, 230px); grid-template-columns: var(--hero-w) minmax(0, 1fr); gap: 0 12px; min-height: 156px; margin: 4px 0 8px;
  border: 1px solid #3a4747; border-radius: 4px; overflow: hidden;
  background: linear-gradient(90deg, rgba(20, 24, 26, .25), rgba(20, 24, 26, .9) 62%), #1b2225 url(assets/backdrop-city-m.webp) left center / cover;
}
.hero-portrait { width: 116px; height: 100%; min-height: 156px; object-fit: cover; object-position: 50% 12%; display: block; }
.hero-text { display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px; padding: 8px 10px 8px 0; min-width: 0; }
.eyebrow { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--gold); }
.hero-name { font-size: 28px; line-height: 1; }
.hero-title { font-size: 13px; color: var(--dim); }
.hero-line { font-size: 13px; margin: 2px 0 6px; }
.cmd-pick { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-bottom: 8px; }
.cmd-pick p { grid-column: 1 / -1; margin: 0; }
button.cmd-opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; min-height: 66px; text-align: left; font-weight: 400; }
button.cmd-opt span { display: flex; flex-direction: column; min-width: 0; }
#top { background: linear-gradient(170deg, #222b2f 0 50%, var(--panel) 50%); }
#tabs button[aria-current="page"] { background: linear-gradient(170deg, #2a363b 0 50%, #232c30 50%); }

/* the panel enters from the side of the tab you went to; one short pass, then still */
@keyframes enter-r { from { transform: translateX(18px); opacity: 0; } }
@keyframes enter-l { from { transform: translateX(-18px); opacity: 0; } }
.view.enter-r { animation: enter-r .16s ease-out; } .view.enter-l { animation: enter-l .16s ease-out; }
@media (prefers-reduced-motion: reduce) { .view.enter-r, .view.enter-l { animation: none; } button:not(:disabled):active { transform: none; } }

.hero-bonus, .cmd-bonus { color: var(--self); font-weight: 600; }
.hero-bonus { font-size: 13px; margin-bottom: 6px; }
button.cmd-opt:disabled { opacity: .45; }
/* phone portrait: the key art takes the free height, the form sits in the thumb zone */
@media (orientation: portrait) and (max-width: 560px) {
  .center-card.login { height: 100vh; height: 100dvh; padding-bottom: max(20px, env(safe-area-inset-bottom)); }
  .key-art { flex: 1 1 0; aspect-ratio: auto; min-height: 180px; background-position: 50% 30%; }
}

/* ---- city scene (own city or a read-only visit): same slot as the world map, never both at once */
#city-pane { grid-area: map; position: relative; display: flex; flex-direction: column; background: #101413; overflow: hidden; height: 48vh; height: 48dvh; min-height: 220px; border-bottom: 1px solid var(--line); }
body:not([data-tab="city"]) #city-pane { display: none; }
#city { display: block; width: 100%; flex: 1 1 0; min-height: 0; touch-action: none; cursor: grab; }
.city-bar { flex: none; z-index: 4; background: rgba(27, 34, 37, .94); border-bottom: 2px solid var(--gold); padding: 6px 10px 8px; }
.city-bar .btn-row { margin-top: 6px; }
.city-bar-text { display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
/* edit mode with nothing picked: three glyphs on one row. While a preview is up the two-line layout stays: its height must not change under the finger. */
.city-bar.one-row:not([hidden]) { display: flex; align-items: center; gap: 12px; }
.city-bar.one-row .btn-row { margin-top: 0; margin-left: auto; }
.city-bar.one-row .city-bar-text { flex-direction: row; align-items: center; gap: 12px; }
/* CITY-B step 2: the two turn glyphs beside the building preview (the scene sets left / top to their centre on every draw) */
#city-pane .city-rot { position: absolute; z-index: 4; width: 46px; height: 46px; min-height: 46px; min-width: 46px; padding: 0; margin: 0; transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(20, 26, 28, .92); border: 2px solid var(--gold);
  color: #ffe08a; box-shadow: 0 3px 12px rgba(0, 0, 0, .55); touch-action: manipulation; }
#city-pane .city-rot svg { width: 26px; height: 26px; }
#city-pane .city-rot:active { background: #3a3220; }
#city-pane .city-rot[hidden] { display: none; }
/* repair 13/11: on a phone the two turn buttons sit in the bar beside Confirm and Cancel (nothing rides on the small scene) */
#city-pane .city-rot.in-bar { position: static; transform: none; flex: none; }
@media (orientation: portrait) and (max-width: 560px) {
  /* while placing or moving, the scene takes the room of the panel under it: the preview is framed bigger (cityscene aimAtGhost) */
  #city-pane.editing { height: min(66vh, calc(100vw + 150px)); height: min(66dvh, calc(100vw + 150px)); }
}
@media (orientation: landscape) and (max-height: 520px) {
  /* on its side the bar is one row (text left, buttons right) so the scene keeps its height */
  /* (a placement's Confirm carries its price and is wider: then the buttons wrap under the text instead of squeezing it) */
  #city-pane .city-bar:not(.one-row):not([hidden]) { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px 8px; }
  #city-pane .city-bar:not(.one-row) .btn-row { margin-top: 0; margin-left: auto; flex: 0 0 auto; flex-wrap: nowrap; }
  #city-pane .city-bar:not(.one-row) .city-bar-text { flex: 1 1 140px; min-width: 140px; }
}
.city-ctx { position: absolute; z-index: 6; width: 166px; display: flex; flex-direction: column; gap: 6px; padding: 8px; background: #20282c; border: 2px solid var(--gold); border-radius: 8px; box-shadow: 0 6px 24px rgba(0, 0, 0, .6); }
.ok-text { color: #7fe6d4; } .bad-text { color: #ffb39c; font-weight: 600; }
.city-head h2 { margin: 0; }
.city-head { margin: 2px 0 6px; flex-wrap: nowrap; }
.strip { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
button.strip-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; width: 62px; min-height: 62px; padding: 4px 2px; font-size: 11px; font-weight: 500; }
.strip-thumb { width: 38px; height: 34px; object-fit: contain; }
.card.built { opacity: .6; }
.card.visit { border-color: var(--gold); }
/* buildings without generated art: a flat block in the building's colour (missing art is listed in A-FAIRE.md) */
.noart { display: block; border-radius: 4px; border: 2px solid #12181a; background: #9aa0a0; position: relative; }
.thumb.noart { width: 56px; height: 48px; margin: 8px 4px; }
.strip-thumb.noart { width: 30px; height: 26px; margin: 4px; }
.noart.lab { background: linear-gradient(160deg, #9c88f0 0 50%, #7a63d6 50%); }
.noart.hospital { background: linear-gradient(#c0392b, #c0392b) center / 26% 70% no-repeat, linear-gradient(#c0392b, #c0392b) center / 70% 26% no-repeat, #d9dedb; }
.noart.alliance_hall { background: linear-gradient(160deg, #6aa5ee 0 50%, #4d8fe0 50%); }

/* ---- Icon vocabulary (js/icons.js). A glyph replaces a label; the words live in aria-label and in the tip bubble. */
.gi { width: 1.15em; height: 1.15em; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -.2em; }
.stat { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.stat .ico, .cost .ico { width: 16px; height: 16px; }
.stat .glyph { transform: scale(.75); }
.stats { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; font-size: 13px; margin-top: 4px; }
.stats.big-stats { font-size: 15px; gap: 4px 18px; }
.stats.next { color: var(--gold); }
.cost { display: inline-flex; gap: 8px; align-items: center; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cost-part { display: inline-flex; gap: 3px; align-items: center; }
.cost.short { color: #ffcf9c; }
p.stat { display: flex; }
button.ib { padding: 0; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
button.ib.with-text { padding: 0 12px; }
button.ib .gi { width: 20px; height: 20px; }
button.ib .ico { width: 18px; height: 18px; }
button.ib .cost { gap: 6px; }
.map-btns button.ib { min-width: 0; }
#tip { position: fixed; z-index: 80; max-width: min(280px, 92vw); background: #0b0f12; color: #fff; border: 1px solid var(--gold); border-radius: 4px; padding: 6px 9px; font-size: 13px; line-height: 1.3; pointer-events: none; white-space: normal; }
#tabs button { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; font-size: 10px; letter-spacing: .02em; }
#tabs .tab-ico { width: 26px; height: 26px; }
#tabs button { min-height: 46px; }
#who-power { display: inline-flex; align-items: center; gap: 4px; }
.pick-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.city-ctx .btn-row { margin-top: 0; flex-wrap: nowrap; }
.city-ctx .btn-row button { flex: 1 1 0; }
@media (min-width: 900px) and (min-height: 521px), (orientation: landscape) and (max-height: 520px) { #tabs button { font-size: 10px; } }
.stats.none { display: none; }

/* ---- help folded into a glyph, legend marks, small building thumbs, notices away from the header */
.hint { color: var(--dim, #9aa7a3); cursor: help; }
.hint .gi { width: 18px; height: 18px; }
.legend-item { cursor: help; }
.legend-item .gi, .legend-item .ico { width: 16px; height: 16px; }
.swatch.ring { box-shadow: 0 0 0 2px #fff; border-radius: 50%; }
.swatch.tri { clip-path: polygon(50% 0, 100% 100%, 0 100%); border-radius: 0; }
.mini-thumb { width: 24px; height: 22px; object-fit: contain; }
.mini-thumb.noart { width: 18px; height: 16px; margin: 3px; border-width: 1px; }
.unlocks .stat.dim .mini-thumb { filter: grayscale(1) brightness(.7); }
#toasts { top: auto; bottom: calc(72px + env(safe-area-inset-bottom)); }
@media (min-width: 900px) and (min-height: 521px), (orientation: landscape) and (max-height: 520px) { #toasts { bottom: 16px; } }

/* ---- my shield in the header: dome + time left, a tap opens the Shop */
/* the button is a 44 px touch target; the visible pill inside stays small so the header keeps its height */
.shield-chip, .event-chip { display: inline-flex; align-items: center; flex: none; min-height: 44px; margin: -11px 0; padding: 0 2px; background: none; border: 0; box-shadow: none; }
.shield-chip .pill, .event-chip .pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 7px; font-size: 12px; font-variant-numeric: tabular-nums; border-radius: 999px; white-space: nowrap; }
.shield-chip .pill { color: #bfe9ff; background: #12303d; border: 1px solid #3f8fb0; }
.shield-chip .gi, .event-chip .gi { width: 14px; height: 14px; }
/* ---- themed event: header chip (same shape) + card on the Alliance tab */
.event-chip .pill { color: #f3dc9a; background: #33290f; border: 1px solid #8a6f24; }
.event-chip.ready .pill { color: #1d1603; background: var(--gold); border-color: #fff0b8; }
.card.event { border-color: #6f5a1f; }
.ev-strip { display: flex; gap: 6px; align-items: stretch; }
.ev-day { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 5px 9px; border: 1px solid var(--line); border-radius: 4px; color: var(--dim); }
.ev-day .gi { width: 18px; height: 18px; }
.ev-day.now { flex: 1; justify-content: flex-start; color: #1d1603; background: var(--gold); border-color: transparent; font-weight: 700; font-variant-numeric: tabular-nums; }
.ev-day.now .cd { margin-left: auto; }
.ev-mine { margin-top: 10px; }
.ev-mine .big { font-size: 20px; font-weight: 700; color: var(--gold); }
.ev-mine .big .gi { width: 20px; height: 20px; }
.ev-bar i { background: var(--gold); }
button.chest { flex: 1 1 0; justify-content: center; min-width: 0; padding-left: 4px; padding-right: 4px; gap: 4px; white-space: nowrap; overflow-wrap: normal; font-variant-numeric: tabular-nums; }
button.chest .gi { flex: none; width: 16px; height: 16px; }
button.chest { font-size: 13px; }
button.chest .chest-gain { font-size: 11px; }
button.chest:disabled[data-state="claimed"] { color: #7fe6d4; }
.chest-gain { display: inline-flex; align-items: center; gap: 1px; margin-left: 2px; font-size: 12px; opacity: .85; white-space: nowrap; }
.chest-gain .ico { width: 13px; height: 13px; }
.ev-prev { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line); }
.ev-prev button.chest { flex: 0 1 auto; }
.ev-rank { margin-top: 10px; border-top: 1px solid var(--line); }
.ev-row { padding: 5px 0; border-bottom: 1px solid #2b3538; flex-wrap: nowrap; }
.ev-row.mine { color: var(--gold); }
.ev-row .dim { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ev-pos { width: 1.4em; text-align: right; color: var(--dim); font-variant-numeric: tabular-nums; }
/* narrow phone: the chip keeps theme + points; the time left is on the card (with a shield chip beside it the name was crushed) */
@media (max-width: 520px) { .event-chip .cd { display: none; } }

/* commander level and experience */
.hero-level { margin: 2px 0 0; }
.stat.lvl { color: #f2c94c; font-weight: 700; }
.bar.xp { height: 5px; margin: 4px 0 6px; width: 220px; max-width: 100%; flex: none; }
.bar.xp i { background: #f2c94c; }
button:disabled.credit { opacity: .45; }

/* rarity, skills, medals, free draw */
.gem { display: inline-flex; vertical-align: -3px; margin-right: 4px; }
.gem .gi { width: 14px; height: 14px; fill: currentColor; fill-opacity: .35; }
.eyebrow { display: inline-flex; align-items: center; }
.card.skills { padding: 4px 10px; }
.skill { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px; min-height: 48px; border-top: 1px solid #2f3b3f; }
.skill:first-child { border-top: 0; }
.skill.off .skill-fx { color: var(--dim); }
.skill-fx { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 10px; font-weight: 600; color: var(--self); }
.skill-fx .stats.next { margin: 0; font-weight: 400; gap: 4px; }
.skill-fx .stats.next .gi { width: 12px; height: 12px; }
.pips { display: inline-flex; gap: 3px; }
.pips i { width: 7px; height: 12px; background: #36444a; transform: skewX(-14deg); }
.pips i.on { background: #f2c94c; }
.skill button.ib { min-height: 44px; gap: 8px; }
.skill { min-height: 52px; }
.stat.medal { font-weight: 700; }
.stat.medal .gi { width: 18px; height: 18px; }
.medal-row, .draw-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.draw-row { margin-top: 8px; padding-top: 8px; border-top: 1px solid #2f3b3f; gap: 6px 12px; }
button.ib.xchg { min-width: 44px; min-height: 44px; padding: 0 5px; font-size: 11px; gap: 2px; color: var(--dim); }
.medal-row { gap: 6px 5px; }
button.ib.xchg .gi { width: 12px; height: 12px; }
.stats.odds { margin: 0; gap: 4px 10px; font-size: 12px; }
.stats.odds .gi { width: 12px; height: 12px; fill: currentColor; fill-opacity: .35; }
button.cmd-opt strong { display: flex; align-items: center; }
button.cmd-opt strong .gem { flex-direction: row; margin-right: 4px; }
.report.note .dim { margin-left: auto; }
/* Reports in glyphs: the troop glyph needs a box inside a table cell; the second line of a report keeps its gaps. */
td > .glyph { display: inline-block; vertical-align: middle; }
button.report > span.small-text.stats { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin-top: 4px; }

.none .stat { opacity: .75; }
h3.sec, h2.sec { display: flex; align-items: center; gap: 6px; }
h3.sec .gi { width: 18px; height: 18px; }
h2.sec .gi { width: 24px; height: 24px; }
.sec-row { align-items: center; gap: 10px; margin: 16px 0 6px; }
.sec-row h3 { margin: 0; }
.state.stat { padding: 2px 5px; }
.sheet-head strong .gi, .sheet-head strong .ico { width: 22px; height: 22px; }
.role.stat { display: inline-flex; align-items: center; }

/* skills of any commander: one face per commander above the skills card */
.skill-tabs { display: flex; gap: 6px; margin: 8px 0 4px; }
button.skill-tab { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 4px 6px; min-width: 48px; min-height: 48px; opacity: .55; background: transparent; }
button.skill-tab.on { opacity: 1; background: #1c262b; box-shadow: inset 0 -3px 0 currentColor; }
button.skill-tab .stat { font-size: 12px; }
.stat.pair { gap: 6px; white-space: nowrap; }

/* commander not recruited yet: face kept, greyed, with a lock */
.cmd-opt.locked, .skill-tab.locked { filter: grayscale(1); opacity: .5; }
.cmd-opt.locked { cursor: default; }
.draw-row .sure { color: var(--gold); }

/* ---- narrow phone header: the alliance tag sits under the name, so the name is not cut by the shield and event chips */
.who strong .nm { overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 520px) {
  .who strong { display: flex; flex-direction: column; line-height: 1.15; }
  .who strong .tg { font-size: 11px; font-weight: 600; color: var(--dim); }
  .who { gap: 6px; }
}


/* ---- squads dock + one menu button (bottom right), chat toggle (centre of the left edge) — 11/11 */
.dock { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: var(--panel); border-top: 1px solid var(--line); }
.squads { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.squads::-webkit-scrollbar { display: none; }
.sq-face { position: relative; flex: none; width: 50px; height: 50px; min-width: 0; min-height: 0; padding: 0; border-radius: 50%;
  border: 2px solid var(--rar, var(--line)); background: #10151a; overflow: visible; display: inline-flex; align-items: center; justify-content: center;
  -webkit-touch-callout: none; user-select: none; touch-action: manipulation; }
.sq-face.small { width: 40px; height: 40px; }
.sq-img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; pointer-events: none; }
.sq-lv { position: absolute; left: -3px; bottom: -3px; min-width: 18px; height: 18px; line-height: 18px; border-radius: 9px; background: #10151a;
  border: 1px solid var(--gold); color: var(--gold); font-size: 11px; font-weight: 700; text-align: center; padding: 0 3px; }
.sq-out { position: absolute; right: -4px; top: -4px; width: 20px; height: 20px; border-radius: 50%; background: var(--self); color: #06110f;
  display: flex; align-items: center; justify-content: center; } .sq-out svg { width: 13px; height: 13px; }
.sq-out.post { background: var(--ally); color: #fff; }
.sq-add { border-style: dashed; border-color: var(--gold); color: var(--gold); background: rgba(224, 178, 58, .08); }
.sq-add svg { width: 24px; height: 24px; }
.sq-lock { color: var(--dim); border-style: dotted; opacity: .7; }
.menu-btn { position: relative; flex: none; width: 56px; height: 50px; min-width: 0; padding: 0; display: none; align-items: center; justify-content: center; gap: 0;
  border-color: var(--self); }
.menu-btn .tab-ico { width: 24px; height: 24px; }
.menu-btn .menu-lines svg { width: 12px; height: 12px; opacity: .7; }
.sq-menu { position: fixed; z-index: 40; min-width: 200px; background: #20282c; border: 1px solid var(--gold); border-radius: 8px; padding: 8px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .6); }
.sq-menu.sq-release-menu { min-width: 0; padding: 0; background: transparent; border: 0; box-shadow: none; }
.sq-release { width: 42px; min-width: 42px; height: 42px; min-height: 42px; padding: 0; border-radius: 50%; background: #20282c; box-shadow: 0 3px 14px rgba(0, 0, 0, .7); }
.sq-menu-head { gap: 8px; margin-bottom: 6px; } .sq-menu-head .face { width: 32px; height: 32px; border-radius: 50%; }
.sq-menu-list { display: flex; flex-direction: column; gap: 4px; }
.sq-item { display: flex; align-items: center; gap: 10px; justify-content: flex-start; min-height: 42px; font-weight: 500; text-align: left; }
.sq-item svg { width: 20px; height: 20px; flex: none; }
.sq-sec { display: flex; align-items: center; gap: 6px; margin: 10px 0 4px; color: var(--dim); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }
.sq-sec svg { width: 16px; height: 16px; }
.sq-pick, .sq-choose { display: flex; gap: 6px; flex-wrap: wrap; }
.sq-cand { display: flex; flex-direction: column; align-items: center; gap: 2px; min-height: 0; padding: 6px; width: 72px; font-size: 11px; font-weight: 500; }
.sq-cand.small { width: 60px; padding: 4px; }
.sq-cand .face { width: 40px; height: 40px; border-radius: 50%; }
.sq-cand.on { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold) inset; }
.sq-cand.locked { opacity: .45; }
.sq-cand-tag { font-size: 10px; color: var(--gold); } .sq-cand-tag svg { width: 12px; height: 12px; }
.sq-trow { justify-content: space-between; }
#squad-sheet select, #squad-sheet input[type=number] { width: 100%; }
#squad-sheet .pick-row input[type=number] { width: 72px; }
.occupants { gap: 6px; margin: 6px 0; flex-wrap: wrap; }
.talk-btn { position: fixed; z-index: 30; left: 0; top: calc(50% - 28px); transform: none !important; margin: 0 !important; width: 34px; height: 56px; min-width: 0; padding: 0;
  border-radius: 0 10px 10px 0; border-left: 0; padding-left: env(safe-area-inset-left); display: flex; align-items: center; justify-content: center; opacity: .92; }
.talk-btn svg { width: 22px; height: 22px; }
.talk-btn.on { border-color: var(--self); color: var(--self); }
.talk-btn .badge { top: 2px; right: 2px; }
.talk-box { position: fixed; z-index: 29; left: calc(46px + env(safe-area-inset-left)); top: 50%; transform: translateY(-50%); width: min(78vw, 360px);
  height: min(52vh, 440px); height: min(52dvh, 440px); display: flex; flex-direction: column; gap: 6px; background: rgba(27, 34, 37, .97);
  border: 1px solid var(--line); border-radius: 10px; padding: 8px; box-shadow: 0 8px 30px rgba(0, 0, 0, .6); }
.talk-box[hidden] { display: none; }
.talk-head { justify-content: space-between; flex-wrap: nowrap; }
.talk-tabs { display: flex; gap: 4px; }
.talk-tab { min-width: 44px; padding: 0 8px; } .talk-tab.on { border-color: var(--self); color: var(--self); }
.talk-log { flex: 1 1 auto; min-height: 0; overflow-y: auto; max-height: none; }
.talk-name { display: inline; min-height: 0; min-width: 0; padding: 0; border: 0; background: none; color: var(--ally); font-weight: 700; cursor: pointer; }
.msg.mine .talk-name { color: var(--self); }
.talk-peer { display: flex; align-items: center; gap: 8px; width: 100%; margin: 3px 0; justify-content: flex-start; }
.talk-box .chat-form { display: flex; gap: 6px; } .talk-box .chat-form input { flex: 1 1 auto; min-width: 0; }

/* wide / landscape: the tab bar stays as it is; the dock is the last row under the panel */
#dock { grid-area: dock; min-width: 0; }
/* phone portrait: the six areas fold into the menu button; the bottom row = squads + menu */
@media (orientation: portrait) and (max-width: 560px) {
  #app { grid-template-areas: "top" "notices" "map" "panel" "dock"; }
  #app { grid-template-rows: auto auto auto minmax(0, 1fr) auto; }
  #dock { padding-bottom: max(6px, env(safe-area-inset-bottom)); }
  .menu-btn { display: inline-flex; }
  #tabs { position: fixed; z-index: 35; right: 8px; bottom: calc(70px + env(safe-area-inset-bottom)); flex-direction: column; width: 76px;
    border: 1px solid var(--self); border-radius: 12px; padding: 4px 0; box-shadow: 0 8px 30px rgba(0, 0, 0, .6); display: none; }
  body.menu-open #tabs { display: flex; }
  #tabs button { flex: none; min-height: 52px; border-top: 0; border-left: 3px solid transparent; }
  #tabs button[aria-current="page"] { border-left-color: var(--self); }
  .sheet { bottom: 0; z-index: 36; }
}
@media (orientation: landscape) and (max-height: 520px) {
  .talk-btn { left: calc(84px + env(safe-area-inset-left)); } .talk-box { left: calc(130px + env(safe-area-inset-left)); }
}

/* ---- UX pass 11/11 14h: the chat glyph never covers a card or a sheet */
/* the panel keeps a left gutter as wide as the glyph (34 px + air): no button of a card sits under it, on any tab or size */
#panel { padding-left: 42px; }
.talk-box { left: calc(38px + env(safe-area-inset-left)); }
@media (orientation: landscape) and (max-height: 520px) { .talk-box { left: calc(122px + env(safe-area-inset-left)); } }
/* phone: an open sheet (tile, squad) takes the screen; the glyph and the chat step aside until it closes (their state is kept) */
@media (orientation: portrait) and (max-width: 560px), (orientation: landscape) and (max-height: 520px) {
  body:has(#sheet:not([hidden])) .talk-btn, body:has(#squad-sheet:not([hidden])) .talk-btn,
  body:has(#sheet:not([hidden])) .talk-box, body:has(#squad-sheet:not([hidden])) .talk-box { display: none; }
}
/* phone portrait: the city is a flat diamond (about half as tall as wide); a pane of half the screen left wide empty bands
   above and below it. The pane now follows the width (whole city still in view, the fit is width/height), the panel gets the rest. */
@media (orientation: portrait) and (max-width: 560px) {
  #city-pane { height: min(48vh, calc(64vw + 62px)); height: min(48dvh, calc(64vw + 62px)); min-height: 220px; }
}

/* ---- UX pass 2, 11/11 14h40 */
/* empty lists: glyph + the sentence on screen (it was in a long-press tip only) */
.none { display: flex; align-items: flex-start; gap: 8px; line-height: 1.35; }
.none .none-text { font-size: 13px; color: var(--dim); max-width: 46ch; }
li.none { list-style: none; }
/* phone: a notice bubble answering a sheet or the open menu sat at the bottom, ON the sheet, for ~3 s. While a sheet or the
   menu is open the bubbles go to the top of the screen (under the header's notch inset), out of the hand's way. */
@media (orientation: portrait) and (max-width: 560px) {
  body:has(#sheet:not([hidden])) #toasts, body:has(#squad-sheet:not([hidden])) #toasts, body.menu-open #toasts {
    top: calc(8px + env(safe-area-inset-top)); bottom: auto; }
}
/* phone landscape: the sheet is the left half of the screen; the bubbles go to the right half while it is open */
@media (orientation: landscape) and (max-height: 520px) {
  body:has(#sheet:not([hidden])) #toasts, body:has(#squad-sheet:not([hidden])) #toasts {
    left: auto; right: calc(12px + env(safe-area-inset-right)); transform: none; width: min(40vw, 360px); }
}
/* Squad sheet: OK / Disband always in reach at the bottom of the phone sheet, the rest scrolls above (11/11 pass 3). */
#squad-sheet > .btn-row:last-child { position: sticky; bottom: calc(-14px - env(safe-area-inset-bottom)); z-index: 1; background: #20282c;
  margin: 6px -14px calc(-14px - env(safe-area-inset-bottom)); padding: 8px 14px calc(10px + env(safe-area-inset-bottom)); border-top: 1px solid rgba(255, 255, 255, .08); }

/* pass 4 (11/11): a dim sentence that says in words what a glyph row or a greyed button means */
.why-text { font-size: 12px; line-height: 1.35; color: var(--dim); margin: 6px 0 0; max-width: 60ch; }

/* B1 (11/11): a refusal's unlock as a chip (building thumb + star + level, resource + missing, builder busy), sentence under it in .why-text */
.need-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 6px; }
.need-row > .why-text { margin: 0; flex: 1 1 14ch; }
.need-chip { display: inline-flex; align-items: center; gap: 4px; min-height: 32px; padding: 2px 8px 2px 4px; border-radius: 16px; font-size: 13px; font-weight: 600;
  color: #ffcf9c; background: rgba(255, 207, 156, .1); border: 1px solid rgba(255, 207, 156, .45); }
button.need-chip { cursor: pointer; font: inherit; font-size: 13px; font-weight: 600; }
button.need-chip:hover, button.need-chip:focus-visible { background: rgba(255, 207, 156, .2); }
.need-chip .gi { width: 16px; height: 16px; }
.need-chip .ico { width: 18px; height: 18px; }
.need-thumb { width: 28px; height: 26px; object-fit: contain; }
.need-thumb.noart { width: 22px; height: 18px; margin: 3px; border-width: 1px; }

/* Roads (11/11): a building works only beside a road joined to the Command Post */
.warn-text { color: #ffc24a; }
.city-bar .road-off { display: flex; align-items: center; gap: 6px; white-space: normal; min-width: 0; }
.city-bar .road-off.off { visibility: hidden; }
.city-bar .road-off .why-text { margin: 0; font-size: 12px; color: #ffd98a; white-space: normal; min-width: 0; flex: 1 1 auto; }
.off-road { display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 6px 8px; border-radius: 8px;
  background: rgba(226, 96, 58, .12); border: 1px solid rgba(255, 138, 102, .4); }
.off-road > .stat .gi { width: 20px; height: 20px; }
.off-road .need-row { margin-top: 0; flex: 1 1 auto; }
.need-chip.road { padding: 2px 10px; }

/* ---- commander crates (11/11): header chip, crate cards, odds sheet, pity bar, reveal */
.gift-chip { display: inline-flex; align-items: center; flex: none; min-height: 44px; margin: -11px 0; padding: 0 2px; background: none; border: 0; box-shadow: none; }
.gift-chip .pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 7px; font-size: 12px; font-weight: 700; border-radius: 999px; color: #1d1603; background: var(--gold); border: 1px solid #fff0b8; }
.gift-chip .gi { width: 14px; height: 14px; }
.gift-chip .pill.full { box-shadow: 0 0 0 2px rgba(232, 162, 58, .45), 0 0 12px rgba(232, 162, 58, .7); }
.crates { margin-top: 4px; }
.card.gift.ready { border-color: var(--gold); background: linear-gradient(155deg, #3a3217 0 38%, #2a2716 38%); }
.gift-head { color: var(--gold); }
.gift-head .gi { width: 20px; height: 20px; }
.gift-next { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; margin: 4px 0 8px; color: var(--dim); font-size: 13px; }
.gift-next .why-text { margin: 0; flex: 1 1 100%; }
.gift-soon { padding: 3px 8px; border: 1px dashed var(--line); border-radius: 999px; }
/* state of a crate / box card = corner brackets (four L corners, straight), never a coloured side stripe:
   bright in the crate's colour when some are in stock, dim when none */
.card.crate { --bk: var(--crate); border-radius: 0; background-color: var(--card);
  background-image: linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)),
    linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)),
    linear-gradient(155deg, #29343a 0 38%, var(--card) 38%);
  background-repeat: no-repeat;
  background-size: 16px 2px, 2px 16px, 16px 2px, 2px 16px, 16px 2px, 2px 16px, 16px 2px, 2px 16px, 100% 100%;
  background-position: 0 0, 0 0, 100% 0, 100% 0, 0 100%, 0 100%, 100% 100%, 100% 100%, 0 0; }
.card.crate[data-n="0"] { --bk: #46565a; }
.box-cards { font-size: 12px; color: var(--dim); }
.box-cards .gi { width: 12px; height: 12px; }
button.ib.qmark { font-size: 18px; font-weight: 800; color: var(--gold); }
.box-face { display: flex; flex-wrap: wrap; gap: 5px; margin: 8px 0 2px; }
.box-face .odds-face .face { width: 34px; height: 34px; border-radius: 4px; }
.box-face .odds-face:not(.owned) .face { filter: saturate(.85); }
.box-face.medals { gap: 14px; }
/* featured pack (12/11): the big art of its commanders side by side, cropped on the face; frame = rarity colour (all four
   sides, straight corners); a tapped commander takes the room, the others give it (the width says who is read) */
.pack-face { --h: 168px; display: flex; gap: 4px; height: var(--h); margin: 8px 0 2px; }
.pack-hero { position: relative; flex: 1 1 0; min-width: 0; min-height: 0; height: 100%; padding: 0; overflow: hidden; border-radius: 0; background: #10151a;
  border: 2px solid #0b0f11; box-shadow: none; transition: flex-grow .28s cubic-bezier(.2, .7, .2, 1); }
/* the bust: --z times the frame height (wider when the frame is wider than that, so it always covers), moved so its face
   (--fx, --fy) sits at the middle / 40 % of the frame, clamped so no edge of the picture ever shows (cq units = the frame) */
.pack-hero { container-type: size; isolation: isolate; }
.pack-hero .pack-art { position: absolute; left: 0; top: 0; width: max(calc(var(--z, 1.5) * 75cqh), 100cqw); height: auto; aspect-ratio: 3 / 4; max-width: none;
  transform: translate(clamp(calc(100cqw - 100%), calc(50cqw - var(--fx, .5) * 100%), 0px), clamp(calc(100cqh - 100%), calc(40cqh - var(--fy, .24) * 100%), 0px));
  transition: filter .28s; }
.pack-hero.on { flex-grow: 2.4; }
.pack-face:not([data-on=""]) .pack-hero:not(.on) .pack-art { filter: saturate(.55) brightness(.75); }
.pack-plate { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; gap: 3px; padding: 14px 3px 4px;
  background: linear-gradient(180deg, rgba(10, 14, 16, 0), rgba(10, 14, 16, .92) 55%); color: #fff; font-size: 11px; font-weight: 700; line-height: 1.1; }
.pack-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pack-own { position: absolute; top: 4px; right: 4px; display: inline-flex; padding: 2px; background: #14181a; border: 1px solid var(--self); color: var(--self); }
.pack-own .gi { width: 13px; height: 13px; }
.pack-say { margin: 4px 0 0; min-height: 2.6em; }
@media (min-width: 900px) { .pack-face { --h: 200px; } .pack-plate { font-size: 12px; } }
@media (prefers-reduced-motion: reduce) { .pack-hero, .pack-hero .pack-art { transition: none; } }
.box-medal { display: inline-flex; align-items: center; gap: 3px; color: var(--r); font-weight: 700; font-variant-numeric: tabular-nums; }
.box-medal .gi { width: 30px; height: 30px; }
.odds-each { display: block; font-size: 11px; font-variant-numeric: tabular-nums; }
.rv-pager { display: flex; align-items: center; justify-content: center; gap: 14px; }
.rv-page-n { min-width: 4.5em; text-align: center; font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--gold); }
.reveal.paged .rv-grid { flex: 0 0 auto; overflow: visible; }
.rv-grid.fwd { animation: rv-in-fwd .22s ease-out 1; } .rv-grid.back { animation: rv-in-back .22s ease-out 1; }
@keyframes rv-in-fwd { from { transform: translateX(28px); opacity: .2; } to { transform: none; opacity: 1; } }
@keyframes rv-in-back { from { transform: translateX(-28px); opacity: .2; } to { transform: none; opacity: 1; } }
.rv-sum { display: flex; flex-direction: column; gap: 10px; padding: 10px; border: 1px solid var(--line); background: #1a2124; overflow-y: auto; min-height: 0; }
.rv-sum[hidden], .rv-grid[hidden], .rv-card[hidden] { display: none; }
.rv-sum-new { display: flex; flex-wrap: wrap; gap: 8px; }
.rv-sum-face { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; width: 62px; min-width: 0; min-height: 0; height: auto; padding: 0; border: 0; background: none; box-shadow: none; color: inherit; }
.rv-sum-face .face { width: 52px; height: 52px; border-radius: 4px; border: 2px solid var(--r); }
.rv-sum-row { display: flex; align-items: center; gap: 12px; padding: 5px 0; border-bottom: 1px solid #2b3538; color: var(--r); font-variant-numeric: tabular-nums; }
.rv-sum-row:last-child { border-bottom: 0; }
.rv-sum-name { flex: 1; font-size: 13px; }
@media (prefers-reduced-motion: reduce) { .rv-grid.fwd, .rv-grid.back { animation: none; } }
.crate-head { flex-wrap: nowrap; align-items: center; }
.crate-head .grow { min-width: 0; }
.crate-head strong { display: block; }
.crate .odds { gap: 2px 8px; font-size: 12px; font-variant-numeric: tabular-nums; }
.crate .odds .gi { width: 12px; height: 12px; }
.crate-n { font-weight: 700; font-variant-numeric: tabular-nums; }
.crate-art { position: relative; flex: none; width: 46px; height: 40px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
  background: linear-gradient(170deg, color-mix(in srgb, var(--crate) 70%, #fff 0%) 0 48%, color-mix(in srgb, var(--crate) 60%, #000) 48%);
  border: 2px solid color-mix(in srgb, var(--crate) 50%, #000); box-shadow: inset 0 -4px 0 rgba(0, 0, 0, .25); }
.crate-art .lid { position: absolute; left: -3px; right: -3px; top: -3px; height: 11px; border-radius: 4px; background: color-mix(in srgb, var(--crate) 80%, #fff); border: 2px solid color-mix(in srgb, var(--crate) 50%, #000); }
.crate-art .band { position: absolute; left: 50%; top: 0; bottom: 0; width: 6px; margin-left: -3px; background: rgba(0, 0, 0, .28); }
.crate-art .gi { position: relative; width: 18px; height: 18px; color: #fff; opacity: .9; }
.crate-art.mini { width: 26px; height: 22px; border-width: 1px; }
.crate-art.mini .lid { height: 7px; border-width: 1px; }
.crate-art.mini .gi { width: 12px; height: 12px; }
.crate .btn-row .chest-gain { margin-left: 4px; }
.odds-sheet { margin: 8px 0 2px; padding: 8px; border: 1px solid var(--line); border-radius: 6px; background: #1c2427; }
.odds-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; border-bottom: 1px solid #2b3538; }
.odds-row:last-of-type { border-bottom: 0; }
.odds-name { width: 6.5em; flex: none; font-size: 12px; color: var(--dim); }
.odds-pct { width: 5.2em; flex: none; font-variant-numeric: tabular-nums; font-weight: 700; }
.odds-pool { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.odds-face { position: relative; display: inline-flex; }
.odds-face .face { width: 30px; height: 30px; border-radius: 50%; border: 2px solid var(--r); }
.odds-face .gi { position: absolute; right: -3px; bottom: -3px; width: 13px; height: 13px; background: #14181a; border-radius: 50%; color: var(--self); }
/* the pity bar: a fill out of 24, three marks; full = glow + a pulse that ENDS (8 beats), then a still glow (ECO) */
.pity-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.pity-row .why-text { flex: 1 1 100%; margin: 0; }
button.pity { flex: 1 1 220px; display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 10px; background: #1a2124; border: 1px solid var(--line); border-bottom-width: 3px; }
button.pity .pity-ico { width: 20px; height: 20px; color: var(--leg, var(--gold)); flex: none; }
.pity-track { position: relative; flex: 1; height: 12px; border-radius: 6px; background: #0f1416; border: 1px solid #33403f; overflow: hidden; }
.pity-track i { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 6px; background: linear-gradient(90deg, #6a4c1a, var(--leg, var(--gold))); transition: width .35s ease-out; }
.pity-track b { position: absolute; top: 2px; bottom: 2px; width: 1px; background: rgba(255, 255, 255, .18); }
.pity-n { flex: none; min-width: 3.4em; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--leg, var(--gold)); }
.pity-kept { flex: none; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--leg, var(--gold)); }
.pity-kept[hidden] { display: none; }
button.pity.full .pity-kept { color: #2a1a00; }
.pity-epic { font-size: 12px; font-variant-numeric: tabular-nums; }
.pity-epic .gi { width: 12px; height: 12px; }
button.pity.full { color: #1d1603; border-color: #fff0b8; background: linear-gradient(100deg, #f6d27a, var(--leg, var(--gold)) 45%, #ff9f43 70%, #f6d27a);
  box-shadow: 0 0 0 2px rgba(255, 220, 140, .55), 0 0 22px rgba(232, 162, 58, .75); animation: pity-pulse 1.1s ease-in-out 8; }
button.pity.full .pity-ico, button.pity.full .pity-n { color: #2a1a00; }
button.pity.full .pity-track { background: rgba(60, 35, 0, .35); border-color: rgba(60, 35, 0, .45); }
button.pity.full .pity-track i { background: linear-gradient(90deg, #fff6d6, #fff); }
@keyframes pity-pulse { 50% { box-shadow: 0 0 0 4px rgba(255, 230, 160, .85), 0 0 34px rgba(255, 170, 60, .95); transform: scale(1.015); } }
button.pity.still, button.pity.full.still { animation: none; }
.card.crates-pity.profile { margin-top: 10px; }
/* reveal: the cards turn one by one; a rarity burst behind each, legendary gold glow; nothing loops */
.reveal { position: fixed; inset: 0; z-index: 70; display: flex; align-items: center; justify-content: center; padding: max(10px, env(safe-area-inset-top)) 10px max(10px, env(safe-area-inset-bottom));
  background: radial-gradient(ellipse at 50% 40%, rgba(34, 45, 50, .97), rgba(6, 9, 10, .98)); }
.rv-box { width: min(560px, 100%); max-height: 100%; display: flex; flex-direction: column; gap: 8px; }
.rv-head { display: flex; align-items: center; gap: 8px; }
.rv-head strong { font-size: 18px; color: var(--gold); }
.rv-head strong .gi { width: 22px; height: 22px; }
.rv-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; overflow-y: auto; padding: 14px; margin: 0 -14px; perspective: 900px; }
.reveal.many .rv-grid { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); gap: 6px; }
/* rows as tall as their cards: a squeezed grid (short screen) scrolls instead of laying the cards over each other */
.rv-grid { grid-auto-rows: max-content; flex: 1 1 auto; min-height: 0; align-content: start; }
/* phone landscape / short screens: one row of six, cards sized to fit the height */
@media (max-height: 560px) and (orientation: landscape) {
  .rv-box { width: min(820px, 100%); gap: 4px; }
  .rv-grid { grid-template-columns: repeat(6, 1fr); gap: 8px; padding: 10px; margin: 0 -10px; }
  .reveal.many .rv-grid { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); }
}
.rv-card { position: relative; min-height: 0; min-width: 0; height: auto; aspect-ratio: 3 / 4; padding: 0; border: 0; background: none; box-shadow: none; }
.rv-card:not(:disabled):active { transform: none; margin: 0; }
.rv-inner { position: absolute; inset: 0; transform-style: preserve-3d; transition: transform .5s cubic-bezier(.3, 1.4, .5, 1); }
.rv-card.open .rv-inner { transform: rotateY(180deg); }
.rv-back, .rv-face { position: absolute; inset: 0; border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; backface-visibility: hidden; -webkit-backface-visibility: hidden; overflow: hidden; }
.rv-back { background: repeating-linear-gradient(45deg, #2a3438 0 8px, #222b2f 8px 16px); border: 2px solid #4b5a5c; }
/* WebKit (Safari, iPhone) can paint a back face through backface-visibility (seen 11/11: results shown mirrored before
   the turn, the sure legendary visible from the first frame). The faces are therefore ALSO switched by visibility when
   the card passes 90 degrees: with the flip curve below that is at 12.5 % of .5 s = 62 ms. Works in every engine. */
.rv-face { visibility: hidden; transition: visibility 0s linear 62ms; }
.rv-card.open .rv-face { visibility: visible; }
.rv-back { transition: visibility 0s linear 62ms; }
.rv-card.open .rv-back { visibility: hidden; }
.rv-back .gi { width: 34%; height: 34%; color: #8fa3a3; }
.rv-face { transform: rotateY(180deg); background: radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--r) 45%, #1b2225), #161c1e 72%); border: 2px solid var(--r); }
.rv-face::before { content: ''; position: absolute; inset: -40%; background: radial-gradient(circle, var(--r), transparent 55%); opacity: 0; transform: scale(.2); }
.rv-card.open .rv-face::before { animation: rv-burst .9s ease-out 1 .25s; }
@keyframes rv-burst { 0% { opacity: .9; transform: scale(.2); } 100% { opacity: 0; transform: scale(1.4); } }
.rv-gem { position: absolute; top: 5px; left: 6px; }
.rv-gem .gi { width: 14px; height: 14px; }
.rv-pic { position: relative; width: 62%; aspect-ratio: 1; border-radius: 50%; border: 2px solid var(--r); object-fit: cover; }
.rv-pic.small { width: 34%; opacity: .75; }
.rv-name { position: relative; font-size: 12px; font-weight: 700; color: #fff; text-align: center; padding: 0 3px; }
.rv-new { position: absolute; top: 4px; right: 5px; display: inline-flex; color: var(--self); }
.rv-new .gi { width: 13px; height: 13px; }
.rv-medal { position: relative; display: inline-flex; align-items: center; gap: 2px; font-size: 16px; font-weight: 700; }
.rv-medal .gi { width: 30px; height: 30px; }
.reveal.many .rv-medal { font-size: 12px; } .reveal.many .rv-medal .gi { width: 18px; height: 18px; } .reveal.many .rv-name { display: none; }
.rv-card.r-legendary.open .rv-face { box-shadow: 0 0 0 2px #fff0b8, 0 0 26px 6px rgba(232, 162, 58, .85); }
.rv-card.r-legendary.open { z-index: 1; animation: rv-gold 1.2s ease-out 1 .2s; }
@keyframes rv-gold { 30% { transform: scale(1.08); } 100% { transform: scale(1); } }
.rv-card.r-epic.open .rv-face { box-shadow: 0 0 14px 2px color-mix(in srgb, var(--r) 70%, transparent); }
.reveal.pack .rv-card.sure .rv-back { border-color: var(--gold); background: repeating-linear-gradient(45deg, #4a3a14 0 8px, #3a2e10 8px 16px); }
.rv-pity { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 6px; background: #1a2124; }
.rv-pity .gi { width: 18px; height: 18px; color: var(--gold); }
.rv-pity.full { border-color: var(--gold); box-shadow: 0 0 14px rgba(232, 162, 58, .6); }
@media (prefers-reduced-motion: reduce) {
  .rv-inner, .pity-track i, .rv-back, .rv-face { transition: none; }
  .rv-card.open .rv-face::before, .rv-card.r-legendary.open, button.pity.full { animation: none; }
}
.reveal.quick .rv-inner { transition-duration: .01s; }
.reveal.quick .rv-back, .reveal.quick .rv-face { transition-delay: 0s; }

/* ---- player menu on the profile + ONE city/world switch at the top right (owner 11/11 19:45) */
.who-btn { position: relative; display: flex; align-items: center; gap: 8px; min-width: 0; flex: 0 1 auto; min-height: 44px; padding: 2px 10px 2px 2px; background: none; border: 1px solid transparent; text-align: left; box-shadow: none; }
.who-btn[aria-expanded="true"] { border-color: var(--self); background: #232c30; }
.who-btn > .badge { position: absolute; left: 24px; right: auto; top: -3px; }   /* right: auto: with the .badge's own right: 2px it was stretched over the whole name (seen 12/11 on the pin captures) */
.switch-btn { margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 12px; border-color: var(--self); font-weight: 700; }
.switch-btn .tab-ico { width: 22px; height: 22px; }
.pmenu { position: fixed; z-index: 37; left: 8px; top: var(--menu-top, 90px); width: min(340px, calc(100vw - 16px)); max-height: calc(100vh - var(--menu-top, 90px) - 12px); overflow: auto;
  background: var(--panel); border: 1px solid var(--self); border-radius: 12px; padding: 10px; box-shadow: 0 8px 30px rgba(0, 0, 0, .6); animation: pm-in .16s ease-out; transform-origin: 24px 0; }
@keyframes pm-in { from { opacity: 0; transform: scale(.94) translateY(-6px); } }
.pm-id { display: flex; gap: 10px; align-items: center; margin-bottom: 8px; }
.pm-sub { font-size: 13px; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pm-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; }
.pm-row { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; min-height: 58px; min-width: 0; padding: 6px 4px; font-size: 12px; }
.pm-row svg { width: 22px; height: 22px; }
.pm-row .badge { position: absolute; right: 4px; top: 4px; }
.pm-out { flex-direction: row; gap: 8px; width: 100%; margin-top: 8px; min-height: 44px; }
/* the word under a tab glyph is drawn from its label where the tabs are a menu (phone portrait) or a side column (phone landscape) */
@media (orientation: portrait) and (max-width: 560px), (orientation: landscape) and (max-height: 520px) { #tabs button::after { content: attr(aria-label); font-size: 11px; line-height: 1.1; } }
@media (orientation: portrait) and (max-width: 560px) {
  /* the areas open as one row under the header, the player's tools right below: one tap, no scrolling */
  #tabs { left: 8px; right: 8px; top: var(--menu-top, 90px); bottom: auto; width: auto; flex-direction: row; padding: 0; overflow: hidden; z-index: 38; }
  #tabs button { flex: 1 1 0; min-width: 0; border-left: 0; border-bottom: 3px solid transparent; }
  #tabs button[aria-current="page"] { border-left-color: transparent; border-bottom-color: var(--self); }
  .pmenu { top: calc(var(--menu-top, 90px) + 62px); width: auto; right: 8px; max-height: calc(100vh - var(--menu-top, 90px) - 74px); }
  .pm-row[data-testid="pm-troops"], .pm-row[data-testid="pm-alliance"], .pm-row[data-testid="pm-reports"], .pm-row[data-testid="pm-shop"] { display: none; }
  .switch-btn { padding: 0 9px; } .who-btn { padding-right: 4px; gap: 6px; }
}
@media (max-width: 420px) { .switch-btn .sw-word { display: none; } }
@media (prefers-reduced-motion: reduce) { .pmenu { animation: none; } }
/* home pane fits the panel: commander = one line, tap = its own pane; tighter cards on short screens */
.hero-line { display: flex; align-items: center; gap: 10px; cursor: pointer; border-color: var(--gold); }
.hero-line > svg { width: 20px; height: 20px; flex: none; opacity: .8; }
.hero-line:active { transform: translateY(1px); }
@media (max-height: 760px) {
  #panel .card { padding-top: 7px; padding-bottom: 7px; margin-bottom: 6px; }
  #panel .city-head { margin-bottom: 2px; }
}
@media (orientation: portrait) and (max-width: 560px) {
  /* zoom buttons ride on the scene instead of taking a row: the row's height goes to the panel */
  #city-pane .map-btns, #map-pane .map-btns { position: absolute; right: 6px; bottom: 6px; padding: 0; background: none; z-index: 2; }
  #city-pane .map-btns button, #map-pane .map-btns button { width: 44px; min-height: 44px; background: rgba(27, 34, 37, .9); }
  #city-pane { height: min(42vh, calc(64vw + 16px)); height: min(42dvh, calc(64vw + 16px)); min-height: 200px; }
  #panel { padding-bottom: 8px; }
  #panel .card { padding-top: 7px; padding-bottom: 7px; margin-bottom: 6px; }
}
/* phone on its side (CITY-A finish 13/11): the city pane takes the full height of its grid column (the base 48dvh above
   came after the landscape block and won, leaving a 220 px strip), and the zoom buttons ride on the scene instead of a row under it */
@media (orientation: landscape) and (max-height: 520px) {
  #city-pane { height: auto; min-height: 0; }
  #city-pane .map-btns { position: absolute; right: 6px; bottom: 6px; padding: 0; background: none; z-index: 2; flex-direction: column; }
  #city-pane .map-btns button { width: 44px; min-height: 44px; background: rgba(27, 34, 37, .9); }
}
@media (orientation: portrait) and (max-width: 560px) { #panel { padding-top: 4px; } #panel [data-testid="queue-build-idle"] { margin: 0 0 4px; } #panel .city-head { margin-bottom: 0; } }
/* ---- world finder (owner 11/11): name / TAG / x:y + one-tap filters, results nearest first */
.finder { margin-bottom: 6px; }
.find-row-top { gap: 6px; align-items: center; }
.find-in { flex: 1 1 90px; min-width: 0; min-height: 44px; padding: 0 10px; background: #10151a; color: var(--text); border: 1px solid var(--line); border-radius: 6px; font: inherit; }
.find-in:focus { outline: none; border-color: var(--self); }
.find-f { flex: none; min-width: 44px; min-height: 44px; }
.find-hit { display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 4px 8px; border-bottom: 1px solid var(--line); cursor: pointer; }
.find-hit:active { background: #232c30; }
.find-hit .dim { font-size: 13px; white-space: nowrap; }
.find-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.pm-row .badge, .who-btn > .badge { font-size: 11px; }
/* wide screens keep the glyph-only tab row (name in the tip); the word shows where the tabs are a menu or a side column */

/* R1 12/11 - the roster: 16 commanders, one row per rarity (highest first), each face framed in its rarity colour */
.skill-tabs.roster { display: grid; grid-template-columns: repeat(auto-fit, minmax(292px, 1fr)); gap: 4px 12px; }
.roster-row { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; position: relative; padding-left: 40px; }
.roster-row > .roster-tag { position: absolute; left: 0; top: 0; height: 52px; }
.roster-row button.skill-tab { position: relative; flex: 0 0 calc((100% - 24px) / 5) !important; max-width: calc((100% - 24px) / 5) !important; }
/* 13/11 23:50: 76 commanders (every UNBREAKABLE AGE character): a rarity row of 29 wraps instead of running off the screen */
.roster-tag { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 34px; flex: none; font-size: 11px; font-weight: 700; line-height: 1.1; }
.roster-tag svg { width: 16px; height: 16px; }
.roster-row button.skill-tab { flex: 1 1 0; max-width: 72px; min-width: 48px; padding: 3px 4px; opacity: 1; }
.roster-row button.skill-tab .face { width: 40px; height: 40px; border: 2px solid var(--r); }
.roster-row button.skill-tab.on { box-shadow: inset 0 -3px 0 var(--r); }
.roster-row button.skill-tab.locked { opacity: .5; }
.roster-row button.skill-tab.locked .face { border-style: dashed; }
/* the leader picker: same grouping, compact tiles (face, name, level or lock, main bonus) */
.cmd-pick { grid-template-columns: minmax(0, 1fr); gap: 6px; }
/* 13/11 23:50, 76 commanders: --n is capped at 5 in city.js, a big rarity WRAPS onto more rows (the picker scrolls) */
/* --n = the rarity's count, 4 at least (13/11: 5 legendaries with Torstein wrapped Magni onto a 5th row and pushed the commons
   under the dock at 390 px): one row per rarity, the picker stays one screen */
.cmd-group { display: grid; grid-template-columns: repeat(var(--n, 4), minmax(0, 1fr)); gap: 6px; }
.cmd-group.wide { gap: 4px; }
.cmd-group.wide button.cmd-opt { padding: 0; }
.cmd-group.wide button.cmd-opt .stats { gap: 0 2px; flex-wrap: wrap; }
.cmd-group.wide button.cmd-opt .stat { gap: 0; }
button.cmd-opt { flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 0; padding: 0; min-height: 0; text-align: center; border-width: 2px; }
button.cmd-opt span { align-items: center; }
button.cmd-opt strong { font-size: 12px; }
button.cmd-opt .stats { flex-direction: row; gap: 6px; font-size: 11px; flex-wrap: wrap; justify-content: center; }
.sq-cand .face { border: 2px solid var(--r, #55666a); }
.roster-face { position: relative; display: block; line-height: 0; }
.roster-face .stat.lvl { position: absolute; right: -6px; bottom: -3px; padding: 0 3px; border-radius: 3px; background: #10151a; font-size: 11px; line-height: 15px; gap: 1px; }
.roster-face .stat.lvl svg { width: 11px; height: 11px; }
.roster-name { font-size: 11px; font-weight: 600; line-height: 1.2; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.roster-row button.skill-tab { max-width: none; gap: 3px; }
.roster-row button.skill-tab.on { background: #1c262b; }
button.cmd-opt .stats, button.cmd-opt .stat { flex-direction: row; align-items: center; }

.cmd-pick { scroll-margin-top: 4px; }
button.cmd-opt { overflow: hidden; }
button.cmd-opt .stats { flex-wrap: nowrap; gap: 4px; font-size: 11px; }
button.cmd-opt .stat { padding: 0; background: none; gap: 1px; white-space: nowrap; }
button.cmd-opt .stat svg { width: 11px; height: 11px; }
button.cmd-opt strong { line-height: 1.2; }
/* 13/11: with a 5th rarity row (the 7 Viral influencers) the picker did not fit the phone panel (8 commons and rares under the dock at
   390 px): five rows, one line of figures each, smaller faces */
@media (max-width: 560px) {
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) button.cmd-opt strong { font-size: 11px; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) .cmd-group.wide button.cmd-opt strong { font-size: 10px; letter-spacing: -.02em; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) button.cmd-opt .stats { flex-wrap: nowrap; font-size: 10px; gap: 0 2px; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) .cmd-group.wide button.cmd-opt .stats { flex-wrap: wrap; font-size: 10px; gap: 0 2px; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) .cmd-group.wide button.cmd-opt { position: relative; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) .cmd-group.wide button.cmd-opt [data-testid^="cmd-locked-"] { position: absolute; top: 2px; right: 2px; }   /* the lock rides on the corner: no second line */
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) .cmd-group.wide button.cmd-opt strong { white-space: nowrap; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) .cmd-group { gap: 4px; }
  .cmd-pick:has(> .cmd-group:nth-of-type(5)) { gap: 4px; }
}
.sq-pick .sq-cand { width: calc((100% - 30px) / 6); min-width: 48px; padding: 4px 2px; overflow: hidden; }
.sq-pick .sq-cand .face { width: 36px; height: 36px; }
.sq-pick .sq-cand-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-pick { gap: 4px; }
.sq-pick .sq-cand { width: calc((100% - 24px) / 7); min-width: 44px; font-size: 11px; }

/* ==== Shop = buy only (12/11 10:15): STAGE with the big art of the selected banner + RAIL of banners + buy bar.
   Nothing opens here. The crate part is exactly one screen of the panel (--ph, set by crates.js). ==== */
#view-shop { margin: -8px -12px 0 -42px; }
.shop-rest { padding: 0 12px 0 42px; }
.shop-crates { margin: 0; display: flex; flex-direction: column; height: var(--ph, 80vh); min-height: 430px; background: #0f1416; }
.stage { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden;
  background: radial-gradient(120% 90% at 50% 8%, color-mix(in srgb, var(--crate) 34%, #141a1d) 0, #12181b 58%, #0b0f11 100%); }
.stage::before { content: ""; position: absolute; inset: 0; opacity: .5; pointer-events: none;
  background: repeating-linear-gradient(115deg, rgba(255, 255, 255, .035) 0 2px, transparent 2px 46px); }
/* the bar is a HEADER (--bar high), the art starts under it: title + dots, the two steps, the stock. No step on a face (12/11) */
.stage { --bar: 52px; }
.stage-body { position: absolute; inset: var(--bar) 0 0; }
.stage-top { position: absolute; left: 0; right: 0; top: 0; z-index: 3; height: var(--bar); display: flex; align-items: center; gap: 6px; padding: 0 8px;
  background: linear-gradient(180deg, rgba(8, 11, 12, .94), rgba(8, 11, 12, .78)); border-bottom: 1px solid rgba(224, 178, 58, .22); }
.stage-head { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.stage-title { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: 17px; line-height: 1.1; color: #fff; text-shadow: 0 1px 0 #000; white-space: nowrap; }
.stage-title-t { min-width: 0; }
.stage-title .box-cards { font-size: 12px; color: #d6dcd8; }
button.inv-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 10px; border-radius: 0; font-size: 12px; font-weight: 700;
  background: rgba(16, 20, 22, .88); border: 1px solid var(--line); color: var(--text); box-shadow: none; }
button.inv-chip:disabled { opacity: 1; color: var(--dim); }
button.inv-chip.has { border-color: var(--gold); color: #ffe9ae;
  background-image: linear-gradient(var(--gold), var(--gold)), linear-gradient(var(--gold), var(--gold)), linear-gradient(var(--gold), var(--gold)), linear-gradient(var(--gold), var(--gold));
  background-repeat: no-repeat; background-size: 10px 3px, 3px 10px, 10px 3px, 3px 10px; background-position: 0 0, 0 0, 100% 100%, 100% 100%; }
button.inv-chip .gi { width: 14px; height: 14px; }
.inv-chip.new { animation: chip-lock .6s cubic-bezier(.2, 1.5, .4, 1) 1; }
@keyframes chip-lock { from { transform: translateY(26px) scale(1.18); box-shadow: 0 0 0 3px rgba(224, 178, 58, .7), 0 0 26px rgba(224, 178, 58, .9); } to { transform: none; box-shadow: none; } }
button.chev { flex: none; width: 44px; min-width: 44px; height: 44px; min-height: 44px; padding: 0; border-radius: 0; color: var(--gold); background: rgba(12, 16, 18, .62); border: 1px solid rgba(224, 178, 58, .45); box-shadow: none; }
button.chev .gi { width: 22px; height: 22px; }
/* return (owner 12/11 13:08): the glyph alone, floating on the art in the top right corner of the stage. No ground, no border,
   no frame of its own: a soft shadow makes it read on a light or a dark picture. 48 px to the finger. */
button.stage-return { position: absolute; z-index: 3; top: var(--bar); right: 0; width: 48px; min-width: 48px; height: 48px; min-height: 48px; padding: 0;
  border: 0; border-radius: 0; background: none; box-shadow: none; color: #fff; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .95)) drop-shadow(0 0 7px rgba(0, 0, 0, .7)); }
button.stage-return .gi { width: 26px; height: 26px; }
button.stage-return:not(:disabled):active { transform: scale(.88); margin: 0; }
/* the finger on the stage: sideways is ours (commanders); up / down is ours only where nothing scrolls under it (crates.js) */
.stage { touch-action: pan-y; }
.stage[data-vswipe="1"] { touch-action: none; }
button.inv-chip { flex: none; }
.stage-dots { display: flex; gap: 5px; padding-left: 2px; }
.stage-dots i { width: 7px; height: 7px; background: rgba(255, 255, 255, .28); }
.stage-dots i.on { background: var(--gold); }
.stage-info { position: absolute; z-index: 3; right: 6px; bottom: 6px; display: flex; gap: 6px; }
.stage-info button { min-height: 44px; border-radius: 0; background: rgba(16, 20, 22, .9); }
/* the bust at ONE scale for everybody: as tall as its frame (wider frames: as wide), only the sides cut around the face (--fx) */
.bust { position: absolute; left: 0; top: 0; --w: 100cqw; width: max(75cqh, var(--w)); height: auto; aspect-ratio: 3 / 4; max-width: none;
  transform: translateX(clamp(calc(var(--w) - 100%), calc(var(--w) / 2 - var(--fx, .5) * 100%), 0px)); }
.hero-in { position: absolute; inset: 0; display: block; }
/* group view: the top rarity LARGE, leaning slices that overlap; the next rarity small under them */
.stage-group { position: absolute; inset: 0; display: flex; flex-direction: column; }
.group-big { flex: 1 1 auto; min-height: 0; display: flex; margin: 0 -1.5% 0 0; }   /* 14/11 accordion: no left overhang, the open first slice starts on the screen */
.group-big .pack-hero { transform: skewX(-6deg); margin-left: -2px; border-width: 0 2px; }
.group-big .hero-in { inset: 0 -18%; transform: skewX(6deg); }
.group-big .bust { --w: 136cqw; }
/* owner 12/11 14:41 ("the commander still is not half body, from the head to the top of the knees"): a WIDE slice (desktop)
   enlarged the picture to its width and cut it at the chest. The large slices now draw the cut-out commander at the height of
   the frame, whatever its width (head to knees always), centred on the face, over one slate ground. */
.group-big .pack-hero { background: var(--biome, radial-gradient(90% 70% at 50% 32%, #33363f, #1b1d23 78%)); }
.group-big .bust[data-cut="1"] { width: 75cqh; transform: translateX(calc(68cqw - var(--fx, .5) * 100%)); }
.group-small { flex: 0 0 40%; min-height: 76px; display: flex; gap: 0; padding: 4px 56px 6px 8px; background: linear-gradient(180deg, rgba(8, 11, 12, .35), rgba(8, 11, 12, .9)); }   /* 40 %: its open commander stands whole too (14/11); 56 px: the "?" and details buttons stand one above the other at its right */
.group-small .pack-hero { flex: 0 1 84px; padding: 0; }   /* its class "small" met button.small (padding 0 10px): the picture was 20 px narrower than its frame */
.stage .pack-hero:hover .bust, .stage .pack-hero:focus-visible .bust { filter: brightness(1.14); }
.stage .pack-plate { padding: 16px 2px 4px; }
.stage .pack-own { position: static; padding: 1px; flex: none; }
/* a small frame: the mark in its corner, the whole width for the name */
.stage .group-small .pack-own { position: absolute; top: 3px; right: 3px; z-index: 1; }
.group-big .pack-plate { font-size: 13px; padding-bottom: 8px; }
/* THE ACCORDION (owner 14/11 13:12, his Commander box capture: 8 legendaries at 51 px and 20 epics at 10 px, nobody readable:
   "the 1st commander full larger body, same for the 1st row and the 2nd row; each time the user clicks the next commander the 1st
   goes reduce size: only the full body commander 1 by 1"). Each row of the group view (.acc, crates.js row()) shows ONE commander
   whole, .open: the cut-out figure at the row's full height, both sides inside the slice, centred on the figure (--bx, balance.json
   body); the others are slices that give it the room, face in their middle, darker, no name (it is in their tip). The row is the
   container (cq = the row): the open width = the figure (--bw) at the row height + the lean of a large slice (tan 6deg = 10.5cqh)
   + 6 px, never more than the row minus --sl for each of the others. The room passes in ONE move (the same buttons stay, crates.js
   openHero): the new one grows where it stands while the old one folds, the picture slides from its face to its figure. */
.stage-group .acc { container-type: size; user-select: none; -webkit-user-select: none; }
.group-big.acc { --sl: 18px; } .group-small.acc { --sl: 8px; }
.stage-group .acc > .pack-hero { flex: 1 1 0; min-width: 0; transition: flex .34s cubic-bezier(.2, .7, .2, 1), border-color .34s; }
.stage-group .group-big.acc > .pack-hero.open { flex: 0 0 min(calc(var(--bw, 1) * 75cqh + 10.5cqh + 6px), calc(100cqw - (var(--n) - 1) * var(--sl))); }
.stage-group .group-small.acc > .pack-hero.open { flex: 0 0 min(calc(var(--bw, 1) * 75cqh + 4px), calc(100cqw - (var(--n) - 1) * var(--sl))); }
.stage-group .group-small.acc > .pack-hero:not(.open) { border-width: 0 1px; }   /* 2 px of frame on a 10 px slice is a fifth of it */
/* "the 1st will go reduce size": a closed commander is also drawn SMALLER (--rs of the row height), standing on the same ground
   line, face in the middle of its slice; the open one is the only one at full height. The size passes in the same move. */
.acc { --rs: .68; }
/* scale() AFTER translateX in the same transform, about the point under the face (or the figure) on the ground line: the picture
   shrinks where it stands, then the face is laid in the middle of the slice (the separate `scale` property applies after the
   translation and pushed the faces out of the narrow slices) */
.acc .bust[data-cut="1"] { width: 75cqh; transform-origin: calc(var(--fx, .5) * 100%) 100%;
  transition: transform .34s cubic-bezier(.2, .7, .2, 1), transform-origin .34s cubic-bezier(.2, .7, .2, 1), filter .34s; }
.acc .pack-hero.open .bust[data-cut="1"] { transform-origin: calc(var(--bx, .5) * 100%) 100%; }
.group-big.acc .bust[data-cut="1"] { transform: translateX(calc(68cqw - var(--fx, .5) * 100%)) scale(var(--rs)); }
.group-big.acc .pack-hero.open .bust[data-cut="1"] { transform: translateX(calc(68cqw - var(--bx, .5) * 100%)) scale(1); }
.group-small.acc .bust[data-cut="1"] { transform: translateX(calc(50cqw - var(--fx, .5) * 100%)) scale(var(--rs)); }
.group-small.acc .pack-hero.open .bust[data-cut="1"] { transform: translateX(calc(50cqw - var(--bx, .5) * 100%)) scale(1); }
.acc > .pack-hero:not(.open) .bust { filter: saturate(.6) brightness(.68); }
.stage .acc > .pack-hero:not(.open):hover .bust, .stage .acc > .pack-hero:not(.open):focus-visible .bust { filter: saturate(.85) brightness(.95); }
.acc > .pack-hero:not(.open) .pack-plate, .acc > .pack-hero:not(.open) .pack-own { opacity: 0; visibility: hidden; }
.acc > .pack-hero .pack-plate { transition: opacity .2s .14s; }
/* the open slice's name: the slice leans (skewX -6deg about its middle), at its BOTTOM its window sits tan(6deg) x half its height
   left of the middle ('Torste|' 13/11): the plate moves left by that much (0.105 x (50cqh - the name row)) */
.group-big.acc .pack-plate { font-size: 15px; padding-left: 12%; padding-right: 12%; gap: 4px; translate: -4.8cqh 0; }
.group-small.acc .pack-plate { font-size: 12px; }
/* the "?" and details buttons: one above the other at the right of the small row (it keeps its width for the slices) */
.stage:has(.group-small) .stage-info { flex-direction: column; }
@media (prefers-reduced-motion: reduce) { .stage-group .acc > .pack-hero, .acc .bust, .acc > .pack-hero .pack-plate { transition: none; } }
/* a banner with no small row: its names stand above the "?" and details buttons (the last name was under them) */
.stage-group:not(:has(.group-small)) .group-big .pack-plate { padding-bottom: 56px; }
/* one commander alone: the art fills the stage on a phone, a tall framed picture on a wide screen */
.stage-solo { position: absolute; inset: 0; }
.solo-art { position: absolute; inset: 0; container-type: size; overflow: hidden; }
.solo-plate { position: absolute; left: 0; right: 0; bottom: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; padding: 46px 112px 14px 12px;
  background: linear-gradient(180deg, rgba(8, 11, 12, 0), rgba(8, 11, 12, .93) 60%); }
.rar-badge { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .08em;
  color: var(--r); border: 1px solid var(--r); background: rgba(10, 14, 16, .82); }
.rar-badge .gi { width: 12px; height: 12px; }
/* STORY 13/11: the faction a commander swears to, beside its rarity on the card plate: a small shield split in the
   faction's two colours, edged in its accent (the portraits predate the factions; this costs no picture) */
.cc-tags { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.fac-mark { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px 2px 4px; font-size: 12px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; color: var(--fa); background: rgba(10, 14, 16, .82); border: 1px solid color-mix(in srgb, var(--fa) 55%, transparent); }
.fac-shield { width: 13px; height: 15px; flex: none; background: linear-gradient(135deg, var(--fm) 0 50%, var(--fs) 50% 100%);
  clip-path: polygon(0 0, 100% 0, 100% 58%, 50% 100%, 0 58%); box-shadow: inset 0 0 0 1px var(--fa); }
.solo-name { font-size: 32px; line-height: 1; color: #fff; text-shadow: 0 2px 0 #000; }
.solo-title { font-size: 13px; color: var(--r); font-weight: 700; }
.solo-own { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--dim); }
.solo-own.mine { color: var(--self); }
.solo-own .gi { width: 13px; height: 13px; }
.stage-medals { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 6%; margin: 0; }
.stage-medals .box-medal { flex-direction: column; gap: 2px; font-size: 16px; }
.stage-medals .box-medal .gi { width: min(16vw, 84px); height: min(16vw, 84px); filter: drop-shadow(0 0 14px color-mix(in srgb, var(--r) 60%, transparent)); }
.stage-medals .box-medal small { font-size: 12px; font-weight: 400; color: var(--dim); }
/* the move says which way you went: banners follow the rail, commanders follow the chevron */
.stage-body[class*="-fwd"] { animation: stage-in .26s cubic-bezier(.2, .7, .2, 1) 1; --dx: 46px; }
.stage-body[class*="-back"] { animation: stage-in .26s cubic-bezier(.2, .7, .2, 1) 1; --dx: -46px; }
@keyframes stage-in { from { transform: translate(var(--dx, 0), var(--dy, 0)); opacity: .15; } to { transform: none; opacity: 1; } }
/* a banner reached by a finger going up / down comes from below / above, on every screen */
.stage .stage-body[class*="in-vbanner-fwd"] { --dx: 0px; --dy: 46px; }
.stage .stage-body[class*="in-vbanner-back"] { --dx: 0px; --dy: -46px; }
.stage.still .stage-body, .stage.still .inv-chip { animation: none; }
/* rail: one swipe strip of banners under the stage (phone); the selected one wears the corner brackets */
.rail { flex: none; display: flex; gap: 6px; padding: 6px 8px; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; background: #141a1d; border-top: 1px solid var(--line); }
.rail::-webkit-scrollbar { display: none; }
button.rail-b { --rh: 76px; position: relative; flex: 0 0 148px; height: var(--rh); min-height: 0; padding: 0; overflow: hidden; border-radius: 0; background: #10151a; border: 1px solid #36434a; box-shadow: none; opacity: .78; }
.rail-art { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, color-mix(in srgb, var(--crate) 40%, #12181b), #12181b 70%); }
.rail-cell { position: relative; flex: 0 1 calc(var(--rh) * .56); height: 100%; overflow: visible; container-type: size; }
/* a banner with crates in stock wears its count top right: the three stand on the left, the count never lies on a head */
button.rail-b:not([data-n="0"]) .rail-art { justify-content: flex-start; padding-left: 4px; }
/* owner 12/11 15:35: in the rail no rarity colour, on the slate of the stage, the rarity gem under each commander.
   owner 12/11 16:30 (replaces "smaller"): "the image should be close, it is too far; the 3 personnages can also touch each other".
   The picture is drawn LARGER than the cell (1.1 of its height) and its box is a window on the top of it, head to waist
   (object-fit: cover, from the top); the cells are narrower than that window, so the three stand shoulder to shoulder and overlap. */
.rail-art:has(.rail-cell) { background: radial-gradient(90% 70% at 50% 32%, #33363f, #1b1d23 78%); }
.rail-cell .bust { left: 50%; top: 0; width: 80cqh; height: 62cqh; aspect-ratio: auto; object-fit: cover; object-position: 50% 0; transform: translateX(calc(var(--fx, .5) * -100%));
  -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent); mask-image: linear-gradient(180deg, #000 78%, transparent); }
/* Phil 15/11 (replaces the head-to-waist window above for the rail): "a ton of empty gaps ... fill the frame ... show their face".
   The three cells share the WHOLE width of the banner (no empty sides), each draws the head-and-shoulders cut-out filling the
   cell from its top down to the gem row (no empty band), the gem under it, the name under the gem. */
.rail-art:has(.rail-cell) { justify-content: stretch; gap: 0; padding: 0 2px; }
.rail-art .rail-cell { flex: 1 1 0; min-width: 0; }
.rail-cell .bust.face-in { left: 0; top: 0; width: 100%; height: calc(100% - var(--rail-low, 27px)); transform: none; object-fit: cover; object-position: 50% 0;
  -webkit-mask-image: linear-gradient(180deg, #000 82%, transparent); mask-image: linear-gradient(180deg, #000 82%, transparent); }
.rail-cell:has(.face-in) .rail-rar { top: auto; bottom: calc(var(--rail-low, 27px) - 12px); width: 10px; height: 10px; }
.rail-rar { position: absolute; left: 50%; top: 63cqh; width: clamp(8px, 13cqh, 14px); height: clamp(8px, 13cqh, 14px); transform: translateX(-50%); display: block; line-height: 0; z-index: 1; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .8)); }
.rail-rar .gi { display: block; width: 100%; height: 100%; fill: currentColor; fill-opacity: .45; }
.rail-name { position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 4px 3px; font-size: 11px; font-weight: 700; line-height: 1.1; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: linear-gradient(180deg, rgba(8, 11, 12, 0), rgba(8, 11, 12, .95) 62%); }
.rail-n { position: absolute; top: 2px; right: 2px; display: inline-flex; align-items: center; gap: 2px; padding: 0 4px; font-size: 11px; font-weight: 700; color: #ffe9ae; background: rgba(8, 11, 12, .85); }
.rail-n .gi { width: 11px; height: 11px; }
button.rail-b.on { opacity: 1; border-color: var(--crate); }
button.rail-b.on::after { content: ""; position: absolute; inset: 0; pointer-events: none; --bk: #fff;
  background-image: linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)),
    linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk)), linear-gradient(var(--bk), var(--bk));
  background-repeat: no-repeat; background-size: 12px 2px, 2px 12px, 12px 2px, 2px 12px, 12px 2px, 2px 12px, 12px 2px, 2px 12px;
  background-position: 0 0, 0 0, 100% 0, 100% 0, 0 100%, 0 100%, 100% 100%, 100% 100%; animation: rail-lock .3s ease-out 1; }
@keyframes rail-lock { from { inset: -8px; opacity: 0; } to { inset: 0; opacity: 1; } }
button.stage-strip { flex: none; display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px; padding: 4px 10px; text-align: left; font-size: 12px; font-weight: 400; line-height: 1.25;
  color: var(--dim); background: #141a1d; border: 0; border-top: 1px solid #263034; border-radius: 0; box-shadow: none; }
button.stage-strip .gi { flex: none; width: 16px; height: 16px; color: var(--leg, var(--gold)); }
/* the chat handle docks at the left of this line while the Shop shows it (crates.js dockTalk): the line keeps its gutter */
.shop-crates > button.stage-strip { min-height: 58px; padding-left: 44px; }
body[data-tab="shop"][data-talk-dock] .talk-btn { top: var(--talk-y, calc(50% - 28px)); }
.shop-crates > .why-text { margin: 0; padding: 2px 10px; background: #141a1d; }
.buyzone { flex: none; padding: 6px 8px 8px; background: #141a1d; }
.buybar { display: flex; gap: 8px; }
.buybar button.buy { flex: 1 1 0; min-height: 52px; justify-content: center; gap: 10px; font-size: 16px; border-radius: 0; }
.buy-k { font-size: 19px; }
.buyzone .why-text { margin: 4px 0 0; }
/* Touch screen (12/11 14:46): the stage + rail + buy row are ONE first screen that does not scroll (a finger up / down on the
   stage then changes banner); the rest of the Shop is behind the "more" entry of the buy row. With a mouse: both in the page. */
.shop-more, .shop-back { display: none; }
@media (pointer: coarse) {
  body[data-tab="shop"][data-shop-face="stage"] #panel { overflow-y: hidden; padding-bottom: 0; }
  #view-shop[data-face="stage"] .shop-rest, #view-shop[data-face="rest"] .shop-crates { display: none; }
  .buybar button.shop-more { display: flex; flex: none; align-items: center; justify-content: center; gap: 4px; min-width: 76px; min-height: 52px; padding: 0 10px; font-size: 13px; border-radius: 0;
    background: transparent; border: 1px solid var(--line); color: var(--text); }
  .shop-more .gi { width: 16px; height: 16px; }
  /* the "more" entry takes its width from the two buy buttons: their count and price stay on ONE line (seen 15:34: "×10" broke into "×1 / 0") */
  .buybar button.buy { gap: 6px; padding: 0 6px; } .buy-k { white-space: nowrap; } .buybar button.shop-more { min-width: 62px; padding: 0 6px; }
  button.shop-back { display: flex; align-items: center; gap: 8px; min-height: 44px; margin: 8px 0 2px; padding: 0 12px 0 8px; font-size: 13px; border-radius: 0; background: transparent; border: 1px solid var(--line); color: var(--text); }
  .shop-back .gi { width: 18px; height: 18px; }
}
/* ---- popups over the game: the Inventory (everything that opens) and the "?" / details sheet */
.dlg { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; padding: 16px; background: rgba(6, 9, 10, .74); }
#crate-dlg { z-index: 64; }
#crate-dlg.card-dlg { z-index: 74; }   /* a commander card can be opened from a turned card of the reveal (z 70): it sits over it */
.dlg-box { position: relative; width: min(540px, 100%); max-height: 100%; display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--line); animation: dlg-in .2s ease-out 1; }
@keyframes dlg-in { from { transform: translateY(18px); opacity: 0; } to { transform: none; opacity: 1; } }
.dlg-head { flex: none; display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 12px; border-bottom: 1px solid var(--line); color: var(--gold); }
.dlg-head strong { font-size: 18px; color: var(--text); }
.dlg-head > .gi { width: 20px; height: 20px; }
.dlg-box > .odds-sheet { margin: 0; border: 0; border-radius: 0; overflow-y: auto; padding: 8px 12px 12px; }
.inv-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 6px 12px 12px; -webkit-overflow-scrolling: touch; }
.card.crate.inv-row { display: flex; align-items: center; gap: 8px; }
.inv-name { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.inv-row button { min-height: 44px; flex: none; white-space: nowrap; }
.inv-row button.primary { padding: 0 10px; }
.inv-empty { display: flex; align-items: center; gap: 10px; padding: 10px 0; }
.inv-empty .why-text { flex: 1; margin: 0; }
/* invite codes (14/11): my code + copy, the box to type a friend's code */
.card.invite { border-color: var(--crate); }
.card.invite .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.invite-code { font: 700 18px/1.2 ui-monospace, Consolas, monospace; letter-spacing: 2px; color: var(--crate); user-select: all; min-width: 0; }
.card.invite button { min-height: 44px; flex: none; white-space: nowrap; }
.invite-form { margin-top: 6px; }
.invite-form .invite-input { flex: 1 1 120px; min-width: 0; min-height: 44px; text-transform: uppercase; letter-spacing: 1px; }
.card.invite .why-text { margin: 4px 0 0; }
.sure-chip { font-size: 12px; }
.details-list { display: flex; flex-direction: column; margin: 6px 0; }
.details-row { display: flex; align-items: center; gap: 10px; padding: 5px 0; border-bottom: 1px solid #2b3538; }
.details-row strong { display: block; }
.details-sub { font-size: 12px; color: var(--r); }
.gift-chip .pill.zero { color: var(--dim); background: #1b2225; border-color: var(--line); }
@media (orientation: portrait) and (max-width: 560px) {
  #view-shop { margin-top: -4px; }
  .dlg.inv { padding: 0; align-items: stretch; }
  .inv .dlg-box { width: 100%; height: 100%; border: 0; padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom); }
  .card.crate.inv-row { gap: 6px; } .inv-row button.primary { padding: 0 8px; }   /* 390 px: with "?", "1" and "x10" the name column was 88 px and "Commander" (90 px) broke in two */
}
/* wide screen: the Shop takes the whole view (the map is hidden while it is open); stage left, rail on the right */
@media (min-width: 900px) and (min-height: 521px) {
  body[data-tab="shop"] #app { grid-template-columns: minmax(0, 1fr); grid-template-areas: "top" "notices" "tabs" "panel" "dock"; }
  body[data-tab="shop"] #app #map-pane { display: none; }
  .shop-rest { max-width: 780px; margin: 0 auto; padding-top: 10px; }
  .shop-crates { display: grid; grid-template-columns: minmax(0, 1fr) 264px; grid-template-rows: minmax(0, 1fr) auto auto auto; }
  .shop-crates > .stage { grid-column: 1; grid-row: 1; }
  .shop-crates > .rail { grid-column: 2; grid-row: 1 / -1; flex-direction: column; overflow-x: hidden; overflow-y: auto; border-top: 0; border-left: 1px solid var(--line); padding: 10px; gap: 8px; }
  .shop-crates > .stage-strip, .shop-crates > .why-text, .shop-crates > .buyzone { grid-column: 1; }
  button.rail-b { --rh: 104px; flex: 0 0 var(--rh); width: 100%; }
  button.rail-b:not([data-n="0"]) .rail-art { justify-content: center; padding-left: 0; }
  .rail-name { font-size: 13px; padding-bottom: 5px; }
  button.rail-b { --rail-low: 34px; }
  .rail-cell:has(.face-in) .rail-rar { width: 12px; height: 12px; bottom: calc(var(--rail-low) - 14px); }
  .shop-crates[data-dir] .stage-body[class*="in-banner-fwd"] { --dx: 0px; --dy: 46px; }
  .shop-crates .stage-body[class*="in-banner-back"] { --dx: 0px; --dy: -46px; }
  .stage { --bar: 64px; } .stage-title { font-size: 30px; } .stage-top { padding: 0 16px; gap: 10px; }
  .group-big .pack-plate, .group-big.acc .pack-plate { font-size: 17px; padding-bottom: 12px; }
  .group-small { padding-left: 16px; } .group-small.acc .pack-plate { font-size: 14px; }
  .buybar { max-width: 560px; } .buybar button.buy { min-height: 58px; font-size: 18px; }
  .solo-art { inset: 0 auto 0 50%; height: 100%; aspect-ratio: 3 / 4; transform: translateX(-20%); border: 0 solid var(--r); border-width: 0 2px; }
  .solo-plate { right: auto; bottom: 22%; padding: 0 0 0 64px; background: none; max-width: 42%; gap: 8px; }
  .solo-name { font-size: 58px; } .solo-title { font-size: 18px; } .rar-badge, .fac-mark { font-size: 14px; } .solo-own { font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) { .stage-body, .inv-chip.new, .dlg-box, button.rail-b.on::after { animation: none !important; } }
/* phone landscape (844x390, 12/11): the panel is about 278 px high. Stage on the left, as high as the panel; rail, sentence
   and buy bar in one column on the right: nothing scrolls. The stage keeps the panel gutter, where the chat handle stands. */
@media (orientation: landscape) and (max-height: 520px) {
  #view-shop { margin-left: 0; } .shop-rest { padding-left: 0; }
  .shop-crates { display: grid; grid-template-columns: minmax(0, 1fr) 276px; grid-template-rows: auto auto auto minmax(0, 1fr); min-height: 0; }
  .shop-crates > .stage { grid-column: 1; grid-row: 1 / -1; }
  .shop-crates > .rail { grid-column: 2; grid-row: 1; border-top: 0; border-left: 1px solid var(--line); }

  .shop-crates > button.stage-strip { grid-column: 2; grid-row: 2; min-height: 44px; padding-left: 10px; border-left: 1px solid var(--line); }
  .shop-crates > .why-text { grid-column: 2; grid-row: 3; border-left: 1px solid var(--line); }
  .shop-crates > .buyzone { grid-column: 2; grid-row: 4; align-self: stretch; display: flex; flex-direction: column; justify-content: flex-end; border-left: 1px solid var(--line); }
  .buybar { gap: 6px; } .buybar button.buy { min-height: 48px; gap: 6px; font-size: 14px; padding: 0 4px; } .buy-k { font-size: 16px; }
  @media (pointer: coarse) { .buybar button.buy > svg { display: none; } .buybar button.shop-more { min-width: 56px; padding: 0 4px; } }
  .stage { --bar: 48px; } .group-small { min-height: 66px; padding-right: 108px; } .stage:has(.group-small) .stage-info { flex-direction: row; }   /* a 90 px row: the two buttons side by side */
  /* one commander alone: a framed picture as high as the stage, the words beside it (the art over the whole stage showed a forehead) */
  .solo-art { inset: 0 54px 0 auto; height: 100%; aspect-ratio: 3 / 4; border: 0 solid var(--r); border-width: 0 2px; }   /* 54 px: the 'return' glyph stands beside the picture, not on the face */
  .solo-plate { right: auto; bottom: 0; padding: 0 0 8px 12px; background: none; max-width: 52%; gap: 2px; }
  .solo-name { font-size: 26px; }
}
/* the commander's card (12/11 10:50): big art, then one button per thing to read */
button.solo-art { padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; min-height: 0; cursor: pointer; }
button.solo-art:not(:disabled):active { transform: none; margin: 0; }
button.card-btn { min-height: 44px; padding: 0 12px; border-radius: 0; margin-top: 4px; }
button.details-row { width: 100%; text-align: left; background: none; border: 0; border-bottom: 1px solid #2b3538; border-radius: 0; box-shadow: none; min-height: 48px; font-weight: 400; }
/* the commander card (owner 12/11 11:55): the art is the whole card; name plate, detail and symbols lie OVER it on a fade */
.dlg-box.cc { width: min(460px, 100%); height: min(820px, 100%); border-color: #2b3538; overflow: hidden; background: #10151a; touch-action: pan-y; }
.dlg-box.cc.still { animation: none; }
.cc-art { position: absolute; inset: 0; overflow: hidden; container-type: size; background: #222025; }
/* (before ART-L) the card's picture (owner's ART-C C box 12/11 20:45): as tall as the card, unless that cuts the figure at the sides (Sif's hand and
   hilt, Dagny's hilt); then as wide as the figure needs (--bw, its share of the bust + a margin), standing on the bottom edge and
   centred on the figure (--bx). Above it the card shows the picture's own slate (#222025), the top of the picture fades into it. */
/* ART-L 13/11 (Phil: the phone cover crop cut Astrid's axe blade and Ylva's axe head; the 64cqh floor forced it): no floor any more.
   The picture is as large as the card allows WITHOUT cutting the figure (--bw = its width share + 6 % margin, never taller than
   the card), centred on the figure; wider than the card it still covers it, narrower it stays inside it (the slate of the
   picture meets the card's #222025: Ylva's bright corner was normalised for this, tools/art/process.py SLATE_LIKE). */
/* --plate (crates.js, measured): the picture stands on the top of the name plate + symbols and fades out over its last 5 %, so
   a low weapon (Ylva's axe head) is above the name, never under it. */
/* ART-M 13/11 (supervisor, Dagny box 13:55): the two sizes are taken on the FIGURE, not on the picture frame — the picture is
   as large as the figure's HEIGHT filling the space above the plate (--bh) or as its WIDTH filling the card (--bw), whichever
   is smaller, so nothing is ever cut. --bbot: her lowest pixel stands ON the plate, not the picture's edge, so the figure is
   as big and as high as the card allows. What is still left over a very wide pose (Dagny at 390x844: her figure is wider than
   the space above the plate, 0.69 against 0.55) is the owner's call — closing it would cut her elbow and her sword hilt by
   about 49 px a side. 3 cqh of headroom is kept over the tallest figure (Sif's head was touching the card's edge). Top fade:
   never deeper than the figure's own top margin (--bt), so Sif's hair top is not washed out. */
.cc-art .bust { top: auto;
  --picw: min(calc((100cqh - var(--plate, 0px) - 3cqh) * .75 / var(--bh, 1)), calc(100cqw / var(--bw, .75)));
  --pich: calc(var(--picw) * 4 / 3);
  width: var(--picw); bottom: calc(var(--plate, 0px) - (1 - var(--bbot, 1)) * var(--pich));
  --mtop: min(7%, calc(var(--bt, .07) * 100%));
  transform: translateX(clamp(min(0px, calc(100cqw - 100%)), calc(50cqw - var(--bx, .5) * 100%), max(0px, calc(100cqw - 100%))));
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 var(--mtop), #000 95%, transparent); mask-image: linear-gradient(180deg, transparent, #000 var(--mtop), #000 95%, transparent); }
.cc-close { position: absolute; z-index: 3; top: calc(6px + env(safe-area-inset-top)); right: 6px; background: rgba(12, 16, 18, .8); }
.cc-over { position: absolute; z-index: 2; left: 0; right: 0; bottom: 0; max-height: 100%; display: flex; flex-direction: column; padding-top: 56px; pointer-events: none;
  background: linear-gradient(180deg, rgba(8, 11, 12, 0), rgba(8, 11, 12, .5) 64px, rgba(8, 11, 12, .68)); }
.cc-over > * { pointer-events: auto; }
.cc-plate { flex: none; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; padding: 0 12px 6px; pointer-events: none; text-shadow: 0 1px 2px #000, 0 0 10px rgba(0, 0, 0, .8); }
/* the detail: shut = no height (0fr), open = its own height, never more than about half the card; the plate above it rides up */
.cc-drawer { display: grid; grid-template-rows: 0fr; min-height: 0; visibility: hidden; transition: grid-template-rows .28s cubic-bezier(.2, .7, .2, 1), visibility 0s .28s; }
.cc[data-open="1"] .cc-drawer { grid-template-rows: 1fr; visibility: visible; transition-delay: 0s, 0s; }
.cc-panel { min-height: 0; max-height: min(46cqh, 380px); overflow: hidden; padding: 0 12px; text-shadow: 0 1px 2px #000, 0 0 8px rgba(0, 0, 0, .85); }
.cc[data-open="1"] .cc-panel { overflow-y: auto; }
.dlg-box.cc { container-type: size; }
.cc-skill, .cc-line { display: flex; align-items: center; gap: 10px; padding: 5px 0; border-top: 1px solid rgba(255, 255, 255, .16); }
.cc-skill strong { display: block; }
.cc-skill .details-sub { color: #e6d9b8; }
/* 14/11 skill values: what each story skill does in numbers, under its line */
.cc-fx { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.cc-chip { display: inline-flex; align-items: center; gap: 4px; padding: 1px 6px; font-size: 12px; color: #f3ead2; background: rgba(8, 11, 12, .55); border: 1px solid rgba(255, 255, 255, .18); }
.cc-chip b { color: var(--rl, var(--r)); }
.cc-chip.up { border-color: rgba(120, 200, 120, .55); } .cc-chip.up b { color: #9fe39a; }
.cc-chip.down { border-color: rgba(230, 120, 100, .55); } .cc-chip.down b { color: #f0a090; }
.cc-chip svg { width: 12px; height: 12px; }
/* SKILL-VERBS 14/11: the shouted signature leads its skill's chips */
.cc-chip.cc-shout { border-color: var(--r); font-weight: 800; letter-spacing: .04em; }
/* battle report: one line per verb that fired, in round order */
.verb-lines { margin: 6px 0; padding: 6px 8px; border-left: 3px solid var(--accent, #d9a441); background: rgba(255, 255, 255, .04); }
.verb-lines p { margin: 2px 0; } .verb-lines .off { opacity: .7; }
.cc-n { flex: none; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-weight: 800; color: var(--r); border: 1px solid var(--r); background: rgba(8, 11, 12, .45); text-shadow: none; }
.cc-line { justify-content: space-between; } .cc-line span { color: #cfd6d6; font-size: 13px; }
.cc-quote { font-size: 16px; font-style: normal; color: #fff; margin: 10px 0 14px; }
.cc-power { display: flex; gap: 8px; align-items: center; font-size: 14px; color: var(--r); margin: 0 0 8px; text-transform: uppercase; letter-spacing: .06em; }
.cc-power .gi { width: 16px; height: 16px; }
.cc-story { font-size: 14px; line-height: 1.45; color: #dfe6e6; margin: 0 0 10px; }
.cc-fight { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; line-height: 1.4; color: var(--r); margin: 0 0 12px; }
.cc-fight .gi { flex: none; width: 16px; height: 16px; margin-top: 1px; }
.cc-fight span { color: #dfe6e6; }
/* the symbols: no word, no ground, set in the bottom edge of the frame; the one that is open takes the colour of the rarity
   and a mark that merges with the frame line */
.cc-tabs { flex: none; display: flex; align-items: stretch; padding: 0 4px env(safe-area-inset-bottom); }
button.cc-tab { position: relative; flex: none; width: 52px; min-width: 44px; min-height: 44px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 0; border: 0;
  background: none; box-shadow: none; color: #fff; opacity: .86; filter: drop-shadow(0 1px 2px #000) drop-shadow(0 0 6px rgba(0, 0, 0, .7)); }
button.cc-tab .gi { width: 22px; height: 22px; }
button.cc-tab:hover, button.cc-tab:focus-visible { opacity: 1; background: none; }
button.cc-tab.on { color: var(--r); opacity: 1; outline: none; }
button.cc-tab.on::after { content: ""; position: absolute; left: 12px; right: 12px; bottom: 0; height: 3px; background: var(--r); }
button.cc-arrow { margin-left: auto; }
.cc[data-open="0"] button.cc-arrow .gi { animation: cc-nudge 1.2s ease-in-out .6s 2; }
@keyframes cc-nudge { 0%, 100% { transform: none; } 50% { transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .cc-drawer { transition: none; } .cc[data-open="0"] button.cc-arrow .gi { animation: none; } }
/* SHOP-SWIPE 14/11 (owner 17:22: "on mobile it shouldn't be full full screen, let the player tap around the card to quit it instead of
   only the x"): the card leaves a band of the game all round; a tap on that band closes it (crates.js renderSheet, e.target === d) */
@media (orientation: portrait) and (max-width: 560px) {
  .dlg.card-dlg { padding: max(54px, calc(env(safe-area-inset-top) + 40px)) 20px max(46px, calc(env(safe-area-inset-bottom) + 30px)); }
  .dlg.card-dlg .dlg-box.cc { width: 100%; height: min(100%, 720px); } }
/* R3-H (owner 12/11 12:09: "the background of the profile, better to show its rarity colour tone") + 14:29 ("too much: the
   colour should be BEHIND the profile, the head at the first layer, the colour maybe -50 %"): every face tile stands on a
   ground in the tone of its rarity. ONE rule for every screen; --tone comes from balance.json (assets.js setRarities writes
   [data-rar="epic"] { --tone: ... }). The ground is a flat field at 42 % of the tone (it was a ring at 82 % laid OVER the
   picture) and the commander is drawn over it from its cut-out file (<name>-cut.webp, tools/art/process.py --cut). */
[data-rar] { --ground: radial-gradient(120% 120% at 50% 35%, color-mix(in srgb, var(--tone, #55666a) 42%, transparent), color-mix(in srgb, var(--tone, #55666a) 30%, transparent)); }
img[data-rar] { object-fit: cover; background: var(--ground) #10151a; border-color: color-mix(in srgb, var(--tone, #55666a) 70%, #10151a); }
.group-small .pack-hero[data-biome] .hero-in::before { content: ""; position: absolute; inset: 0; background: var(--biome, #10151a); pointer-events: none; }
.group-small .pack-plate { z-index: 1; }
/* a commander not yet yours: the FACE loses its colours (its picture keeps only its light, in the tone of the ground), the ground keeps the tone of its rarity
   (the whole button was greyed: a locked legendary and a locked common looked the same) */
.cmd-opt.locked, .skill-tab.locked, .roster-row button.skill-tab.locked { filter: none; opacity: .72; }
.cmd-opt.locked img[data-rar], .skill-tab.locked img[data-rar] { object-position: 0 -300vh; background: var(--art) center / cover no-repeat, var(--ground) #10151a; background-blend-mode: luminosity, normal; }
/* R3-H: phone landscape - the card is wide, the art is a column on the left and the name plate, the detail and the symbols
   stand in a column BESIDE the face (open, they lay across the commander's eyes) */
@media (orientation: landscape) and (max-height: 560px) {
  .dlg.card-dlg { padding: 10px 56px; }   /* SHOP-SWIPE: a band left and right to tap out */
  .dlg-box.cc { width: min(760px, 100%); height: 100%; }   /* ART-L tried 75vh + 340 px (less empty navy): the open detail then wrapped and cut its 3rd skill; kept 760 */
  .cc-art { right: auto; width: 75cqh; }
  .cc-art .bust { --picw: min(calc(97cqh * .75 / var(--bh, 1)), calc(100cqw / var(--bw, .75)));   /* the plate is BESIDE the art here: full height, no drop */
    width: var(--picw); bottom: calc(0px - (1 - var(--bbot, 1)) * var(--pich));
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 var(--mtop, 7%)); mask-image: linear-gradient(180deg, transparent, #000 var(--mtop, 7%)); }
  .cc-over { left: 75cqh; top: 0; padding-top: 52px; justify-content: flex-end; background: linear-gradient(180deg, rgba(8, 11, 12, .2), rgba(8, 11, 12, .5)); }
  .cc-plate .solo-name { font-size: 28px; }
}

/* ---- MAP FIRST (owner 12/11 16:03, Last Shelter captures): coordinates of the centre + arrow back to my own city on the map (every screen);
   on a phone held upright the map takes the whole World screen and the lists of the view are one sheet behind the march glyph */
/* GROUND-1115: the two ways of a march on the sheet of a place (round the hard ground / straight across) */
.way-row { display: flex; gap: 6px; margin: 6px 0; }
.way-row[hidden] { display: none; }
.way-row .way-opt { flex: 1 1 0; min-height: 44px; justify-content: center; gap: 8px; border: 1px solid var(--line); border-radius: 4px; background: rgba(27, 34, 37, .9); color: var(--text); }
.way-row .way-opt.on { border-color: #e0b23a; box-shadow: inset 0 0 0 1px #e0b23a; }
/* WORLD-1115: the overview map, top-left of the map pane (bottom-left = list and X / Y, right = zoom buttons) */
.map-mini { position: absolute; left: 6px; top: 6px; z-index: 2; width: 96px; height: 96px; border: 1px solid rgba(255, 255, 255, .6); border-radius: 4px; box-shadow: 0 1px 6px rgba(0, 0, 0, .55); background: #1d4d7c; touch-action: none; cursor: pointer; }
@media (max-width: 720px) { .map-mini { width: 84px; height: 84px; } }
@media (min-width: 1100px) { .map-mini { width: 132px; height: 132px; } }
.map-hud { position: absolute; left: 6px; bottom: 6px; z-index: 2; display: flex; gap: 6px; align-items: stretch; }
button.map-list, button.map-xy { min-height: 44px; margin: 0; padding: 0 10px; display: inline-flex; align-items: center; gap: 6px; background: rgba(27, 34, 37, .9); border: 1px solid var(--line); border-radius: 4px; color: var(--text); font-variant-numeric: tabular-nums; }
button.map-list { display: none; position: relative; width: 44px; padding: 0; justify-content: center; }
button.map-list .badge { position: absolute; top: -5px; right: -5px; }
button.map-xy { background: rgba(16, 21, 26, .72); border-color: transparent; font-size: 14px; }
button.map-xy b { min-width: 2ch; text-align: left; color: #8fd0e8; font-weight: 600; margin-right: 4px; }
button.map-home { position: absolute; z-index: 2; width: 56px; height: 56px; min-height: 0; min-width: 0; margin: -28px 0 0 -28px; padding: 0; border: 0; border-radius: 50%; background: none; color: #fff; }
button.map-home[hidden] { display: none; }
.mh-in { position: absolute; inset: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0; border-radius: 50%; background: rgba(16, 21, 26, .86); box-shadow: 0 0 0 2px var(--self); line-height: 1; }
.mh-in svg { width: 15px; height: 15px; }
.mh-d { font-size: 11px; font-variant-numeric: tabular-nums; }
.mh-arrow { position: absolute; left: 50%; top: 50%; width: 22px; height: 22px; margin: -11px 0 0 -11px; color: var(--self); pointer-events: none; }
.mh-arrow svg { display: block; width: 22px; height: 22px; stroke-width: 3.2; filter: drop-shadow(0 0 2px #000); }
button.wl-close { display: none; }
@media (orientation: portrait) and (max-width: 560px) {
  body[data-tab="world"] #app { grid-template-rows: auto auto minmax(0, 1fr) 0 auto auto; }
  body[data-tab="world"] #app #map-pane { height: auto; min-height: 0; border-bottom: 0; }
  body[data-tab="world"] #map-pane .map-btns { flex-direction: column; }
  button.map-list { display: inline-flex; }
  body[data-tab="world"] #panel { position: fixed; z-index: 28; left: 0; right: 0; bottom: 0; max-height: 68vh; max-height: 68dvh; padding: 0 12px 16px 42px; background: var(--panel); border-top: 2px solid var(--gold);
    transform: translateY(104%); visibility: hidden; }
  body[data-tab="world"].world-moved #panel { transition: transform .22s ease-out, visibility 0s .22s; }
  body[data-tab="world"].world-open #panel { transform: none; visibility: visible; }
  body[data-tab="world"].world-open.world-moved #panel { transition: transform .22s ease-out; }
  body[data-tab="world"] button.wl-close { display: flex; position: sticky; top: 0; z-index: 3; width: 100%; min-height: 44px; margin: 0 0 4px; justify-content: center; background: var(--panel); border: 0; border-radius: 0; }
}
/* phone on its side (12/11 16:28): the same map-first World. The map takes the panel's place, the lists slide in from the left */
@media (orientation: landscape) and (max-height: 520px) {
  body[data-tab="world"] #app { grid-template-areas: "tabs top top" "tabs notices notices" "tabs map map" "tabs dock dock"; }
  button.map-list { display: inline-flex; }
  #map-pane .map-btns { position: absolute; right: 6px; bottom: 6px; z-index: 2; flex-direction: column; padding: 0; background: none; }
  #map-pane .map-btns button { width: 44px; min-height: 44px; }
  body[data-tab="world"] #panel { position: fixed; z-index: 28; left: 84px; top: 0; bottom: 0; width: min(430px, 52vw); padding: 0 12px 16px; overflow-y: auto; background: var(--panel); border-right: 2px solid var(--gold);
    transform: translateX(-130%); visibility: hidden; }
  body[data-tab="world"].world-moved #panel { transition: transform .22s ease-out, visibility 0s .22s; }
  body[data-tab="world"].world-open #panel { transform: none; visibility: visible; }
  body[data-tab="world"].world-open.world-moved #panel { transition: transform .22s ease-out; }
  body[data-tab="world"] button.wl-close { display: flex; position: sticky; top: 0; z-index: 3; width: 100%; min-height: 44px; margin: 0 0 4px; justify-content: center; background: var(--panel); border: 0; border-radius: 0; }
}
@media (prefers-reduced-motion: reduce) { body[data-tab="world"] #panel { transition: none; } }

/* ---- "?" every commander (owner 13/11): a grid of every profile of the game, a tap opens the full card over it */
button.roster-btn { flex: none; display: inline-flex; align-items: center; gap: 3px; min-height: 44px; min-width: 44px; padding: 0 9px; border-radius: 0;
  background: rgba(16, 20, 22, .88); border: 1px solid rgba(224, 178, 58, .45); color: var(--gold); box-shadow: none; }
button.roster-btn .gi { width: 17px; height: 17px; }
button.roster-btn b { font-size: 18px; font-weight: 800; line-height: 1; }
.roster .dlg-box.roster-box { width: min(640px, 100%); height: min(900px, 100%); }
.roster-have { font-size: 12px; font-weight: 700; color: var(--dim); }
.roster-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 12px 14px; -webkit-overflow-scrolling: touch; }
.roster-rar { display: flex; align-items: center; gap: 6px; margin: 12px 0 7px; font-size: 13px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--r); }
.roster-rar .gi { width: 15px; height: 15px; fill: currentColor; fill-opacity: .45; }
.roster-rar small { color: var(--dim); font-weight: 700; }
/* 4 a row: every rarity has 4 commanders today (3 left one alone on a 2nd row at 390 px); more wrap by 4. 12 tracks so a rarity
   with MAIN heroes (13/11: Sif + Torstein, legendary) shows them 2 a row, large, and its others 3 a row (5 = 2 + 3, none alone) */
.roster-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 7px; }
.roster-grid > .roster-cell { grid-column: span 3; }
.roster-grid.has-main > .roster-cell { grid-column: span 4; }
.roster-grid.three > .roster-cell { grid-column: span 4; }
.roster-grid.has-main > .roster-cell.main { grid-column: span 6; aspect-ratio: 1 / 1; border-top-width: 3px; }
.roster-main { position: absolute; top: 5px; left: 5px; padding: 2px 6px; font-size: 11px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  color: #15110a; background: var(--r); }
.roster-cell.main .roster-name strong { font-size: 16px; }
.roster-cell.main .roster-name small { font-size: 12px; }
button.roster-cell { position: relative; display: block; width: 100%; aspect-ratio: 3 / 4; min-height: 0; padding: 0; overflow: hidden; border-radius: 0;
  border: 1px solid #2b3538; border-top: 2px solid var(--r); box-shadow: none; background: radial-gradient(90% 70% at 50% 32%, #33363f, #1b1d23 78%); }
.roster-art { position: absolute; inset: 0; container-type: size; overflow: hidden; }
.roster-art .bust { left: 50%; top: 2cqh; width: auto; height: 84cqh; transform: translateX(calc(var(--fx, .5) * -100%)); transition: transform .25s ease-out, filter .25s; }
button.roster-cell:not(.owned) .bust { filter: saturate(.7) brightness(.86); }
button.roster-cell:hover .bust, button.roster-cell:focus-visible .bust { filter: brightness(1.12); transform: translateX(calc(var(--fx, .5) * -100%)) scale(1.04); }
button.roster-cell:not(:disabled):active { transform: scale(.96); margin: 0; }
.roster-own { position: absolute; top: 4px; right: 4px; display: flex; padding: 2px; color: var(--ok, #7fd08a); background: rgba(8, 11, 12, .85); }
.roster-own .gi { width: 13px; height: 13px; }
.roster-name { position: absolute; left: 0; right: 0; bottom: 0; display: flex; flex-direction: column; padding: 16px 5px 4px; text-align: left; line-height: 1.15;
  background: linear-gradient(180deg, rgba(8, 11, 12, 0), rgba(8, 11, 12, .95) 55%); }
.roster-name strong { font-size: 13px; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.roster-name small { font-size: 10px; color: #b9c2bd; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (orientation: portrait) and (max-width: 560px) { .dlg.roster { padding: 0; } .roster .dlg-box.roster-box { width: 100%; height: 100%; border: 0; padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom); } .roster-grid { gap: 5px; } .roster-scroll { padding: 2px 8px 12px; } .roster-name strong { font-size: 12px; } }
@media (prefers-reduced-motion: reduce) { .roster-art .bust { transition: none; } }

/* zoom buttons: bare icons on the scene, no ground and no frame (owner 12/11 16:28, Last Shelter); the finger target stays 44 px */
#app .map-btns button, #app .map-btns button:hover, #app .map-btns button:active { width: 44px; min-width: 0; min-height: 44px; padding: 0; background: none; border: 0; border-radius: 0; box-shadow: none; color: #fff; }
#app .map-btns button svg { width: 26px; height: 26px; stroke-width: 2.4; filter: drop-shadow(0 0 2px #000) drop-shadow(0 1px 3px rgba(0, 0, 0, .9)); transition: transform .12s ease-out; }
#app .map-btns button:active svg { transform: scale(.84); }

/* ---- my march, opened by a tap on it on the map (12/11): leader, route, time left, Speed up */
.march-head { gap: 10px; align-items: center; }
.march-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.march-who strong { font-size: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.march-route { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.march-route > .gi { width: 20px; height: 20px; opacity: .7; flex: none; }
.march-place { display: inline-flex; flex-direction: column; gap: 1px; min-width: 0; }
.march-place b { font-size: 14px; }
.march-place .coord { font-size: 12px; opacity: .75; }
.march-left { font-size: 17px; margin-left: auto; color: #ffe9a8; }
.march-btns { flex-wrap: nowrap; }
.march-btns [data-testid="march-speedup"] { flex: 1 1 auto; min-height: 48px; justify-content: center; gap: 8px; font-size: 15px; }
.march-btns [data-testid="march-speedup"] .su-off { font-weight: 800; }
.march-btns [data-testid="march-speedup"] .su-pay { display: inline-flex; align-items: center; gap: 3px; padding: 2px 8px; border-radius: 4px; background: rgba(0, 0, 0, .28); font-weight: 800; }
.march-btns [data-testid="march-recall"] { min-width: 48px; min-height: 48px; flex: none; }

/* ---- THE PIN (owner 12/11 19:13): on every row, card or sheet that names a place of the World map; one tap flies the map there.
   A bare glyph, no ground, no frame; the finger target is 44 px whatever the size of the row (the ::after box takes the tap). */
.pin { position: relative; cursor: pointer; -webkit-tap-highlight-color: transparent; user-select: none; -webkit-user-select: none; }
.pin::after { content: ''; position: absolute; left: 50%; top: 50%; width: max(100%, 44px); height: 44px; transform: translate(-50%, -50%); }
.pin > svg, .stat.pin .ico { width: 18px; height: 18px; color: var(--gold); flex: none; }
.pin.bare > svg { width: 22px; height: 22px; }
.pin:active > svg { transform: scale(.82); }
.pin:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.msg .pin { display: inline-flex; vertical-align: -3px; margin: 0 2px; }
.find-hit .pin { margin-left: 4px; }
.march-head .pin.bare { margin: 0 8px; }
@media (prefers-reduced-motion: reduce) { .pin:active > svg { transform: none; } }

/* ---- GO TO X / Y (owner 12/11 18:2x): long hold / right click on the map, or a tap on the X / Y bar. Sits above that bar. */
.goto { position: absolute; z-index: 3; left: 6px; bottom: 56px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; max-width: calc(100% - 68px); margin: 0; padding: 6px 8px; background: rgba(16, 21, 26, .95); border: 1px solid var(--line); border-radius: 4px; box-shadow: 0 6px 18px rgba(0, 0, 0, .5); }
.goto[hidden] { display: none; }
.goto-f { display: inline-flex; align-items: center; gap: 5px; font-size: 14px; }
.goto input.goto-in { width: 3.6em; min-height: 44px; padding: 0 4px; text-align: center; font-size: 16px; font-variant-numeric: tabular-nums; }
.goto button.goto-ok { min-height: 44px; min-width: 56px; margin: 0; }
.goto button.small { min-width: 44px; min-height: 44px; margin: 0; }
.goto-err { flex-basis: 100%; margin: 0; max-width: 250px; }

/* ---- 13/11 23:15 (owner): commanders shown at HALF BODY wherever they lead something, troops as picture cards */
.hb { position: relative; display: block; overflow: hidden; aspect-ratio: 5 / 4; flex: none; border-radius: 6px;
  background: var(--ground, none) #10151a; border: 2px solid color-mix(in srgb, var(--tone, #55666a) 75%, #10151a); }
.hb .hb-img { position: absolute; left: 0; top: 0; width: 100%; height: auto; max-width: none; aspect-ratio: 3 / 4;
  margin-top: calc(var(--bt, .03) * -133.3%); object-fit: cover; }
.hb::after { content: ""; position: absolute; inset: auto 0 0 0; height: 22%; background: linear-gradient(180deg, transparent, rgba(10, 13, 15, .75)); pointer-events: none; }
.march-body { display: flex; gap: 10px; align-items: flex-start; flex-wrap: wrap; }
.march-body > .stats { flex-basis: 100%; }
.march-hb { width: 132px; }
.march-side { flex: 1 1 160px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.march-head { min-height: 0; }
/* 14/11 11:25 (Phil, phone capture: "gaps everywhere, the profile is really tiny, nothing aligned"): the commander stands WHOLE on
   the left (3:4 picture, its empty top cut by --bt so the box ends at the picture's bottom), as tall as the column beside it:
   route (one line per place, arrow down), troops, then load and time on the last line. No band of empty scene under the face. */
.sheet[data-kind="march"] .march-body { flex-wrap: nowrap; align-items: stretch; gap: 12px; }
.sheet[data-kind="march"] .march-hb { --mw: clamp(112px, min(35vw, 26vh), 176px); width: var(--mw); aspect-ratio: auto;
  height: calc(var(--mw) * 1.3333 * (1 - var(--bt, .03))); }
.sheet[data-kind="march"] .march-side { justify-content: space-between; gap: 8px; }
.sheet[data-kind="march"] .sheet-info > .march-side, .sheet[data-kind="march"] .march-side > div { margin: 0; }
.sheet[data-kind="march"] .march-route { display: grid; grid-template-columns: minmax(0, max-content) max-content; column-gap: 10px; row-gap: 2px;
  align-items: baseline; justify-content: start; }
.sheet[data-kind="march"] .march-route > .gi { grid-column: 1 / -1; transform: rotate(90deg); margin-left: 4px; width: 16px; height: 16px; }
/* both coordinates in one column (subgrid; without it a place falls back to name over coordinates) */
.sheet[data-kind="march"] .march-place { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: baseline; }
.sheet[data-kind="march"] .march-place b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sheet[data-kind="march"] .march-place b { font-size: 15px; }
.sheet[data-kind="march"] .march-side > .stats { align-items: center; }
.sq-menu-head .sq-menu-hb { width: 64px; }
/* troop cards: Last-Shelter-like tabs, the unit standing on its card, the count on a strip */
.tcards { display: flex; gap: 6px; flex-wrap: wrap; }
.tcard { position: relative; width: 58px; height: 84px; border-radius: 6px; overflow: hidden; display: flex; flex-direction: column; justify-content: flex-end;
  background: radial-gradient(90% 70% at 50% 40%, #33413f, #161c1f 80%); border: 1px solid #4d5c5f; }
.tcard img { position: absolute; left: 50%; top: 2px; height: calc(100% - 22px); width: auto; transform: translateX(-50%); }
.tcard-n { position: relative; display: flex; gap: 3px; align-items: center; justify-content: center; height: 20px; font-size: 12px; font-weight: 700;
  background: rgba(8, 11, 12, .85); border-top: 1px solid var(--gold); }
.tcard-n .glyph { width: 9px; height: 9px; }
.tcard.empty { width: auto; height: auto; padding: 4px 10px; justify-content: center; }
/* the formation editor: the chosen commander large, the others as half-body tiles on one sliding row */
.sq-hero-row { display: flex; gap: 8px; align-items: stretch; }
.sq-hero { flex: none; width: 128px; display: flex; flex-direction: column; gap: 2px; align-items: center; }
.sq-hero-hb { width: 128px; }
.sq-hero-name { font-size: 14px; }
.sq-hero-row .sq-pick { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto; align-content: flex-start; scrollbar-width: thin; }
.sq-pick .sq-cand, .sq-hero-row .sq-pick .sq-cand { flex: none; width: 64px; min-width: 0; padding: 3px; }
.sq-cand .sq-cand-hb { width: 100%; }
.sq-cand.on .sq-cand-hb { border-color: var(--gold); }
.sq-choose .sq-cand.small { width: 64px; }
@media (orientation: portrait) and (max-width: 560px) {
  .march-hb { width: 112px; }
  .sq-hero, .sq-hero-hb { width: 104px; }
  .tcard { width: 52px; height: 76px; }
}
.sq-hero-row .sq-pick { align-items: flex-start; }
.list-hb { width: 64px; }
.tcards.small .tcard { width: 44px; height: 62px; }
.tcards.small .tcard img { height: calc(100% - 18px); }
.tcards.small .tcard-n { height: 16px; font-size: 11px; }
@media (min-width: 900px) and (min-height: 600px) { .sheet .march-hb { width: 168px; } }
.pick-troop { height: 40px; width: auto; flex: none; margin-right: 2px; }
.sq-hero { position: relative; }
.sq-hero-name { position: absolute; left: 0; right: 0; bottom: 4px; text-align: center; text-shadow: 0 1px 3px #000, 0 0 6px #000; z-index: 1; }
@media (orientation: portrait) and (max-width: 560px) {
  .pick-troop { height: 30px; }
  #squad-sheet .pick-row { margin: 3px 0; }
  #squad-sheet .sq-sec { margin-top: 4px; }
}
#squad-sheet .pick-name:has(.pick-troop) > .glyph { display: none; } #squad-sheet .pick-name { white-space: nowrap; display: inline-flex; align-items: center; gap: 4px; }

/* 13/11 INFLUENCERS: a rarity with a `gradient` in balance.json (Viral, the rainbow tier) is painted by the rules assets.js rainbow()
   writes for its id; glyphs and figures in its colour turn through the rainbow with this one animation (only without reduced motion) */
@keyframes rfx-hue { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } }

/* ---- 14/11 FACE PROFILES + FILM SCENES (owner 02:2x: "the profile of the commander is still too tiny ... the half body on the
   profile is a bad idea, better the face profile, and the half body on the bg frame with random scene, like we see that commander
   inside the film action"). The tiles show the FACE (assets.js headshot: the bust laid so its face box fills the frame width); the
   card and the Shop stage show a FILM SCENE on the whole frame (assets.js scenePic, balance.json commanders.*.scenes), cropped on
   the face point (--sx/--sy) so a narrow phone never cuts the face away; a commander without a scene keeps its bust. */
.headshot { position: absolute; max-width: none; height: auto; aspect-ratio: 3 / 4; width: calc(100cqw / var(--hz, .5)); left: calc(var(--hl, 0) * 100cqw); top: calc(var(--ht, 0) * 100cqw); }
.roster-art .headshot { transform-origin: 50% 35%; transition: transform .25s ease-out, filter .25s; }
button.roster-cell:not(.owned) .headshot { filter: saturate(.7) brightness(.86); }
button.roster-cell:hover .headshot, button.roster-cell:focus-visible .headshot { filter: brightness(1.12); transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) { .roster-art .headshot { transition: none; } }
/* the city profile: the face in its column, the commander's scene as the ground of the block (dimmed and soft behind the words) */
.hero-face { position: relative; z-index: 1; display: block; width: 100%; height: 100%; min-height: 156px; overflow: hidden; container-type: inline-size; }
.hero-face .headshot.hero-portrait { width: calc(100cqw / var(--hz, .5)); height: auto; min-height: 0; object-fit: fill; object-position: 0 0; }
.hero.has-scene { background: #141a1c; isolation: isolate; }
.hero.has-scene::before { content: ""; position: absolute; inset: -8px; z-index: -1; background: var(--hero-bg) 50% calc(var(--sy, .3) * 100%) / cover no-repeat;
  filter: blur(2px) brightness(.62) saturate(1.05); }
.hero.has-scene::after { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(90deg, rgba(10, 13, 15, .2) var(--hero-w), rgba(10, 13, 15, .55) 62%, rgba(10, 13, 15, .82)); }
.hero.has-scene .hero-text { position: relative; z-index: 1; text-shadow: 0 1px 2px #000; }
/* the card / the stage: the scene fills the frame */
/* across: centred (the prompt centres the figure; a 390 px phone keeps 82 % of the width, so a face at x >= .12 stays in, and the
   weapon at the far side is not the part cut: Torstein's axe blade was, framed on his face); down: on the face (phone landscape
   keeps 75 % of the height) */
.cc-art .cc-scene, .solo-art .solo-scene { position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; object-fit: cover;
  object-position: 50% calc(var(--sy, .3) * 100%); }
.dlg-box.cc.has-scene .cc-art { background: #0c0f11; }
/* UX-SCENE-CARD 14/11: the veil stays see-through (R3-G: nothing opaque over the art, < .8); the name and the symbols keep their
   own shadows, measured on every scene by tests/scene_card_test.py */
.dlg-box.cc.has-scene .cc-over { background: linear-gradient(180deg, rgba(8, 11, 12, 0), rgba(8, 11, 12, .6) 72px, rgba(8, 11, 12, .78)); }
.cc-scene-ground { display: none; }
/* a shot can be bright where the bust's slate never was (Harald's title measured 2.4 on his sky): on a scene card the small texts in
   the rarity colour take that colour lit by half (--rl; the badge and the frame keep the pure one), the dim ones go light, the
   skill number gets a deeper chip, and every text of the plate and of the detail a tighter shadow */
.dlg-box.cc.has-scene { --rl: color-mix(in srgb, var(--r) 50%, #fff); }
.dlg-box.cc.has-scene .solo-title, .dlg-box.cc.has-scene .cc-n, .dlg-box.cc.has-scene .cc-power, .dlg-box.cc.has-scene .cc-fight { color: var(--rl); }
.dlg-box.cc.has-scene .cc-n { background: rgba(8, 11, 12, .66); }
.dlg-box.cc.has-scene .cc-panel .stat.dim { color: #dfe6e6; }
.cc-line strong { color: var(--c, inherit); }   /* Status: the rarity's and the faction's own colour (crates.js --c), lit by half on a scene */
.dlg-box.cc.has-scene .cc-line strong { color: color-mix(in srgb, var(--c, #fff) 50%, #fff); }
.dlg-box.cc.has-scene .cc-plate, .dlg-box.cc.has-scene .cc-panel { text-shadow: 0 0 2px #000, 0 1px 3px #000, 0 0 10px rgba(0, 0, 0, .9); }
/* the shot moves like a film: a slow push toward the face, with a drift drawn at each opening (crates.js cardScene: --dx/--dy, and
   --t0 continues it when the card is rebuilt); reduced motion = the still frame.
   The push turns round the face, but never round a point closer than 18 % / 14 % to an edge: the margin the scale adds on that
   side (.1 x that share) then always exceeds the drift (1.6 % / 1.2 % x 1.1), so the shot covers the card at every moment. */
.cc-scene { transform-origin: clamp(18%, calc(var(--sx, .5) * 100%), 82%) clamp(14%, calc(var(--sy, .3) * 100%), 86%); }
@media (prefers-reduced-motion: no-preference) {
  .cc-scene { animation: scene-push 18s cubic-bezier(.22, .61, .36, 1) var(--t0, 0ms) both; }
}
@keyframes scene-push { from { transform: scale(1.02); } to { transform: scale(1.1) translate(calc(var(--dx, 0) * 100%), calc(var(--dy, 0) * 100%)); } }

/* K6: narrow cards keep the full shot width, including hands at the edges.
   The portrait still occupies >= 75% of this phone card; its lower margin
   sits behind the name plate. Vertical drift cannot clip either side. */
@container (max-aspect-ratio: 9/16) {
  /* UX-SCENE-CARD 14/11: the card is taller than the 9:16 shot. What is left below it is the shot's own soft copy (never a
     black band with a hard edge); the shot fades into it over its last 48 px, behind the name plate. The frame is 6 px taller
     than the card, the length of the drift, so the drift never lifts its bottom edge into the card. */
  .cc-art .cc-scene { object-fit: contain; object-position: 50% 0; bottom: -6px; height: calc(100% + 6px);
    -webkit-mask-image: linear-gradient(180deg, #000 calc(177.78cqw - 48px), transparent 177.78cqw); mask-image: linear-gradient(180deg, #000 calc(177.78cqw - 48px), transparent 177.78cqw); }
  .cc-art .cc-scene-ground { display: block; position: absolute; left: -48px; right: -48px; bottom: -48px; top: calc(177.78cqw - 96px);
    background: 50% 100% / 100% auto no-repeat; filter: blur(16px) brightness(.8); }
  @media (prefers-reduced-motion: no-preference) {
    .cc-art .cc-scene { animation-name: scene-drift; }
  }
}
@keyframes scene-drift { from { transform: translateY(0); } to { transform: translateY(-6px); } }

/* A6: the picker reserves one name row for the level; long names yield with an ellipsis. */
.cmd-pick button.cmd-opt > span { display: grid; width: 100%; min-width: 0; grid-template-columns: minmax(0, 1fr) auto; column-gap: 2px; }
.cmd-pick button.cmd-opt > span > strong { grid-column: 1; grid-row: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmd-pick button.cmd-opt > span > .stats { display: contents; }
.cmd-pick button.cmd-opt .stats > .lvl { grid-column: 2; grid-row: 1; justify-self: end; white-space: nowrap; }
.cmd-pick button.cmd-opt .stats > :not(.lvl) { grid-column: 1 / -1; justify-content: center; }

/* ---- 14/11 10:12 (owner, four phone captures): the picker tile's top is the FACE edge to edge (no gap between the frame corners
   and the picture, the whole face shown, nothing laid on it); name and figures under it */
button.cmd-opt .cmd-art { position: relative; display: block; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; container-type: inline-size;
  background: radial-gradient(circle at 50% 40%, #2c363a, #12181a 78%); }
button.cmd-opt.locked .cmd-art img { filter: saturate(.6) brightness(.72); }
button.cmd-opt .cmd-info { display: flex; flex-direction: column; align-items: center; min-width: 0; padding: 2px 1px 3px; }
button.cmd-opt .cmd-info strong { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
/* the City line, the march card and the march sheet stand on the commander's film scene (assets.js sceneGround): the picture crosses
   the whole frame at the face height, a dark veil on the side of the words */
.scene-ground, .sheet[data-kind="march"] {
  background: var(--scrim, linear-gradient(90deg, rgba(10, 13, 15, .5), rgba(10, 13, 15, .8))), var(--scene-bg, none) 50% calc(var(--sy, .4) * 100%) / cover no-repeat, #182024; }
.card.hero-line.scene-ground { --scrim: linear-gradient(90deg, rgba(10, 13, 15, .82), rgba(10, 13, 15, .5) 55%, rgba(10, 13, 15, .12)); min-height: 84px; }
.hero-line .face.big { width: 64px; height: 64px; }
.card.march.scene-ground { --scrim: linear-gradient(90deg, rgba(10, 13, 15, .22), rgba(10, 13, 15, .6) 42%, rgba(10, 13, 15, .72)); }
.sheet[data-kind="march"] { --scrim: linear-gradient(180deg, rgba(18, 24, 27, .38), rgba(18, 24, 27, .8) 72%); }
.scene-ground strong, .scene-ground .stats, .sheet[data-kind="march"] .march-side, .sheet[data-kind="march"] .march-who { text-shadow: 0 1px 2px #000; }
.list-hb { width: 84px; }

/* ---- SQUAD POPUP (owner 14/11 14:05, "like Last Shelter"): a popup with a gap all around (a tap there closes it, no X),
   the gear top right widens it to the whole screen. The gap on top is where the notice bubbles land while it is open. */
.sq-dlg { position: fixed; inset: 0; z-index: 37; display: flex; align-items: center; justify-content: center;
  padding: calc(52px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom)); background: rgba(6, 9, 10, .62); }
.sq-dlg[hidden] { display: none; }
.sq-box { position: relative; width: min(460px, 100%); max-height: 100%; overflow-y: auto; overscroll-behavior: contain; background: #20282c;
  border: 2px solid var(--gold); border-radius: 14px; padding: 6px 8px 8px; box-shadow: 0 12px 40px rgba(0, 0, 0, .6); }
.sq-box.in { animation: dlg-in .18s ease-out 1; }   /* the opening draw only (squads.js drawSquad) */
.sq-dlg.full { padding: calc(10px + env(safe-area-inset-top)) 8px calc(10px + env(safe-area-inset-bottom)); }
.sq-dlg.full .sq-box { width: min(1100px, 100%); height: 100%; }
/* SQUAD-POP-MARCH (Phil 15/11: "remove the frame that s useless and gaps and the unless space ... 'done' buttom juste remove it
   and move it front of the setup symbol with a check symbol ... a taps for multi march"): ONE top line = a tab per squad, the
   total of its troops, the check, the gear. No title line, no "Troops" line, no Done bar under the sliders. */
.sq-head { flex-wrap: nowrap; gap: 6px; align-items: center; }
button.sq-gear, button.sq-ok { flex: none; min-width: 44px; min-height: 44px; }
button.sq-gear svg, button.sq-ok svg { width: 24px; height: 24px; }
.sq-total { flex: none; color: var(--gold); }
.sq-tabs { display: flex; gap: 8px; min-width: 0; overflow-x: auto; scrollbar-width: none; padding: 3px 3px 5px 4px; }
.sq-tabs::-webkit-scrollbar { display: none; }
button.sq-tab { position: relative; flex: none; width: 44px; height: 44px; min-width: 0; min-height: 0; padding: 0; border-radius: 10px;
  border: 2px solid var(--rar, #55666a); background: #10151a; color: var(--gold); opacity: .55; display: inline-flex; align-items: center; justify-content: center; }
button.sq-tab.on { opacity: 1; box-shadow: 0 0 0 2px #fff; }
button.sq-tab:disabled { opacity: .3; }
.sq-tab-img { width: 100%; height: 100%; border-radius: 8px; object-fit: cover; pointer-events: none; }
.sq-tab-n { position: absolute; left: -4px; bottom: -4px; min-width: 16px; height: 16px; line-height: 16px; border-radius: 8px; background: #10151a;
  border: 1px solid var(--gold); color: var(--gold); font-size: 10px; font-weight: 700; text-align: center; }
/* opened to march: where the squad goes (left) and what leaves (right), one line, glyphs and figures */
.sq-march { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 2px 12px; padding: 2px 2px 4px; font-size: 13px;
  border-bottom: 1px solid rgba(255, 255, 255, .08); margin-bottom: 4px; }
.sq-march .sq-est { margin-left: auto; color: var(--gold); }
.sq-march .way-row { flex: 1 1 100%; margin: 2px 0 0; }   /* GROUND-1115: the two ways, under the line of the place */
.sq-disband { margin-top: 10px; }
.sq-cols { display: flex; flex-direction: column; gap: 4px; }
.sq-dlg.full .sq-cols { display: grid; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 760px), (orientation: landscape) and (max-height: 560px) {
  .sq-dlg.full .sq-cols { grid-template-columns: minmax(260px, 2fr) minmax(0, 3fr); gap: 16px; align-items: start; }
  .sq-dlg.full .sq-box { height: auto; }   /* wide screen: the box is as tall as what it holds, no empty half under Done */
  .sq-dlg.full .sq-leads { grid-template-columns: 1fr; justify-items: center; } .sq-dlg.full .sq-slide { width: 100%; }
  .sq-dlg.full .sq-slide.lead .sq-slide-row { max-width: 360px; } .sq-dlg.full .sq-slide.deputy .sq-slide-row { max-width: 260px; }
}
/* commander + sub-commander, side by side; the lead is the bigger one */
.sq-leads { display: grid; grid-template-columns: 1.35fr 1fr; gap: 8px; align-items: end; margin-top: 2px; }
.sq-slide { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; }
.sq-slide-label { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .06em; }
.sq-slide-row { display: flex; align-items: center; gap: 2px; width: 100%; }
button.sq-arrow { flex: none; min-width: 26px; width: 26px; min-height: 44px; padding: 0; background: transparent; border: 0; color: var(--gold); }
button.sq-arrow:disabled { opacity: .25; }
.sq-slide-art { flex: 1 1 auto; min-width: 0; touch-action: pan-y; cursor: grab; border-radius: 8px; outline-offset: 2px;
  transform: translateX(var(--dx, 0)); transition: transform .18s ease-out; user-select: none; -webkit-user-select: none; }
.sq-slide-art.drag { transition: none; cursor: grabbing; opacity: .85; }
.sq-slide-art .hb { width: 100%; border: 2px solid var(--r, #55666a); }
.sq-slide-none { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; aspect-ratio: 5 / 4; width: 100%;
  border: 2px dashed var(--gold); border-radius: 6px; color: var(--gold); font-size: 12px; text-align: center; padding: 4px; }
.sq-slide-none svg { width: 26px; height: 26px; }
.sq-slide-name { font-size: 15px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-slide.deputy .sq-slide-name { font-size: 13px; }
.sq-slide-tag { font-size: 11px; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
/* the strip of faces a tap on the picture opens: one row, slid sideways */
.sq-strip { display: flex; gap: 6px; overflow-x: auto; scroll-snap-type: x proximity; padding: 6px 2px; margin-top: 4px; border-top: 1px solid rgba(255, 255, 255, .08); scrollbar-width: thin; }
.sq-strip-face { flex: none; scroll-snap-align: center; display: flex; flex-direction: column; align-items: center; gap: 2px; width: 62px; min-height: 0; padding: 3px; font-size: 11px; }
.sq-strip-face .face { width: 46px; height: 46px; border-radius: 50%; border: 2px solid var(--r, #55666a); }
.sq-strip-face.on { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold) inset; }
.sq-strip-face .sq-cand-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-none-face { display: flex; align-items: center; justify-content: center; color: var(--dim); }
/* troop sliders */
.sq-sum { color: var(--gold); font-size: 14px; letter-spacing: 0; text-transform: none; }
.sq-troops { display: flex; flex-direction: column; gap: 0; }
/* Phil 14/11: the troop's picture stands over both lines of its row (name + free above, slider + number below); no -, + and "all" buttons */
.sq-trow2 { display: grid; grid-template-columns: auto minmax(0, 1fr) 64px; grid-template-areas: "img name name" "img range num";
  align-items: center; gap: 0 8px; padding: 1px 0; }
.sq-trow2 > .pick-troop { grid-area: img; width: 50px; height: 58px; object-fit: contain; margin: 0; align-self: center; }   /* one width for every troop, so names and sliders line up (a rider is wider than a footman) */
.sq-tname { grid-area: name; display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; }
.sq-trow2 input[type=number] { grid-area: num; width: 64px; min-height: 34px; padding: 2px 4px; text-align: center; }
/* no frame round the slider (the page's `input` rule gave it a border, a ground and 10 px of padding each side) */
.sq-range { grid-area: range; width: 100%; min-width: 0; min-height: 0; height: 34px; margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent; -webkit-appearance: none; appearance: none; touch-action: pan-y; }
.sq-range::-webkit-slider-runnable-track { height: 8px; border-radius: 4px; background: linear-gradient(90deg, var(--gold) var(--p, 0%), #10151a var(--p, 0%)); border: 1px solid #55666a; }
.sq-range::-moz-range-track { height: 8px; border-radius: 4px; background: linear-gradient(90deg, var(--gold) var(--p, 0%), #10151a var(--p, 0%)); border: 1px solid #55666a; }
.sq-range::-webkit-slider-thumb { -webkit-appearance: none; width: 26px; height: 26px; margin-top: -10px; border-radius: 50%; background: #f3d27a; border: 2px solid #7a5a12; box-shadow: 0 2px 6px rgba(0, 0, 0, .5); }
.sq-range::-moz-range-thumb { width: 24px; height: 24px; border-radius: 50%; background: #f3d27a; border: 2px solid #7a5a12; }
.sq-range:disabled { opacity: .35; }
.sq-grid { flex-wrap: wrap; }
.sq-dlg.full .sq-grid .sq-cand { width: 76px; }
.sq-cand.dep { border-color: #8fb3c9; box-shadow: 0 0 0 2px #8fb3c9 inset; }
#squad-sheet select { width: 100%; }
@media (prefers-reduced-motion: reduce) { .sq-box.in { animation: none; } .sq-slide-art { transition: none; } }

/* ---- COMMANDER CARD as a POPUP on phones (owner 14/11 14:05, again 17:22): ONE rule, the SHOP-SWIPE band (search "SHOP-SWIPE 14/11
   (owner 17:22"). The SQ-POP copy that stood here (12 px gap + a ring in the rarity colour all round) was cut at the REL-1114 merge:
   it came later in the file and won, and the ring broke the 17:22 rule "rarity on the corners only". */
/* phone, whole-screen squad: the two slides share the width equally so the faces grid, the post and Done fit one screen (7 faces) */
@media (max-width: 559px) and (orientation: portrait) {
  .sq-dlg.full .sq-leads { grid-template-columns: 1fr 1fr; }
  .sq-dlg.full .sq-trow2 { padding: 2px 0; }
}
@media (max-width: 559px) and (orientation: portrait) {
  .sq-dlg.full .sq-sec { margin: 6px 0 2px; }
  .sq-dlg.full .sq-slide-tag { display: none; }   /* rarity is the frame colour; the labels Commander / Sub-commander stay */
}
/* phone landscape: the squad popup is centred now; its notice bubbles go back to the top gap (52 px) above it, not to the right half */
@media (orientation: landscape) and (max-height: 520px) {
  body:has(#squad-sheet:not([hidden])) #toasts { left: 50%; right: auto; transform: translateX(-50%); width: min(60vw, 420px); top: calc(6px + env(safe-area-inset-top)); bottom: auto; }
}
/* phone landscape: the small popup is wide and in two columns (slides | troops), so the sliders are seen without scrolling */
@media (orientation: landscape) and (max-height: 520px) {
  .sq-dlg { padding: calc(44px + env(safe-area-inset-top)) 14px 10px calc(14px + env(safe-area-inset-left)); }
  .sq-box { width: min(760px, 100%); }
  .sq-cols { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: 12px; align-items: start; }
  .sq-slide-label { font-size: 11px; } .sq-slide-name { font-size: 13px; }
  .sq-trow2 { padding: 1px 0; } .sq-tname { font-size: 12px; }
}
/* SQUAD-POOL: beside the troop name, how many of it this squad may take (owned minus the other squads) */
#squad-sheet .sq-free { margin-left: auto; color: var(--dim); font-size: 12px; font-weight: 500; font-variant-numeric: tabular-nums; }
/* WORLD-BAR: a held face is our menu, never the system callout or a text selection */
.sq-face { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* WORLD-BAR-DESK-ALIGN: on the wide World screen the small popup stands in the middle of the MAP, clear of the 460 px side panel
   (474 = panel + the 14 px gap of the other side, so the box centre is the map centre); the whole-screen view keeps the whole screen */
@media (min-width: 900px) and (min-height: 521px) {
  body[data-tab="world"] .sq-dlg:not(.full) { padding-left: 474px; }
}
/* 14/11 17:00 YOUR commanders only (owner: "you mixed up the commander that the player have already obtained ... with all the no obtained").
   One row of the faces this account owns, leader first; it scrolls sideways when it outgrows the panel. The ones not found yet are ONE
   tile at the end ("+77 To find") that opens the catalogue of every commander. */
.skill-tabs.mine { display: flex; flex-wrap: nowrap; gap: 6px; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; padding: 2px 2px 4px; scroll-snap-type: x proximity; }
.skill-tabs.mine::-webkit-scrollbar { display: none; }
.skill-tabs.mine button.skill-tab { flex: 0 0 60px; width: 60px; padding: 4px 2px 3px; opacity: 1; position: relative; scroll-snap-align: start; border-radius: 6px; }
.skill-tabs.mine button.skill-tab .face { width: 44px; height: 44px; border: 2px solid var(--r, #5a6a70); border-radius: 50%; }
.skill-tabs.mine button.skill-tab.on { background: #1c262b; box-shadow: inset 0 -3px 0 var(--r, currentColor); }
.skill-tabs.mine .roster-name { position: static; display: block; padding: 0; width: 100%; text-align: center; font-size: 11px; color: inherit; }
.skill-tabs.mine button.skill-tab.more { color: #9aa6a2; }
.more-n { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 44px; height: 44px; border: 2px dashed #4b585c; border-radius: 50%; font-size: 12px; line-height: 1; gap: 1px; }
.more-n svg { width: 15px; height: 15px; }
.cmd-pick .cmd-group { grid-template-columns: repeat(var(--n, 4), minmax(0, 1fr)); }
.cmd-pick button.cmd-opt.more { justify-content: center; align-items: center; gap: 4px; padding: 8px 2px; color: #9aa6a2; border-style: dashed; }
.cmd-pick button.cmd-opt.more .roster-name { position: static; padding: 0; text-align: center; color: inherit; }
.cmd-pick button.cmd-opt.more > span.more-n { display: flex; flex-direction: column; grid-template-columns: none; width: 52px; height: 52px; margin: 6px auto 2px; }
.cmd-pick button.cmd-opt.more > span.roster-name { display: block; width: 100%; }

/* ---- SHOP-SWIPE 14/11 (owner 17:22: "remove the rarity colour that is on the bg, let the rarity colour only show on the corner of
   the frame; the profile that shows only the head keeps the rarity colour on its background, the others better a background
   according to their plot, their story, their biome").
   1) RARITY = CORNERS. A half-body card (shop slices, the commander card) has a dark frame and four L corners in its rarity colour
   (--r), drawn over the picture by ::after; no rarity colour behind the commander any more. Head-only tiles (img[data-rar], the
   faces) keep their rarity ground (the R3-H rule above, untouched).
   2) BIOME GROUND. Behind the cut-out commander: the land of its faction (balance.json factions.*.biome, cm.faction = data-biome),
   drawn in CSS: Frostmark snow and tundra, Ashen Reach burnt mountains, Deepwood forest and marsh, Tidehold coast and sea, the
   vagabonds' road. Painted biome plates (art) are a later pass: A-FAIRE SHOP-SWIPE-BIOME-ART. */
.pack-hero::after, .dlg-box.cc::after { content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none; --cl: clamp(8px, 14%, 18px); --cw: 3px;
  background:
    var(--rc, linear-gradient(var(--r), var(--r))) left top / var(--cl) var(--cw) no-repeat, var(--rc, linear-gradient(var(--r), var(--r))) left top / var(--cw) var(--cl) no-repeat,
    var(--rc, linear-gradient(var(--r), var(--r))) right top / var(--cl) var(--cw) no-repeat, var(--rc, linear-gradient(var(--r), var(--r))) right top / var(--cw) var(--cl) no-repeat,
    var(--rc, linear-gradient(var(--r), var(--r))) left bottom / var(--cl) var(--cw) no-repeat, var(--rc, linear-gradient(var(--r), var(--r))) left bottom / var(--cw) var(--cl) no-repeat,
    var(--rc, linear-gradient(var(--r), var(--r))) right bottom / var(--cl) var(--cw) no-repeat, var(--rc, linear-gradient(var(--r), var(--r))) right bottom / var(--cw) var(--cl) no-repeat; }
.dlg-box.cc::after { --cl: 26px; --cw: 3px; z-index: 5; }
/* a closed slice is a sliver: four hooks on each made a comb of teeth along the row (seen 17:4x, phone-1-group.png); it keeps ONE
   thin line of its rarity along its top, dimmed like its picture, and only the open commander has the four corners */
.acc > .pack-hero:not(.open)::after { opacity: .6; background: var(--rc, linear-gradient(var(--r), var(--r))) left top / 100% 2px no-repeat; }
.acc > .pack-hero.open::after { --cl: clamp(12px, 18%, 26px); }
.cc-art.biome { background: var(--biome, #222025); }
/* the commander card without a scene: the hd picture under the alpha of its -cut file (crates.js cardBust), so the biome shows round it */
.cc-art.biome .bust { -webkit-mask-size: 100% 100%; mask-size: 100% 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
[data-biome="frostmark"] { --biome:
  radial-gradient(1.5px 1.5px at 20% 30%, rgba(230, 236, 240, .55), transparent 70%) 0 0 / 37px 43px,
  radial-gradient(1px 1px at 60% 70%, rgba(230, 236, 240, .45), transparent 70%) 0 0 / 53px 61px,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V150l50-22 40 14 60-30 50 22 46-26 54 28 48-18 52 20v62z' fill='%230d1822'/%3E%3C/svg%3E") left bottom / 100% 34% no-repeat,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V118l38-36 30 22 52-62 44 48 34-30 58 58 46-44 40 30 58-50v146z' fill='%2324394d'/%3E%3Cpath d='M38 82l14 13-9 2-9-5zM120 42l20 23-12-2-11 6-9-8zM198 74l12 11-10 1zM302 70l13 12-9-1-8 4zM342 54l10 9-8 2z' fill='%23a9bccb' fill-opacity='.55'/%3E%3C/svg%3E") left bottom / 100% 58% no-repeat,
  radial-gradient(120% 55% at 50% 72%, rgba(120, 160, 190, .32), transparent 70%),
  linear-gradient(180deg, #0c141c, #1b2b3a 62%, #101b25); }
[data-biome="ashen"] { --biome:
  radial-gradient(1.5px 1.5px at 30% 40%, rgba(255, 140, 70, .6), transparent 70%) 0 0 / 41px 47px,
  radial-gradient(1px 1px at 70% 20%, rgba(200, 190, 180, .35), transparent 70%) 0 0 / 29px 53px,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V140l60-18 40 16 70-26 60 20 50-16 60 22 60-12v74z' fill='%23110c0b'/%3E%3C/svg%3E") left bottom / 100% 32% no-repeat,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V112l30-30 20 16 44-74 30 46 22-20 50 68 30-38 44 26 40-60 36 44 24-18 30 34v94z' fill='%23241a17'/%3E%3C/svg%3E") left bottom / 100% 60% no-repeat,
  radial-gradient(110% 50% at 50% 70%, rgba(190, 70, 30, .45), transparent 72%),
  linear-gradient(180deg, #120b0a, #2a1410 60%, #170d0b); }
[data-biome="deepwood"] { --biome:
  radial-gradient(1.5px 1.5px at 40% 50%, rgba(200, 146, 58, .45), transparent 70%) 0 0 / 47px 59px,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V120l14-50 14 50h6l18-70 18 70h8l12-44 12 44h10l20-80 20 80h6l14-52 14 52h10l18-66 18 66h8l12-40 12 40h10l20-74 20 74h6l14-48 14 48h8l16-60 16 60v80z' fill='%23090f0a'/%3E%3C/svg%3E") left bottom / 100% 46% no-repeat,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V110l20-60 20 60h10l16-48 16 48h14l22-70 22 70h12l14-40 14 40h16l20-62 20 62h10l18-56 18 56h14l16-44 16 44h12l22-66 22 66v90z' fill='%23172417'/%3E%3C/svg%3E") left bottom / 100% 68% no-repeat,
  radial-gradient(120% 40% at 50% 66%, rgba(140, 170, 120, .22), transparent 72%),
  linear-gradient(180deg, #0a110b, #1a2a1a 60%, #0c140d); }
[data-biome="tidehold"] { --biome:
  repeating-linear-gradient(176deg, transparent 0 9px, rgba(216, 206, 182, .07) 9px 10px) left bottom / 100% 30% no-repeat,
  linear-gradient(180deg, #123a40, #0a1b1e) left bottom / 100% 30% no-repeat,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V150l30-30 40 10 30-24 30 20 40 6 40 18 50-4 60-26 40 8 40 24v48z' fill='%230f2629'/%3E%3C/svg%3E") left bottom / 100% 48% no-repeat,
  radial-gradient(120% 45% at 50% 70%, rgba(201, 166, 70, .22), transparent 70%),
  linear-gradient(180deg, #0a1517, #173a40 62%, #0c1f22); }
[data-biome="vagabond"] { --biome:
  radial-gradient(1px 1px at 30% 60%, rgba(183, 176, 162, .4), transparent 70%) 0 0 / 31px 37px,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V132c40-16 90-22 140-10s110 10 150-6 80-8 110 2v82z' fill='%23140f0b'/%3E%3Cpath d='M182 200l12-66h12l12 66z' fill='%23302419'/%3E%3C/svg%3E") left bottom / 100% 40% no-repeat,
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200' preserveAspectRatio='none'%3E%3Cpath d='M0 200V110c50-30 100-34 150-18s90 4 130-14 90-14 120 6v116z' fill='%23261d15'/%3E%3C/svg%3E") left bottom / 100% 56% no-repeat,
  radial-gradient(120% 50% at 50% 68%, rgba(183, 130, 80, .28), transparent 72%),
  linear-gradient(180deg, #110f0c, #2b2118 60%, #14100c); }
/* the card that turns: it comes in from the side the finger sent it (next from the right, previous from the left); at the end of the
   list it leans and comes back */
.dlg-box.cc.in-next { animation: cc-in-next .26s cubic-bezier(.2, .7, .2, 1) 1; }
.dlg-box.cc.in-prev { animation: cc-in-prev .26s cubic-bezier(.2, .7, .2, 1) 1; }
@keyframes cc-in-next { from { transform: translateX(42px); opacity: .2; } to { transform: none; opacity: 1; } }
@keyframes cc-in-prev { from { transform: translateX(-42px); opacity: .2; } to { transform: none; opacity: 1; } }
.dlg-box.cc.cc-end-r { animation: cc-end-r .3s ease-out 1; } .dlg-box.cc.cc-end-l { animation: cc-end-l .3s ease-out 1; }
@keyframes cc-end-r { 40% { transform: translateX(-12px); } }
@keyframes cc-end-l { 40% { transform: translateX(12px); } }
.cc-nav { display: inline-flex; align-items: center; gap: 2px; margin-left: auto; }
.cc-nav + button.cc-arrow { margin-left: 4px; }
.cc-nav button.cc-go { width: 44px; height: 44px; min-height: 44px; padding: 0; background: rgba(8, 11, 12, .55); border: 0; }
.cc-nav button.cc-go:disabled { opacity: .3; }
.cc-pos { min-width: 4.2ch; text-align: center; font-size: 13px; font-weight: 700; color: #d7ddd9; font-variant-numeric: tabular-nums; text-shadow: 0 1px 2px #000; }
@media (prefers-reduced-motion: reduce) { .dlg-box.cc.in-next, .dlg-box.cc.in-prev, .dlg-box.cc.cc-end-r, .dlg-box.cc.cc-end-l { animation: none; } }

/* ---- ECO-1115: six resources, the stores, the Market, the node map ---- */
.res-glyph { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.res-glyph.res-timber { color: #c79a5a; } .res-glyph.res-stone { color: #c6c8c0; } .res-glyph.res-silver { color: #e3e9f0; } .res-glyph.res-runestone { color: #a995ff; }
/* six resources + credits in the strip: a chip never cuts its number, the strip takes a second row when the first is full */
.res-bar { flex-wrap: wrap; gap: 3px; }
.res-chip { flex: 1 0 auto; padding: 3px 5px; gap: 3px; font-size: 13px; overflow: visible; }
.res-chip b { margin-left: auto; overflow: visible; }
.res-chip .ico, .res-chip .res-glyph { width: 15px; height: 15px; }
.res-chip[hidden] { display: none; }
/* PHONE: ONE row, as before ECO-1115 (a second row took 27 px from every screen: the Shop stage and the commander card are laid
   out to the pixel on a 390 x 844 phone, game_city_e2e "card G" / "shop D (h)" went red). Smaller chips, numbers from 10 000 up
   written 35k (main.js); if the row still runs short the number is the part that yields, never the icon. */
@media (max-width: 560px) {
  .res-bar { flex-wrap: nowrap; gap: 2px; }
  #res .chip { flex: 1 1 auto; min-width: 0; min-height: 28px; padding: 3px 3px; gap: 2px; font-size: 11.5px; overflow: hidden; }   /* 28 px = the chip of before: the header keeps its height to the pixel */
  #res .chip b { overflow: hidden; text-overflow: ellipsis; }
  #res .chip .ico, #res .chip .res-glyph { width: 13px; height: 13px; }
}
.stat.biome .gi, .biome-chip .gi { width: 15px; height: 15px; }
.biome-chip { color: #b9d39a; font-size: 13px; white-space: nowrap; }
.store-row { align-items: center; gap: 6px; }
.res-pick { display: flex; flex-wrap: wrap; gap: 4px; }
.res-btn { min-width: 40px; min-height: 40px; }
.res-btn.on { outline: 2px solid var(--accent, #e0b040); background: #2a3138; }
.res-btn:disabled { opacity: .35; }
.noart.lumber_camp { background: linear-gradient(160deg, #a9814a 0 50%, #8a6a3c 50%); }
.noart.quarry { background: linear-gradient(160deg, #c3c3bc 0 50%, #a3a39c 50%); }
.noart.fishery { background: linear-gradient(160deg, #5aaab5 0 50%, #3f8f9a 50%); }
.noart.trading_post { background: linear-gradient(160deg, #e0c066 0 50%, #c9a646 50%); }
.noart.smithy { background: linear-gradient(160deg, #8a5a50 0 50%, #6a4038 50%); }
.noart.watchtower { background: linear-gradient(160deg, #a8adb6 0 50%, #8a8f98 50%); }
.noart.market { background: linear-gradient(160deg, #d49456 0 50%, #b8763a 50%); }
.noart.stables { background: linear-gradient(160deg, #a89866 0 50%, #8a7a4a 50%); }
.noart.rune_circle { background: linear-gradient(160deg, #8a7aee 0 50%, #6a5acd 50%); }
.noart.steward_hall { background: linear-gradient(160deg, #ecd070 0 50%, #d4b24a 50%); }
.tree { position: relative; display: flex; flex-direction: column; gap: 18px; padding: 6px 2px 10px; }
.tree-links { position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible; }
.tree-links path { fill: none; stroke-width: 2; }
.tree-links path.met { stroke: #6fbf8a; }
.tree-links path.unmet { stroke: #6b747c; stroke-dasharray: 4 4; }
.tree-row { position: relative; display: flex; align-items: flex-start; gap: 6px; }
.tree-tier { flex: none; width: 34px; display: inline-flex; align-items: center; gap: 2px; font-size: 12px; font-weight: 700; color: var(--dim, #8b949e); padding-top: 8px; }
.tree-tier.on { color: #e0b040; }
.tree-tier .gi { width: 14px; height: 14px; }
.tree-nodes { display: flex; flex-wrap: wrap; gap: 14px 8px; flex: 1 1 auto; }
.tree-node { position: relative; z-index: 1; display: inline-flex; flex-direction: column; align-items: center; gap: 2px; width: 86px; min-height: 76px; padding: 4px 3px; border-radius: 8px;
  border: 1px solid #2c343b; background: #161c21; color: inherit; font: inherit; font-size: 11px; cursor: pointer; }
.tree-node.built { border-color: #6fbf8a; }
.tree-node.open { border-color: #e0b040; }
.tree-node.locked { opacity: .55; }
.tree-node .tree-name { line-height: 1.15; text-align: center; font-weight: 600; }
.tree-node .tree-meta { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; }
.tree-node .tree-meta .gi, .tree-node .tree-meta .ico { width: 13px; height: 13px; }

/* Long-press game actions must not start browser text selection. Chat stays copyable. */
body #app, body #app * { -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; }
body #app input, body #app textarea, body #app [contenteditable="true"],
body #app .chat-log, body #app .chat-log *, body #app .chat-form, body #app .chat-form *,
body #app .talk-log, body #app .talk-log * { -webkit-user-select:text; user-select:text; -webkit-touch-callout:default; }
.tile-action-shade { position:absolute; inset:0; z-index:45; display:grid; place-items:center; background:rgba(0,0,0,.2); }
.tile-action-wheel { position:relative; width:280px; height:260px; border-radius:50%; background:var(--panel,#202623); box-shadow:0 8px 40px #0009; border:1px solid #667468; }
.tile-action-coordinate { position:absolute; top:100px; left:0; width:100%; text-align:center; }
.tile-action { position:absolute; width:90px; height:90px; border-radius:50%; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; font-size:12px; padding:5px; }
.tile-action:nth-of-type(1) { top:10px;left:95px; }
.tile-action:nth-of-type(2) { top:125px;left:15px; }
.tile-action:nth-of-type(3) { top:125px;right:15px; }
.tile-action-close { position:absolute; bottom:8px;left:119px;width:42px;height:36px; }

.talk-tabs { flex-wrap:wrap; }
.friend-row { display:flex; flex-wrap:wrap; gap:6px; align-items:center; padding:8px 0; border-bottom:1px solid #66746855; }
.friend-row > span:first-child { flex:1; min-width:80px; overflow-wrap:anywhere; }
.friend-add { display:flex; gap:6px; flex-wrap:wrap; }
.friend-add select { flex:1; min-width:0; max-width:100%; }
