/**
 * Game styling. Everything is scoped under .ax-game so the widget cannot
 * leak into the host page, and the host page cannot bleed into it.
 *
 * MOBILE FIRST, AND LITERALLY SO
 * The base rules below are the phone layout: a column locked to the
 * viewport where nothing scrolls except the control shelf. Wider screens
 * are the exception, added at the bottom, where the page is allowed to
 * scroll again and the map gets more room.
 *
 * That order is deliberate. The previous stylesheet was written for a
 * desktop canvas and the phone got whatever fell out, which is how the
 * arena ended up 4,491 pixels tall on a 390px screen with the map and the
 * controls never visible at the same time.
 *
 * 100dvh, never 100vh: Chrome's collapsing address bar makes 100vh taller
 * than the visible viewport, which is what used to hide the launch button
 * behind the browser chrome.
 */

.ax-game{
  --papel:#F6F4EE; --blanco:#FFFFFF; --tinta:#080808;
  --suave:rgba(8,8,8,.55); --tenue:rgba(8,8,8,.10); --linea:rgba(8,8,8,.13);
  --oro:#C9A84C; --oro-hondo:#8A6A22; --oro-linea:rgba(138,106,34,.42);
  --azul:#0B7FC7; --azul-hondo:#0A5F94;
  --verde:#17836F; --verde-hondo:#116152;
  --rojo:#C4452F; --rojo-hondo:#9B3524;
  --sans:'Jost',system-ui,sans-serif;
  --mono:'JetBrains Mono','Courier New',monospace;
  --serif:'Cormorant Garamond',Georgia,serif;
  --easing:cubic-bezier(.25,.46,.45,.94);
  position:relative;
  font-family:var(--sans);font-weight:300;color:var(--tinta);
  -webkit-font-smoothing:antialiased;
  -webkit-tap-highlight-color:transparent;
}
.ax-game *{box-sizing:border-box}
/**
 * The browser's own rule for the hidden attribute is `[hidden]{display:none}`
 * — one class-free selector that ANY `display` declaration silently beats.
 * This project is built from flex rows, so `el.hidden = true` quietly did
 * nothing three separate times: an empty ribbon floating over the map, the
 * control shelf refusing to make way for the live log, and a stray panel.
 * The failure is invisible in code review and obvious only in a screenshot.
 * One !important here is cheaper than remembering it at every call site.
 */
.ax-game [hidden]{display:none !important}
.ax-game button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0}
.ax-game button:disabled{cursor:default}
.ax-game svg{display:block}

/* ------------------------------------------------------------------ *
 * Shell
 * ------------------------------------------------------------------ */
.ax-shell{
  display:flex;flex-direction:column;
  height:100dvh;max-height:100dvh;overflow:hidden;
  margin:0 calc(-1 * var(--bleed,0px));
  background:var(--papel);
  border:1px solid var(--tenue);border-radius:16px;
}

/* ------------------------------------------------------------------ *
 * HUD
 * ------------------------------------------------------------------ */
.ax-hud{
  flex:0 0 auto;display:flex;align-items:center;gap:12px;
  padding:10px 14px;background:var(--blanco);
  border-bottom:1px solid var(--tenue);
}
.ax-money{display:flex;align-items:baseline;gap:5px;flex:0 0 auto}
.ax-money .v{font-family:var(--mono);font-size:20px;font-weight:500;line-height:1;color:var(--tinta)}
.ax-money .u{font-family:var(--mono);font-size:12px;color:var(--oro-hondo)}
.ax-preview{
  font-family:var(--mono);font-size:11px;color:var(--azul-hondo);
  background:rgba(11,127,199,.10);border:1px solid rgba(11,127,199,.30);
  border-radius:999px;padding:3px 9px;margin-left:4px;
}
.ax-preview[hidden]{display:none}
.ax-shieldbox{display:flex;align-items:center;gap:6px;margin-left:auto}
.ax-shieldbox .n{font-family:var(--mono);font-size:12px;color:var(--verde-hondo)}
.ax-esc{display:flex;gap:2px}
.ax-esc i{
  width:7px;height:11px;background:var(--verde);
  clip-path:polygon(50% 0,100% 27%,100% 73%,50% 100%,0 73%,0 27%);
}
.ax-esc i.off{background:rgba(196,69,47,.30)}
/* Below about 420px the HUD runs out of room once the budget preview
   appears. The pips are the thing to drop: the strip underneath already
   says which wave is coming, in words. */
.ax-pips{display:flex;gap:5px;flex:0 0 auto}
@media (max-width: 419.98px){ .ax-pips{display:none} }
.ax-pips i{width:20px;height:3px;border-radius:999px;background:rgba(8,8,8,.14)}
.ax-pips i.ok{background:var(--verde)}
.ax-pips i.now{background:var(--rojo)}

/* ------------------------------------------------------------------ *
 * Wave strip — one line instead of a 180px panel
 * ------------------------------------------------------------------ */
.ax-game .ax-strip{
  flex:0 0 auto;display:flex;align-items:center;gap:8px;width:100%;
  padding:11px 14px;text-align:left;
  border-bottom:1px solid var(--tenue);
  transition:background 280ms var(--easing);
}
.ax-strip .w{font-family:var(--mono);font-size:10px;letter-spacing:.08em;flex:0 0 auto}
.ax-strip .n{font-size:14px;font-weight:500;flex:0 0 auto}
.ax-strip .t{font-size:13px;color:rgba(8,8,8,.6);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ax-strip .f{font-family:var(--mono);font-size:14px;font-weight:500;margin-left:auto;flex:0 0 auto;color:var(--rojo)}
.ax-strip .f.ok{color:var(--verde)}
.ax-strip .dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto;animation:ax-pulse 1.6s var(--easing) infinite}
.ax-strip.is-threat{background:rgba(196,69,47,.07);border-bottom-color:rgba(196,69,47,.20)}
.ax-strip.is-threat .w{color:var(--rojo-hondo)}
.ax-strip.is-running{background:rgba(196,69,47,.09);border-bottom-color:rgba(196,69,47,.22)}
.ax-strip.is-running .dot{background:var(--rojo)}
.ax-strip.is-armed{background:rgba(11,127,199,.09);border-bottom-color:rgba(11,127,199,.22)}
.ax-strip.is-armed .n,.ax-strip.is-armed .t{color:var(--azul-hondo)}
.ax-strip.is-armed .dot{background:var(--azul)}
.ax-strip.is-settled{background:rgba(8,8,8,.03)}
.ax-strip.is-settled .n,.ax-strip.is-settled .t{color:var(--suave)}
.ax-strip.is-quiet{background:var(--blanco)}
.ax-strip.is-quiet .n{color:var(--suave);font-weight:400}
@keyframes ax-pulse{0%,100%{opacity:.35}50%{opacity:1}}

/* ------------------------------------------------------------------ *
 * Map
 * ------------------------------------------------------------------ */
.ax-map{
  flex:1 1 auto;min-height:0;position:relative;
  display:flex;align-items:center;justify-content:center;
  padding:14px;background:var(--blanco);
  background-image:radial-gradient(rgba(8,8,8,.07) 1px,transparent 1px);
  background-size:16px 16px;
  overflow:hidden;
}
.ax-board{
  position:relative;flex:0 0 auto;
  /* Scale about the centre, because the flex parent centres the *unscaled*
     box: with a top origin the board sank by half the difference and the
     bottom tier was clipped. The sheet case wants a top origin and says so
     below, where it also switches the parent to top alignment. */
  transform-origin:center center;
  transform:scale(calc(var(--ax-k, 1) * var(--ax-shrink, 1)));
  transition:transform 320ms var(--easing);
}
/* The map stays readable behind a sheet — "what just happened to my
   network" is the question the sheet is answering. */
.ax-has-sheet .ax-map{align-items:flex-start}
.ax-has-sheet .ax-board{transform-origin:center top}
.ax-has-sheet{--ax-shrink:.82}
.ax-edges{position:absolute;inset:0;overflow:visible;pointer-events:none}

/* A cable, not a line: casing, body and core, each stroked by map.js in
   the child asset's own colour. Lateral runs dash their core so the tier
   hierarchy still reads before the traffic inside a tier does. */
.ax-cable{transition:opacity 280ms var(--easing)}
/* While an attack is travelling, everything not on its path steps back. */
.ax-board.is-attacking .ax-cable{opacity:.3}
.ax-board.is-attacking .ax-cable.is-hot{opacity:1}

.ax-lan{
  position:absolute;left:-6px;right:-6px;height:0;
  border-top:1px dashed rgba(138,106,34,.35);pointer-events:none;
}
.ax-lan span{
  position:absolute;right:0;top:-6px;
  font-family:var(--mono);font-size:8px;letter-spacing:.10em;
  color:rgba(138,106,34,.85);background:var(--blanco);padding:0 4px;
}

/* ------------------------------------------------------------------ *
 * Asset tiles
 *
 * Each asset carries its own colour, injected as --c and a few alphas by
 * map.js. Blue is never one of them: blue means "you are selecting this",
 * and a colour that means two things means neither.
 * ------------------------------------------------------------------ */
.ax-game .ax-node{
  position:absolute;display:flex;flex-direction:column;align-items:center;
  background:none;border:0;padding:0;
  transition:opacity 280ms var(--easing);
}
.ax-node .tile{
  position:relative;width:54px;height:54px;border-radius:16px;box-sizing:border-box;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(152deg, var(--c-13), var(--c-05)), #FFFFFF;
  border:1px solid var(--c-34);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9), 0 6px 14px -10px var(--c-55);
  transition:background 280ms var(--easing),border-color 280ms var(--easing),
             box-shadow 280ms var(--easing),transform 280ms var(--easing);
}
.ax-node .g{color:var(--c);line-height:0}
.ax-node .l{
  margin-top:5px;font-family:var(--mono);font-size:8.5px;letter-spacing:.07em;
  line-height:1;white-space:nowrap;color:rgba(8,8,8,.55);
  background:var(--blanco);padding:3px 5px;border-radius:4px;
}
/* The drop ring, shown only on the tile actually under the pointer. There
   is deliberately no "+" button: two affordances competing for the same
   corner is how a drop target stops reading as one. */
.ax-node .ring{position:absolute;inset:-6px;border-radius:21px;border:1.5px dashed transparent}

/* Where the threat comes from. */
.ax-node.is-source .tile{
  background:linear-gradient(152deg, var(--c-22), var(--c-05)), #FFFFFF;
  border-color:var(--c-85);
}
.ax-node.is-source .l{color:var(--c)}

/* Somewhere the held control could go: dashed blue, no fill change, so a
   glance counts the options without the board lighting up like a christmas
   tree. */
.ax-node.is-lit .tile{
  background:linear-gradient(152deg, rgba(11,127,199,.13), rgba(11,127,199,.05)), #FFFFFF;
  border:1px dashed rgba(11,127,199,.6);
}
.ax-node.is-lit .g{color:var(--azul-hondo)}
.ax-node.is-lit .l{color:var(--azul-hondo)}

/* The one under the pointer right now. */
.ax-node.is-target .tile{
  background:linear-gradient(152deg, rgba(11,127,199,.22), rgba(11,127,199,.05)), #FFFFFF;
  border:1.5px solid rgba(11,127,199,.85);
  animation:ax-halo 1.8s var(--easing) infinite;
}
.ax-node.is-target .ring{border-color:rgba(11,127,199,.65)}

.ax-node.is-held .tile{
  background:linear-gradient(152deg, rgba(23,131,111,.22), rgba(23,131,111,.05)), #FFFFFF;
  border:1.5px solid rgba(23,131,111,.85);
  animation:ax-repel 1.4s var(--easing);
}
.ax-node.is-held .g,.ax-node.is-held .l{color:var(--verde-hondo)}

.ax-node.is-hit .tile{
  background:linear-gradient(152deg, rgba(196,69,47,.22), rgba(196,69,47,.05)), #FFFFFF;
  border:1.5px solid rgba(196,69,47,.85);
  animation:ax-hit 700ms var(--easing);
}
.ax-node.is-hit .g,.ax-node.is-hit .l{color:var(--rojo-hondo)}

.ax-node.is-dim{opacity:.4}
.ax-node:focus-visible{outline:2px solid var(--azul);outline-offset:4px;border-radius:18px}

/* --- placed controls --- */
.ax-tokens{position:absolute;top:-8px;left:-8px;display:flex}
.ax-game .ax-token{
  width:19px;height:19px;border-radius:50%;flex:0 0 auto;padding:0;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--oro);color:var(--tinta);box-shadow:0 0 0 2px var(--blanco);
  position:relative;
}
.ax-token + .ax-token{margin-left:-6px}
/* Refundable this turn: a small cross says so without waiting for a hover
   that a finger will never produce. */
.ax-game .ax-token.can-remove{cursor:pointer}
.ax-token .x{
  position:absolute;top:-3px;right:-3px;width:11px;height:11px;border-radius:50%;
  background:var(--tinta);box-shadow:0 0 0 1.5px var(--blanco);
}
.ax-token .x::before,.ax-token .x::after{
  content:'';position:absolute;left:2.5px;top:5px;width:6px;height:1.2px;
  background:var(--papel);border-radius:1px;
}
.ax-token .x::before{transform:rotate(45deg)}
.ax-token .x::after{transform:rotate(-45deg)}
.ax-game .ax-token.can-remove:hover .x,.ax-game .ax-token.can-remove:focus-visible .x{
  background:var(--rojo);
}
.ax-game .ax-token.can-remove:hover{transform:scale(1.08)}
.ax-game .ax-token:focus-visible{outline:2px solid var(--azul);outline-offset:2px}

.ax-badge{
  margin-top:5px;
  font-family:var(--mono);font-size:8.5px;letter-spacing:.06em;white-space:nowrap;
  color:#fff;border-radius:999px;padding:2.5px 7px;
}
.ax-badge.held{background:var(--verde)}
.ax-badge.hit{background:var(--rojo)}

@keyframes ax-halo{0%,100%{box-shadow:0 0 0 0 rgba(11,127,199,.35)}50%{box-shadow:0 0 0 7px rgba(11,127,199,0)}}
@keyframes ax-repel{0%,100%{box-shadow:0 0 0 0 rgba(23,131,111,.5)}50%{box-shadow:0 0 0 8px rgba(23,131,111,0)}}
@keyframes ax-hit{0%,100%{transform:translateX(0)}20%{transform:translateX(-2px)}60%{transform:translateX(2px)}}

/* --- attack ---
   The cable lights, and the attacks themselves travel along it: one marker
   per attack the server actually resolved, each carrying the threat's own
   icon. Small on purpose — traffic on a wire, not a mascot. */
.ax-strike-glow{stroke:rgba(196,69,47,.18);stroke-linecap:round}
.ax-strike-base{stroke:var(--rojo);stroke-linecap:round}
.ax-strike-march{stroke:#fff;stroke-linecap:round;stroke-dasharray:6 8;
  animation:ax-dash 900ms linear infinite}
.ax-packet{fill:var(--rojo)}
@keyframes ax-dash{to{stroke-dashoffset:-44}}

.ax-shot{color:#fff}
.ax-shot-halo{fill:rgba(196,69,47,.18);animation:ax-shot-pulse 1s ease-in-out infinite}
.ax-shot-disc{fill:var(--rojo);stroke:#FFFFFF;stroke-width:1.3}
.ax-shot-ic{color:#FFFFFF}
@keyframes ax-shot-pulse{0%,100%{opacity:.30;r:8}50%{opacity:.85;r:10}}

/* A control answering, and an attack landing. Both are a ring that grows
   out of the tile and goes; the colour says which. */
.ax-ripple{
  position:absolute;inset:0;border-radius:16px;pointer-events:none;
  border:2px solid transparent;animation:ax-ripple 900ms var(--easing) forwards;
}
.ax-ripple.held{border-color:rgba(23,131,111,.85)}
.ax-ripple.hit{border-color:rgba(196,69,47,.85)}
@keyframes ax-ripple{
  0%{transform:scale(1);opacity:.95}
  100%{transform:scale(1.9);opacity:0}
}
/* The token that did the blocking. It is the thing the player bought. */
.ax-game .ax-token.is-firing{
  animation:ax-fire 1.1s var(--easing);
  background:var(--verde);color:#fff;
}
@keyframes ax-fire{
  0%{transform:scale(1);box-shadow:0 0 0 2px var(--blanco)}
  25%{transform:scale(1.45);box-shadow:0 0 0 2px var(--blanco),0 0 0 8px rgba(23,131,111,.35)}
  60%{transform:scale(1.1);box-shadow:0 0 0 2px var(--blanco),0 0 0 14px rgba(23,131,111,0)}
  100%{transform:scale(1);box-shadow:0 0 0 2px var(--blanco)}
}

/* ------------------------------------------------------------------ *
 * Dock
 * ------------------------------------------------------------------ */
.ax-dock{
  flex:0 0 auto;background:var(--papel);
  border-top:1px solid var(--tenue);
  padding:9px 0 calc(10px + env(safe-area-inset-bottom));
}
.ax-dock-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:0 14px 7px;
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;
}
.ax-dock-head .l{text-transform:uppercase;color:var(--oro-hondo);white-space:nowrap}
.ax-dock-head .l.is-armed{color:var(--azul-hondo)}
.ax-dock-head .r{color:rgba(8,8,8,.65);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rel{
  display:inline-block;width:5px;height:5px;border-radius:50%;
  background:var(--verde);vertical-align:middle;
}

.ax-shelf{
  display:flex;gap:10px;overflow-x:auto;overflow-y:hidden;
  padding:2px 14px 6px;
  scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.ax-shelf::-webkit-scrollbar{display:none}

.ax-game .ax-pill{
  flex:0 0 auto;height:46px;border-radius:999px;padding:0 14px 0 5px;
  display:flex;align-items:center;gap:9px;
  background:var(--blanco);border:1px solid rgba(8,8,8,.11);
  scroll-snap-align:start;
  /* The hand that invites you to pick it up. */
  cursor:grab;
  /* Tells the browser it owns horizontal panning — that is the shelf being
     scrolled — and hands us vertical movement, which is a drag towards the
     map. Without this the two gestures fight and the shelf stops scrolling
     wherever a control happens to be. */
  touch-action:pan-x;
  user-select:none;-webkit-user-select:none;
  transition:background 280ms var(--easing),border-color 280ms var(--easing),
             opacity 280ms var(--easing),box-shadow 280ms var(--easing);
}
.ax-game .ax-disc{
  width:34px;height:34px;border-radius:50%;flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--oro);color:var(--tinta);
  transition:background 280ms var(--easing),color 280ms var(--easing);
}
.ax-pill .tx{display:flex;flex-direction:column;gap:1px;text-align:left}
.ax-pill .n{font-size:12.5px;font-weight:500;line-height:1.15;white-space:nowrap}
.ax-pill .d{font-family:var(--mono);font-size:10px;letter-spacing:.02em;
  color:rgba(8,8,8,.6);white-space:nowrap}
.ax-pill.on{
  background:rgba(11,127,199,.09);border-color:var(--azul);
  box-shadow:0 6px 14px -10px rgba(11,127,199,.8);
}
.ax-pill.on .ax-disc{background:var(--azul);color:#fff}
.ax-pill.on .d{color:var(--azul-hondo)}
.ax-pill.faded{opacity:.4}

/* ---- the three states of a control, made to look like three things ----
   Buyable: white, gold disc, a shadow that lifts it off the dock, and the
   grab cursor. Already bought: flat and colourless, still readable because
   knowing what you own matters. Unaffordable: greyed and dashed, with the
   price line saying how far short the budget is (see game.js). Opacity
   alone was doing all three jobs and doing none of them clearly. */
.ax-game .ax-pill.can-buy{
  background:var(--blanco);
  border-color:rgba(138,106,34,.34);
  box-shadow:0 8px 18px -14px rgba(8,8,8,.55), inset 0 1px 0 rgba(255,255,255,.9);
}
.ax-game .ax-pill.can-buy:hover{
  border-color:var(--oro);
  box-shadow:0 10px 20px -12px rgba(138,106,34,.6), inset 0 1px 0 rgba(255,255,255,.9);
  transform:translateY(-1px);
}
.ax-game .ax-pill.can-buy .n{color:var(--tinta)}

.ax-pill.done{
  opacity:.6;filter:saturate(.15);
  background:rgba(8,8,8,.03);border-color:rgba(8,8,8,.10);box-shadow:none;
}
.ax-pill.done .ax-disc{background:rgba(8,8,8,.10);color:rgba(8,8,8,.45)}
.ax-pill.done .d{color:rgba(8,8,8,.45)}

.ax-game .ax-pill.broke{
  opacity:.72;
  background:transparent;border-style:dashed;border-color:rgba(8,8,8,.22);
  box-shadow:none;
}
/* Greyed, but not so greyed that the one line worth reading — how far short
   the budget is — goes grey with it. A filter on the pill would have taken
   that colour too, so the desaturation is applied piece by piece. */
.ax-pill.broke .ax-disc{background:rgba(8,8,8,.07);color:rgba(8,8,8,.32)}
.ax-pill.broke .n{color:rgba(8,8,8,.45);font-weight:400}
.ax-pill.broke .d{color:var(--rojo-hondo);opacity:.9}
.ax-pill.broke .rel{background:rgba(23,131,111,.45)}
.ax-game .ax-pill:active{cursor:grabbing}
.ax-game .ax-pill.done,.ax-game .ax-pill.broke,.ax-game .ax-pill:disabled{cursor:default}
/* The pill you are holding stays visible but recedes: the ghost under the
   pointer is the thing being moved now. */
.ax-game .ax-pill.is-lifted{opacity:.35}
.ax-game .ax-pill:focus-visible{outline:2px solid var(--azul);outline-offset:2px}

/* ------------------------------------------------------------------ *
 * The drag ghost
 *
 * Lives inside the widget root, not on document.body. The old one was on
 * the body, outside `.ax-game`, and was invisible for two rounds of "it
 * still does not work" because it was painted with custom properties that
 * only exist inside that container. See trap 3 in HANDOVER.md.
 * ------------------------------------------------------------------ */
.ax-ghost{
  position:fixed;z-index:60;pointer-events:none;
  transform:translate(-50%,-50%) scale(1.12);
  filter:drop-shadow(0 8px 16px rgba(8,8,8,.28));
}
.ax-dragging{cursor:grabbing}
.ax-dragging .ax-node{cursor:copy}


.ax-game .ax-btn{
  display:flex;align-items:center;justify-content:center;gap:11px;
  width:calc(100% - 28px);margin:4px 14px 0;
  background:var(--tinta);color:var(--papel);border-radius:999px;padding:17px 0;
  font-size:13px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;
  transition:background 280ms var(--easing),color 280ms var(--easing);
}
.ax-btn .ax-ic{line-height:0}
.ax-game .ax-btn:disabled,.ax-btn.is-off{background:rgba(8,8,8,.06);color:rgba(8,8,8,.35)}

/* ------------------------------------------------------------------ *
 * Feed and ribbon
 *
 * On a phone these are gone from view, and that is the fix rather than a
 * compromise. The board is the whole height of the map: put a toast at the
 * bottom and it covers the vault tier, put it at the top and it covers the
 * threat source. There is no free corner, because the diagram earned all
 * of them.
 *
 * They are also redundant there. Placing a control makes a token appear on
 * the tile; a wave writes into the live log; a finished wave raises the
 * result sheet. The toast said what the player was already looking at.
 *
 * The element stays in the document and keeps its aria-live, so a screen
 * reader still hears every event. Hidden from sight, not from everyone.
 * ------------------------------------------------------------------ */
.ax-avisos{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;
  list-style:none;margin:0;padding:0;pointer-events:none;
}
.ax-avisos li{display:block}
.ax-banda{
  position:absolute;left:50%;top:12px;transform:translateX(-50%);
  display:flex;gap:16px;z-index:5;pointer-events:none;
  background:rgba(255,255,255,.95);border:1px solid var(--tenue);
  border-radius:14px;padding:12px 18px;
  box-shadow:0 20px 50px -30px rgba(8,8,8,.6);
  transition:opacity 320ms var(--easing);
}
.ax-banda[hidden]{display:none}
.ax-banda .g{display:flex;align-items:center;gap:6px;font-family:var(--mono);
  font-size:12.5px;font-weight:600}

/* ------------------------------------------------------------------ *
 * Live log — takes the dock's place while a wave is in flight
 * ------------------------------------------------------------------ */
.ax-live{padding:0 14px 2px}
.ax-live-head{
  font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--oro-hondo);margin-bottom:8px;
}
.ax-live-rows{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.ax-live-row{
  display:flex;align-items:center;gap:10px;padding:11px 13px;border-radius:12px;
  font-size:14px;line-height:1.35;
  animation:ax-rise 280ms var(--easing) both;
}
.ax-live-row.held{background:rgba(23,131,111,.09);border:1px solid rgba(23,131,111,.25)}
.ax-live-row.breached{background:rgba(196,69,47,.08);border:1px solid rgba(196,69,47,.22)}
.ax-live-row .t{flex:1;min-width:0}
.ax-warn{
  width:26px;height:26px;border-radius:50%;flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px dashed rgba(196,69,47,.5);
  font-family:var(--mono);font-size:13px;color:var(--rojo-hondo);
}
.ax-game .ax-disc.sm{width:26px;height:26px}
.ax-game .ax-disc.sm.held{background:var(--verde);color:#fff}
@keyframes ax-rise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* ------------------------------------------------------------------ *
 * Sheets
 *
 * The scrim only darkens the lower part of the screen: the map has to stay
 * readable behind the sheet, because "what just happened to my network" is
 * the question the sheet is answering.
 * ------------------------------------------------------------------ */
.ax-sheet-wrap{position:absolute;inset:0;z-index:30;display:flex;align-items:flex-end}
.ax-sheet-wrap[hidden]{display:none}
.ax-scrim{
  position:absolute;left:0;right:0;bottom:0;height:62%;
  background:linear-gradient(to bottom,rgba(8,8,8,0),rgba(8,8,8,.22));
  opacity:0;transition:opacity 280ms var(--easing);
}
.ax-sheet{
  position:relative;width:100%;max-height:88%;overflow-y:auto;
  background:var(--papel);border-top:1px solid rgba(8,8,8,.14);
  border-radius:22px 22px 0 0;padding:10px 18px calc(20px + env(safe-area-inset-bottom));
  box-shadow:0 -20px 50px -30px rgba(8,8,8,.5);
  transform:translateY(101%);transition:transform 320ms var(--easing);
}
.ax-sheet-wrap.is-open .ax-scrim{opacity:1}
.ax-sheet-wrap.is-open .ax-sheet{transform:none}
.ax-game .ax-grab{
  display:block;width:38px;height:4px;border-radius:999px;
  background:rgba(8,8,8,.18);margin:0 auto 14px;padding:0;
}
.ax-game .ax-grab[hidden]{display:none}
.ax-sheet-body{display:flex;flex-direction:column;gap:16px}
.ax-game .ax-btn-quiet{background:var(--papel);color:var(--tinta);border:1px solid var(--linea)}
.ax-linkish{
  font-family:var(--mono);font-size:11px;letter-spacing:.08em;
  color:var(--oro-hondo);text-align:center;padding:4px 0;
}

/* --- briefing --- */
.ax-brief{display:flex;flex-direction:column;gap:14px}
.ax-brief-top{display:flex;align-items:center;gap:12px}
.ax-brief-ic{color:var(--rojo);flex:0 0 auto}
.ax-brief-top h3{font-family:var(--serif);font-weight:300;font-size:28px;line-height:1;margin:4px 0 0}
.ax-brief-cost{
  margin-left:auto;text-align:right;font-family:var(--mono);font-size:24px;font-weight:500;
  color:var(--rojo);display:flex;flex-direction:column;line-height:1;
}
.ax-brief-cost i{font-style:normal;font-size:10px;letter-spacing:.10em;color:var(--oro-hondo);margin-top:4px}
.ax-brief-impact{margin:0;font-size:14.5px;line-height:1.55;color:rgba(8,8,8,.72)}
.ax-brief-route > div{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:7px}
.ax-brief-route .hop{
  font-family:var(--mono);font-size:11.5px;padding:4px 9px;border-radius:999px;
  background:var(--blanco);border:1px solid var(--tenue);
}
.ax-brief-route .hop.last{border-color:rgba(196,69,47,.4);color:var(--rojo-hondo)}
.ax-brief-route i{font-style:normal;color:var(--oro-hondo)}
.ax-brief-help ul{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-direction:column;gap:7px}
.ax-brief-help li{
  display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:12px;
  background:var(--blanco);border:1px solid var(--tenue);font-size:14px;
}
.ax-brief-help li.owned{background:rgba(23,131,111,.07);border-color:rgba(23,131,111,.25)}
.ax-brief-help li .n{flex:1;min-width:0}
.ax-brief-help li .d{font-family:var(--mono);font-size:11.5px;color:var(--oro-hondo)}
.ax-brief-help .none{margin:8px 0 0;font-size:14px;color:var(--suave);line-height:1.5}

/* --- wave result --- */
.ax-result{display:flex;flex-direction:column;gap:16px}
.ax-result-head{display:flex;align-items:flex-start;gap:14px}
.ax-result-head h3{font-family:var(--serif);font-weight:300;font-size:34px;line-height:1;margin:5px 0 0}
.ax-result-cost{
  margin-left:auto;text-align:right;font-family:var(--mono);font-size:26px;font-weight:500;
  display:flex;flex-direction:column;line-height:1;
}
.ax-result-cost.ok{color:var(--verde)}
.ax-result-cost.bad{color:var(--rojo)}
.ax-result-cost i{font-style:normal;font-size:10px;letter-spacing:.10em;color:var(--oro-hondo);margin-top:5px}
.ax-result-rows{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.ax-result-rows li{display:flex;align-items:flex-start;gap:10px;font-size:14px;line-height:1.45}
.ax-result-rows .dot{width:6px;height:6px;border-radius:50%;margin-top:7px;flex:0 0 auto}
.ax-result-rows li.held .dot{background:var(--verde)}
.ax-result-rows li.breached .dot{background:var(--rojo)}
.ax-result-income{margin:0;font-family:var(--mono);font-size:12px;color:var(--oro-hondo)}
.ax-next{
  display:flex;align-items:center;gap:12px;padding:11px 13px;
  border:1px solid rgba(8,8,8,.12);border-radius:12px;
}
.ax-next .n{flex:1;font-size:14px;font-weight:500}
.ax-next .c{font-family:var(--mono);font-size:13px;color:var(--rojo)}

/* --- the between-waves card ---
   Floats over the board, says what the wave did and goes on its own. It
   does not take the screen, does not dim the map behind it and asks for no
   click: the countdown bar along the bottom shows how long it has left,
   and the cross is for a player who has finished reading early. */
.ax-game .ax-flash{
  position:absolute;left:50%;top:10px;z-index:12;
  width:min(400px, calc(100% - 24px));
  transform:translate(-50%,-14px);opacity:0;
  display:flex;flex-direction:column;gap:11px;
  padding:15px 16px 17px;
  background:var(--blanco);border:1px solid var(--tenue);border-radius:16px;
  box-shadow:0 24px 44px -28px rgba(8,8,8,.55);
  transition:opacity 260ms var(--easing),transform 260ms var(--easing);
  pointer-events:auto;
}
.ax-game .ax-flash.is-in{opacity:1;transform:translate(-50%,0)}
.ax-game .ax-flash[hidden]{display:none}
.ax-flash.ok{border-left:3px solid var(--verde)}
.ax-flash.bad{border-left:3px solid var(--rojo)}
/* Room for the cross, so the shield count does not sit under it. */
.ax-flash .ax-result-head{padding-right:26px}
.ax-flash .ax-result-head h3{font-size:23px;margin-top:3px}
.ax-flash .ax-result-cost{font-size:19px}
.ax-flash .ax-result-rows{gap:6px}
.ax-flash .ax-result-rows li{font-size:13px;line-height:1.4}
.ax-flash .ax-next{padding:8px 11px}
.ax-flash .ax-next .n{font-size:13px}
.ax-game .ax-flash-x{
  position:absolute;top:8px;right:8px;width:26px;height:26px;border-radius:50%;
  background:rgba(8,8,8,.05);border:0;padding:0;cursor:pointer;
  transition:background 200ms var(--easing);
}
.ax-game .ax-flash-x:hover{background:rgba(8,8,8,.12)}
.ax-flash-x::before,.ax-flash-x::after{
  content:'';position:absolute;left:7px;top:12.4px;width:12px;height:1.4px;
  background:rgba(8,8,8,.6);border-radius:1px;
}
.ax-flash-x::before{transform:rotate(45deg)}
.ax-flash-x::after{transform:rotate(-45deg)}
.ax-flash-bar{
  position:absolute;left:0;bottom:0;height:2px;width:100%;
  transform-origin:left center;border-radius:0 0 0 16px;
  background:rgba(138,106,34,.5);
  animation:ax-flash-bar var(--ax-flash,3400ms) linear forwards;
}
.ax-flash:hover .ax-flash-bar{animation-play-state:paused}
@keyframes ax-flash-bar{from{transform:scaleX(1)}to{transform:scaleX(0)}}

/* --- leaderboard sheet --- */
.ax-boards-sheet{display:flex;flex-direction:column;gap:16px}
.ax-boards-sheet .board-panel[hidden]{display:none}

/* ------------------------------------------------------------------ *
 * Veils: intro, error, report
 * ------------------------------------------------------------------ */
.ax-velo{
  position:absolute;inset:0;z-index:20;
  display:flex;align-items:center;justify-content:center;padding:16px;
  background:rgba(246,244,238,.86);backdrop-filter:blur(3px);
  overflow-y:auto;
}
.ax-velo[hidden]{display:none}
.ax-tarjeta{
  width:100%;max-width:520px;max-height:100%;overflow-y:auto;
  background:var(--blanco);border:1px solid var(--tenue);border-radius:18px;
  padding:22px 20px;
  box-shadow:0 30px 60px -40px rgba(8,8,8,.5);
}
.ax-et{font-family:var(--mono);font-size:10.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--suave)}
.ax-tit{font-family:var(--serif);font-weight:300;font-size:30px;line-height:1.05;margin:0}
.ax-escs{margin:16px 0 0}
.ax-esce{display:flex;align-items:center;gap:12px;padding:14px;
  border:1px solid var(--tenue);border-radius:14px;background:var(--papel)}
.ax-esce .tx{flex:1;min-width:0}
.ax-esce .tt{font-size:15px;font-weight:500}
.ax-esce .dd{font-family:var(--mono);font-size:11.5px;color:var(--suave);margin-top:2px}
.ax-esce .rr{display:flex;gap:3px}
.ax-esce .rr i{width:6px;height:6px;border-radius:50%;background:var(--oro)}
.ax-esce .rr i.off{background:var(--tenue)}
.ax-esce .pp{font-family:var(--mono);font-size:13px;font-weight:600;color:var(--oro-hondo)}
/* Difficulty, in words.
   Three gold dots said there was a scale and never what this scenario's
   place on it was called. The level has a name, a colour that goes with
   how hard it is, and one line saying what that means for the budget —
   which is the only thing difficulty changes in this game. */
.ax-game .ax-diff{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;
  margin:10px 0 0;padding:9px 12px;border-radius:12px;
  border:1px solid var(--c-lvl,var(--tenue));
  background:var(--bg-lvl,var(--papel));
}
.ax-game .ax-diff[hidden]{display:none}
.ax-diff b{font-size:14px;font-weight:500;color:var(--c-lvl,var(--tinta))}
.ax-diff .ax-et{color:var(--suave)}
.ax-diff .bl{flex:1 1 100%;font-size:12.5px;line-height:1.45;color:rgba(8,8,8,.62)}
.ax-diff.lvl-intro{--c-lvl:var(--verde-hondo);--bg-lvl:rgba(23,131,111,.07)}
.ax-diff.lvl-standard{--c-lvl:var(--oro-hondo);--bg-lvl:rgba(201,168,76,.10)}
.ax-diff.lvl-advanced{--c-lvl:var(--rojo-hondo);--bg-lvl:rgba(196,69,47,.08)}

.ax-steps{display:flex;flex-direction:column;gap:9px;margin:16px 0}
.ax-step{display:flex;align-items:center;gap:10px;font-size:14px;color:rgba(8,8,8,.7)}
.ax-build{font-family:var(--mono);font-size:10px;color:rgba(8,8,8,.35);margin:10px 0 0}
.ax-unlock-failed{font-size:13.5px;color:var(--rojo-hondo);margin:12px 0 0}
.ax-errores{margin:14px 0 0;padding-left:18px}
.ax-errores li{font-family:var(--mono);font-size:12px;line-height:1.5}

.ax-rango{font-family:var(--serif);font-size:64px;font-weight:300;line-height:1;margin:6px 0 4px}
.ax-estrellas{display:flex;gap:4px;margin-bottom:14px}
.ax-estrellas i{width:9px;height:9px;background:var(--oro);
  clip-path:polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%)}
.ax-estrellas i.off{background:var(--tenue)}
.ax-datos{display:flex;flex-wrap:wrap;gap:14px 22px;margin-bottom:16px}
.ax-datos .it{display:flex;align-items:center;gap:8px}
.ax-datos .v{font-family:var(--mono);font-size:17px;font-weight:600;line-height:1}
.ax-isos{display:flex;flex-direction:column;gap:10px}

/* ---- the report's own blocks -------------------------------------- *
 * These class names were written in game.js and never given a rule, so
 * the most important screen in the product rendered as unstyled runs of
 * text: an icon stacked above its line, a bold name butted straight up
 * against the sentence after it ("Endpoints3 shields lost"). It reads as
 * a data dump rather than as a report, which is the opposite of what the
 * end of a game is for.
 * ------------------------------------------------------------------- */
.rep-block{display:flex;flex-direction:column;gap:9px;
  padding-top:14px;border-top:1px solid var(--tenue)}
.rep-block:first-child{padding-top:0;border-top:0}
.rep-wave,.rep-line{display:flex;align-items:flex-start;gap:10px}
.rep-wave>svg,.rep-line>svg{flex:0 0 auto;margin-top:2px}
.rep-wave>div,.rep-line>div{min-width:0;font-size:13.5px;line-height:1.45}
/* The bold half names the thing; the span is what happened to it, and it
   belongs on its own line. Without this they ran together. */
.rep-wave b,.rep-line b{font-weight:600;color:var(--tinta)}
.rep-wave span,.rep-line span{display:block;margin-top:2px;
  font-size:12.5px;line-height:1.5;color:rgba(8,8,8,.62)}
.rep-wave.bad b{color:var(--rojo-hondo)}
.rep-wave.good b{color:var(--verde-hondo)}
.rep-line.miss b{color:var(--oro-hondo)}
.rep-line i{font-style:italic}
.rep-standing{display:flex;flex-direction:column;gap:8px}
.rep-bar{height:5px;border-radius:999px;background:var(--tenue);overflow:hidden}
.rep-bar i{display:block;height:100%;border-radius:999px;background:var(--oro)}
.rep-standing p{margin:0;font-size:13px;line-height:1.5;color:rgba(8,8,8,.7)}
.rep-standing p b{font-weight:600;color:var(--tinta)}
.rep-note{margin:0;font-size:13.5px;line-height:1.5;color:rgba(8,8,8,.62)}
.rep-block ul{list-style:none;margin:0;padding:0;
  display:flex;flex-direction:column;gap:8px}
.rep-block li{display:flex;align-items:flex-start;gap:10px;font-size:13px;line-height:1.45}
.rep-block li>svg{flex:0 0 auto;margin-top:2px}
.rep-block li b{font-family:var(--mono);font-size:11.5px;font-weight:500;
  color:var(--oro-hondo);flex:0 0 auto;margin-top:1px}
.rep-block li span{color:rgba(8,8,8,.72)}

.ax-locked{border:1px solid var(--tenue);border-radius:14px;padding:16px;background:var(--papel)}
.ax-locked p{margin:8px 0 0;font-size:14px;line-height:1.55;color:rgba(8,8,8,.72)}
.ax-locked p.small{font-size:12.5px;color:var(--suave)}
/* A short line earns the display face; the two-sentence paragraph this used
   to be would have looked like a pull quote nobody wrote. */
.ax-leccion{font-size:22px;line-height:1.35;color:var(--tinta);margin:20px 0 0;
  font-family:var(--serif);font-weight:400}
/* The invitation sits closer to the buttons than to the lesson: it belongs
   to what you can do next, not to what just happened. */
.ax-invita{font-size:13.5px;line-height:1.6;color:rgba(8,8,8,.58);margin:10px 0 0}
.ax-botones{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}
.ax-b1,.ax-b2{display:inline-flex;align-items:center;gap:9px;border-radius:999px;
  padding:13px 22px;font-size:13px;font-weight:500;letter-spacing:.06em;
  text-decoration:none;transition:background 280ms var(--easing)}
.ax-b1{background:var(--tinta);color:var(--papel)}
.ax-b2{background:var(--papel);color:var(--tinta);border:1px solid var(--linea)}
.ax-offline{padding:40px;text-align:center;color:var(--suave)}

/* ------------------------------------------------------------------ *
 * Wider screens
 *
 * The page is allowed to scroll again here, because a leaderboard and a
 * consent control below the game are useful on a desktop and pointless to
 * hide behind a sheet. The map, the chips and the pills are identical —
 * only the room they get changes.
 * ------------------------------------------------------------------ */
@media (min-width: 700px){
  .ax-map{min-height:200px}
  .ax-hud{padding:14px 20px;gap:18px}
  .ax-money .v{font-size:24px}
  .ax-strip{padding:13px 20px;gap:10px}
  .ax-dock{padding:12px 0 14px}
  .ax-dock-head{padding:0 20px 9px}
  /* Enough width for the whole shelf: wrap it instead of scrolling, so
     every control is visible at once and nothing hides off the edge. */
  .ax-shelf{flex-wrap:wrap;overflow:visible;padding:2px 20px 8px}
  .ax-btn{width:calc(100% - 40px);margin:6px 20px 0;padding:16px 0}
  /* Wide enough that the board no longer fills the pane: the toast fits
     beside it instead of on top of it. */
  .ax-avisos{
    width:auto;height:auto;overflow:visible;clip-path:none;white-space:normal;
    left:20px;right:auto;bottom:16px;top:auto;max-width:340px;
    display:flex;flex-direction:column;gap:6px;z-index:4;
  }
  .ax-avisos li{
    display:flex;align-items:center;gap:8px;
    background:rgba(255,255,255,.94);border:1px solid var(--tenue);
    border-left:3px solid var(--c,var(--oro-hondo));
    border-radius:10px;padding:7px 10px;font-size:12.5px;
    transition:opacity 380ms var(--easing);
    box-shadow:0 8px 20px -16px rgba(8,8,8,.6);
  }
  .ax-avisos li b{font-weight:500}
  .ax-tarjeta{padding:28px 26px}
  .ax-tit{font-size:34px}
  .ax-rango{font-size:74px}
}

/* ------------------------------------------------------------------ *
 * One rule for every screen: the game fits the window
 *
 * Until 0.6.17 the shell went `height:auto` above 900px, on the argument
 * that a desktop has room for the leaderboard underneath. It does — but
 * the game grew past the fold with it, so a player on a 1440x900 laptop
 * had to scroll to find the Launch button and scroll back to see the
 * board. Drag-and-drop across a scroll boundary is not a game.
 *
 * So the shell now takes the height the window has left, at every width.
 * `--ax-chrome` is what the host page used above it, measured by
 * `fitShell()` in game.js rather than guessed here — edit the arena page's
 * heading and this follows. The leaderboard still lives below; it is one
 * scroll away, which is where a leaderboard belongs.
 * ------------------------------------------------------------------ */
.ax-shell{height:calc(100dvh - var(--ax-chrome, 150px))}

@media (min-width: 900px){
  /* A floor, not a target: the map is the flexible row and takes whatever
     the fixed rows leave. Anything larger here and the shell overflows the
     window again, which is the bug this section exists to prevent. */
  .ax-map{min-height:200px}
}

/* ------------------------------------------------------------------ *
 * Desktop: the controls stand beside the board, not under it
 *
 * A phone is a column and there is no argument about it. A desktop is not:
 * stacking the dock under the map spent the screen's scarce dimension
 * (height) to leave its plentiful one (width) empty on both sides of a
 * three-column graph. On a 1366x768 laptop that put the board at 0.73
 * scale with 500px of blank paper either side of it.
 *
 * Side by side, the map gets the whole height under the strip and the
 * board roughly doubles. The shelf becomes a vertical list, which is also
 * where a list of six things belongs.
 *
 * Keyed to width AND height: a short window has no height to give the map
 * and is better off with the stacked layout it already had.
 * ------------------------------------------------------------------ */
@media (min-width: 1000px) and (min-height: 620px){
  .ax-shell{
    display:grid;
    grid-template-columns:1fr minmax(300px, 340px);
    grid-template-rows:auto auto 1fr;
    grid-template-areas:"hud hud" "strip strip" "map dock";
  }
  .ax-hud{grid-area:hud}
  .ax-game .ax-strip{grid-area:strip}
  .ax-map{grid-area:map;min-height:0}
  .ax-dock{
    grid-area:dock;min-height:0;
    display:flex;flex-direction:column;
    border-top:0;border-left:1px solid var(--tenue);
    padding:12px 0 14px;
  }
  /* A column, and one that scrolls itself if a future scenario ships more
     controls than fit. `pan-y` because the axis it owns has changed —
     drag.js reads the same fact from the computed flex-direction. */
  .ax-shelf{
    flex:0 1 auto;min-height:0;flex-direction:column;flex-wrap:nowrap;
    overflow-y:auto;overflow-x:hidden;scroll-snap-type:none;
    padding:2px 16px 8px;gap:8px;
  }
  /* Two short lines rather than one truncated one: the column is 340px and
     the header was losing its second half to an ellipsis. */
  .ax-dock-head{flex-direction:column;align-items:flex-start;gap:3px;padding:0 16px 8px}
  .ax-game .ax-pill{width:100%;touch-action:pan-y}
  .ax-game .ax-btn{width:calc(100% - 32px);margin:auto 16px 0;padding:16px 0}
  .ax-live{flex:1 1 auto;min-height:0;overflow-y:auto}
  /* The toast has the whole map pane to itself again. */
  .ax-avisos{left:16px;bottom:16px;max-width:300px}
}

/* Somebody who has asked for less motion gets the states without the
   movement. The information is in the colour and the badge, not the
   animation. */
@media (prefers-reduced-motion: reduce){
  .ax-game *,.ax-game *::before,.ax-game *::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
}
