/**
 * Defend the Network — the game's styling. Until 0.58.0 this was
 * `public/styles.css`; it moved under `public/mechanics/` (cut 3,
 * docs/DECISIONS.md §77) because it is THIS mechanic's, and the rank
 * panel it used to carry is `public/boards.css` now, the palette
 * `public/tokens.css`. Loaded by the arena pages of this mechanic and by
 * the home page for the demonstration.
 *
 * 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{
  /* The palette is `public/tokens.css` since 0.58.0 — one set of values
     for every page, with these Spanish names as aliases of it. Nothing
     here declares a colour. */
  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}
/* The name under a tile.
   It used to be `white-space:nowrap`, which was fine while the longest
   name in the game was ENDPOINTS. A scenario built in the panel has assets
   called ENGINEERING WORKSTATION, and an unbreakable line grew straight
   out of its cell and through the label beside it. It now wraps inside the
   cell — `max-width:100%` is what holds it there — and the three-line
   clamp stops one very long name shoving the row below it down. */
.ax-node .l{
  margin-top:5px;font-family:var(--mono);font-size:8.5px;letter-spacing:.07em;
  line-height:1.25;color:rgba(8,8,8,.55);
  background:var(--blanco);padding:3px 5px;border-radius:4px;
  max-width:100%;white-space:normal;overflow-wrap:break-word;
  text-align:center;text-wrap:balance;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;
  overflow:hidden;
}
/* 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)}
.ax-offline a{display:block;margin-top:14px;color:var(--oro-hondo);font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-decoration:none}
.ax-offline a:hover{text-decoration:underline}

/* ------------------------------------------------------------------ *
 * 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. */
/* ================================================================== *
 * THE ENTRANCE — the board is alive behind the title card, and Start
 * is a fall, not a close (0.47.0)
 *
 * Two classes on the widget root, set by game.js and nothing else:
 *   .is-intro     while the title card is up: the board breathes behind
 *                 it, and the packets game.js appends run the cables.
 *   .is-entering  for 900 ms after Start: the card drops into the board,
 *                 the veil lifts, the board settles, the controls arrive.
 *
 * Every base state is the finished state. The reduced-motion block just
 * above collapses each animation here to one instant run, so under that
 * preference the board is sharp and still and the card is simply gone —
 * every @keyframes below ends on its resting values for that reason.
 * ================================================================== */

/* Reposo. The blur moves from the veil (`backdrop-filter`, recomputed on
   every frame the veil fades) to the shell behind it (`filter`, rasterised
   once and static). Same picture — the veil covers the whole shell, as
   the backdrop did — at a fraction of the cost when it comes off. Only
   the intro veil changes; the error and result veils keep theirs. */
.ax-game.is-intro .ax-shell{filter:blur(3px)}
/* Thinner than the .86 the veil had: a board that breathes behind a veil
   nobody can see through is a board that might as well be still. The blur
   on the shell keeps the card legible over it; .58 was the point, measured
   in a browser, where the traffic reads and the HUD figures do not. */
.ax-game.is-intro .ax-velo-intro{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(246,244,238,.58)}
.ax-game .ax-velo-ghost{background:rgba(246,244,238,.58)}
/* One set of keyframes and six negative delays: the nodes share a breath
   and never take it together. `nth-of-type` — the nodes are <button>s
   beside an <svg> and a rule, and `nth-child` would count those. */
.ax-game.is-intro .ax-node{animation:ax-breathe 5.2s ease-in-out infinite}
.ax-game.is-intro .ax-node:nth-of-type(2){animation-delay:-0.9s}
.ax-game.is-intro .ax-node:nth-of-type(3){animation-delay:-1.7s}
.ax-game.is-intro .ax-node:nth-of-type(4){animation-delay:-2.6s}
.ax-game.is-intro .ax-node:nth-of-type(5){animation-delay:-3.4s}
.ax-game.is-intro .ax-node:nth-of-type(6){animation-delay:-4.3s}
.ax-game.is-intro .ax-node:nth-of-type(n+7){animation-delay:-0.4s}
.ax-game .ax-pkt{pointer-events:none}
@keyframes ax-breathe{0%,100%{opacity:1}50%{opacity:.55}}

/* La entrada. The real veil is ALREADY hidden — Start set that first, and
   `[hidden]` wins here with !important, by design. What falls is a clone
   game.js drops beside it: `.ax-velo-ghost`, inert, `pointer-events:none`
   so nothing the player does in that second is swallowed. The card's
   opacity runs ahead of its travel: at 55 % it is half gone, so it
   dissolves toward the board rather than landing on it. */
.ax-game .ax-velo-ghost{
  pointer-events:none;overflow:hidden;
  backdrop-filter:none;-webkit-backdrop-filter:none;
  animation:ax-veil-out 620ms var(--easing) 120ms both;
}
.ax-game .ax-velo-ghost .ax-tarjeta{animation:ax-brief-out 560ms var(--easing) both}
/* The settle goes on the pane, not on `.ax-board`, which already carries
   the fit scale: two scales on one element replace each other. */
.ax-game.is-entering .ax-map{animation:ax-board-in 720ms var(--easing) both}
/* The controls arrive AFTER the card has started to fall, on purpose: the
   eye follows the card down and finds them already there. Together, the
   whole screen blinks and neither reads. */
.ax-game.is-entering .ax-pill{animation:ax-ctrl-in 420ms var(--easing) 180ms backwards}
.ax-game.is-entering .ax-pill:nth-child(2){animation-delay:260ms}
.ax-game.is-entering .ax-pill:nth-child(3){animation-delay:340ms}
.ax-game.is-entering .ax-pill:nth-child(4){animation-delay:420ms}
.ax-game.is-entering .ax-pill:nth-child(5){animation-delay:500ms}
.ax-game.is-entering .ax-pill:nth-child(n+6){animation-delay:580ms}
@keyframes ax-veil-out{to{opacity:0}}
@keyframes ax-brief-out{
  0%{opacity:1;transform:none}
  55%{opacity:.5}
  100%{opacity:0;transform:translateY(38px) scale(.88)}
}
@keyframes ax-board-in{from{transform:scale(1.035)}to{transform:none}}
@keyframes ax-ctrl-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

@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;
  }
}

/* ================================================================== *
 * THE GUIDED DEMONSTRATION — "How to play"
 *
 * A modal on the front page that plays a fixed script over a toy
 * scenario. See src/ui/demo.js for what it teaches and why it is a
 * recording rather than a tutorial.
 *
 * Everything here is scoped under .ax-demo, and the dialog carries
 * .ax-game as well so the variables above are in scope. That second class
 * is load-bearing: without it none of the colours, fonts or board styles
 * apply and the demonstration renders as a stack of grey boxes.
 * ================================================================== */
.ax-game.ax-demo{
  /* .ax-game sets position:relative, which would fight the modal
     positioning a <dialog> gets from the user agent. */
  position:fixed;inset:0;
  width:100%;height:100%;max-width:100%;max-height:100%;
  margin:0;padding:0;border:0;background:transparent;overflow:hidden;
}
.ax-demo::backdrop{background:rgba(8,8,8,.62);backdrop-filter:blur(2px)}

.ax-demo-frame{
  position:absolute;inset:0;margin:auto;
  width:min(720px,calc(100% - 24px));height:min(760px,calc(100% - 24px));
  display:flex;flex-direction:column;
  background:var(--papel);border:1px solid var(--tenue);border-radius:18px;
  box-shadow:0 30px 80px -30px rgba(8,8,8,.6);overflow:hidden;
}

.ax-demo-bar{
  flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;
  gap:12px;padding:12px 16px;background:var(--blanco);
  border-bottom:1px solid var(--tenue);
}
.ax-demo-id{display:flex;flex-direction:column;gap:2px;min-width:0}
.ax-demo-id strong{font-size:14px;font-weight:500;line-height:1.1}
.ax-demo-id span{
  font-family:var(--mono);font-size:10px;letter-spacing:.06em;
  color:var(--suave);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.ax-demo-x{
  flex:0 0 auto;border:1px solid var(--linea);background:transparent;
  border-radius:999px;padding:8px 18px;cursor:pointer;line-height:1.1;
  font-family:var(--mono);font-size:10px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--suave);white-space:nowrap;
}
.ax-demo-x:hover{background:var(--papel);color:var(--tinta)}
/* The frame is focusable so that the dialog does not open with a ring
   round Close; it must never draw a ring of its own. */
.ax-demo-frame:focus,.ax-demo-frame:focus-visible{outline:none}
/* Keyboard focus stays visible where it matters, in the widget's own
   language rather than the browser's default oval. */
.ax-demo-x:focus-visible,.ax-demo-b:focus-visible,.ax-demo-go:focus-visible{
  outline:2px solid var(--azul);outline-offset:2px;
}

/* The stage owns the cursor's coordinate space, so the pointer travels
   correctly however the dialog is sized. */
.ax-demo-stage{position:relative;flex:1 1 auto;min-height:0;display:flex}
.ax-demo-shell{
  /* The shell is 100dvh in the game because it *is* the page. Here it is a
     panel inside a dialog and has to fill its parent instead. */
  flex:1 1 auto;height:auto;max-height:none;min-height:0;
  border:0;border-radius:0;margin:0;
}
.ax-demo-wave{
  margin-left:auto;font-family:var(--mono);font-size:10px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--oro-hondo);
}

/* The route, shown once so the player knows a wave travels rather than
   simply arriving. Same hop language as the wave briefing. */
.ax-demo-route{
  flex:0 0 auto;display:flex;align-items:center;flex-wrap:wrap;gap:6px;
  padding:8px 14px;background:var(--blanco);
  border-bottom:1px solid var(--tenue);
  font-size:11px;
}
.ax-demo-route-l{
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--rojo-hondo);margin-right:2px;
}
.ax-demo-route .hop{
  padding:2px 8px;border-radius:999px;
  background:rgba(8,8,8,.05);border:1px solid var(--linea);
}
.ax-demo-route .hop.last{
  background:rgba(196,69,47,.10);border-color:rgba(196,69,47,.34);
  color:var(--rojo-hondo);
}
.ax-demo-route i{color:var(--suave);font-style:normal}

/* ---- the pointer ------------------------------------------------ *
 * A drawn cursor, not the real one. It is translated in stage
 * coordinates and the duration is set per move from the script.
 * ---------------------------------------------------------------- */
.ax-demo-cursor{
  position:absolute;left:0;top:0;z-index:8;pointer-events:none;
  margin:-3px 0 0 -3px;
  transition:transform 900ms var(--easing);
  filter:drop-shadow(0 3px 6px rgba(8,8,8,.35));
}
.ax-demo-cursor.is-down{transform-origin:top left}
.ax-demo-ghost{
  position:absolute;left:0;top:0;z-index:7;pointer-events:none;
  margin:-38px 0 0 -17px;
  transition:transform 900ms var(--easing);
  filter:drop-shadow(0 8px 16px rgba(8,8,8,.28));
}
.ax-demo-ghost .ax-disc{transform:scale(1.1)}

/* The refusal. This is the lesson of step 4, so it has to read as "no"
   at a glance and not as damage — an attack landing is the red ring, and
   these two must not be confused. A dashed ring and a shake. */
.ax-demo .ax-node.ax-demo-no .tile{
  border-color:var(--rojo);
  box-shadow:0 0 0 3px rgba(196,69,47,.18);
}
.ax-demo .ax-node.ax-demo-no .ring{
  border-style:dashed;border-color:var(--rojo);
}
.ax-demo .ax-node.ax-demo-no{animation:ax-demo-nope 420ms var(--easing) 1}
@keyframes ax-demo-nope{
  0%,100%{transform:translateX(0)}
  20%{transform:translateX(-5px)}
  55%{transform:translateX(5px)}
  80%{transform:translateX(-2px)}
}

/* ---- the caption, as a bubble ------------------------------------
 * Laid over the diagram with a tail pointing at whatever the step is
 * about. See placeBubble() in src/ui/demo.js for how the side is chosen;
 * everything here just has to make it readable on top of a board.
 *
 * Which means opaque. A translucent bubble over a dotted background is a
 * bubble nobody can read, and the point of putting the words on the
 * picture is that they can be read without leaving it.
 * ---------------------------------------------------------------- */
.ax-demo-bubble{
  position:absolute;z-index:6;
  display:flex;align-items:flex-start;gap:10px;
  width:max-content;max-width:min(370px, calc(100% - 32px));
  padding:11px 14px 12px;
  background:var(--blanco);border:1px solid var(--linea);border-radius:14px;
  box-shadow:0 14px 32px -16px rgba(8,8,8,.55), 0 2px 6px -3px rgba(8,8,8,.25);
  animation:ax-demo-pop 260ms var(--easing) both;
}
/* The words. Capped on their own so the bubble grows by Aegis's width and
   not by his width plus a line that got longer to fill the space. */
.ax-demo-said{display:block;max-width:250px}
/* Aegis. He is the speaker, so he sits on the side away from the tail —
   the tail is the pointer and he is the voice, and putting both on the
   same edge makes the tail look like it is coming out of his ear. */
.ax-demo-who{flex:0 0 auto;display:block;order:0}
.ax-demo-bubble.is-right .ax-demo-who{order:2}
.ax-demo-who .ax-aegis{display:block;overflow:visible}
@keyframes ax-demo-pop{
  from{opacity:0;transform:scale(.96)}
  to{opacity:1;transform:scale(1)}
}
.ax-demo-step{
  font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--oro-hondo);
}
.ax-demo-bubble h3{margin:3px 0 3px;font-size:15px;font-weight:500;line-height:1.2}
.ax-demo-bubble p{margin:0;font-size:13px;line-height:1.42;color:rgba(8,8,8,.74)}

/* The tail is a rotated square with two of its borders showing, so it
   carries the bubble's outline round the point. Its offset along the edge
   is set from JS; the offset *across* the edge is fixed here. */
.ax-demo-tail{
  position:absolute;width:12px;height:12px;
  background:var(--blanco);border:1px solid var(--linea);
  transform:rotate(45deg);
}
.ax-demo-bubble.is-right .ax-demo-tail{
  left:-7px;margin-top:-6px;border-right:0;border-top:0;
}
.ax-demo-bubble.is-left .ax-demo-tail{
  right:-7px;margin-top:-6px;border-left:0;border-bottom:0;
}
.ax-demo-bubble.is-below .ax-demo-tail{
  top:-7px;margin-left:-6px;border-right:0;border-bottom:0;
}
.ax-demo-bubble.is-above .ax-demo-tail{
  bottom:-7px;margin-left:-6px;border-left:0;border-top:0;
}
/* Nothing in particular to point at. */
.ax-demo-bubble.is-plain .ax-demo-tail{display:none}

/* ---- the controls ----------------------------------------------- */
.ax-demo-controls{
  flex:0 0 auto;display:flex;align-items:center;gap:8px;
  padding:10px 16px calc(12px + env(safe-area-inset-bottom));
  background:var(--papel);border-top:1px solid var(--tenue);
}
.ax-demo-gap{flex:1 1 auto}
.ax-demo-b{
  border:1px solid var(--linea);background:var(--blanco);border-radius:999px;
  padding:8px 15px;cursor:pointer;
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--tinta);
  transition:opacity 200ms var(--easing),background 200ms var(--easing);
}
.ax-demo-b:hover:not(:disabled){background:var(--papel)}
.ax-demo-b:disabled{opacity:.35;cursor:default}
.ax-demo-go{
  display:inline-flex;align-items:center;gap:7px;
  padding:9px 18px;border-radius:999px;text-decoration:none;
  background:var(--tinta);color:var(--blanco);
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;
  transition:transform 240ms var(--easing),box-shadow 240ms var(--easing);
}
.ax-demo-go:hover{background:var(--tinta)}
/* On the last step the invitation stops being one control among four. */
.ax-demo.is-end .ax-demo-go{
  background:var(--azul);
  box-shadow:0 10px 24px -12px rgba(11,127,199,.9);
  transform:translateY(-1px);
}

/* Nothing in the shelf is clickable — it is a recording. Saying so with
   the cursor stops it reading as a control that does not work. */
.ax-demo .ax-pill{cursor:default}
.ax-demo .ax-pill:disabled{opacity:1}
.ax-demo .ax-pill.broke:disabled{opacity:.62}

@media (max-width:560px){
  .ax-demo-frame{
    width:100%;height:100%;border-radius:0;border:0;
  }
  .ax-demo-bubble{max-width:calc(100% - 20px);padding:10px 12px 11px;gap:8px}
  .ax-demo-said{max-width:none}
  .ax-demo-bubble h3{font-size:14.5px}
  .ax-demo-bubble p{font-size:12.5px}
  .ax-demo-controls{padding:9px 12px calc(10px + env(safe-area-inset-bottom));gap:6px}
  .ax-demo-b{padding:8px 11px}
  .ax-demo-go{padding:9px 14px}
  /* The shelf is a row here and three controls do not fit across a phone;
     it scrolls, exactly as it does in the game. */
  .ax-demo .ax-shelf{flex-wrap:nowrap;overflow-x:auto}
  .ax-demo .ax-pill{width:auto}
}

/* ---- the name collision, and why this block exists ----------------
 * The front page has its own `.tile` — the arena cards in "Choose your
 * arena" — declared in the <style> block of index.html. The board's asset
 * squares are also `.tile`. On every other page those two never meet; in
 * the demonstration they are on the same document, and the front page's
 * rule leaks into the board.
 *
 * `.ax-node .tile` wins on specificity for the properties it declares, so
 * the colour and the size were fine. What broke it were the properties the
 * board never had an opinion about — min-height:190px, padding:30px 26px,
 * gap and justify-content — which are inherited unopposed and turned a
 * 54px square into a tall capsule with the icon pinned to the top.
 *
 * The fix is to state them, here, where the reason is written down. If a
 * third page ever grows a `.tile`, this is the block that will need it.
 * ------------------------------------------------------------------ */
.ax-demo .ax-node .tile{
  min-height:0;max-height:none;padding:0;gap:0;
  justify-content:center;align-items:center;
  overflow:visible;flex-direction:row;
}

/* ---- the layout query, and why the demonstration opts out ---------
 * At 1000px and up the game puts the shelf in a column beside the map.
 * That query reads the *window*, and the demonstration is a 720px dialog
 * inside a window that is usually much wider — so on a desktop the board
 * was being squeezed into 378px to make room for a 340px shelf column,
 * inside a panel that had no room to spare for either.
 *
 * 720px is a width at which the real game stacks, so stacking here is not
 * a different game: it is the same layout a 720px browser would get.
 * ------------------------------------------------------------------ */
.ax-demo .ax-shell{
  display:flex;flex-direction:column;
  grid-template-columns:none;grid-template-rows:none;grid-template-areas:none;
}
.ax-demo .ax-map{min-height:0}
.ax-demo .ax-dock{
  flex:0 0 auto;display:block;
  border-top:1px solid var(--tenue);border-left:0;
  padding:9px 0 12px;
}
.ax-demo .ax-shelf{
  flex-direction:row;flex-wrap:nowrap;
  overflow-x:auto;overflow-y:hidden;
  padding:2px 14px 6px;gap:10px;
}
.ax-demo .ax-pill{width:auto}

/* Four assets in two tiers do not need a line drawn round the LAN, and at
   this width its label lands on top of the perimeter's. */
.ax-demo .ax-lan{display:none}

/* Present in the layout, absent to the eye. See the comment on shelf() in
   src/ui/demo.js: reserving the space is what stops the board resizing —
   and therefore being re-scaled and clipped — between steps. */
/* Nothing in the demonstration is hidden any more — see the comment on
   shelf() in src/ui/demo.js. Kept as a one-liner because it costs nothing
   and a future step may want it. */
.ax-demo .is-off{visibility:hidden}

/* The "REPELLED" badge hangs below the tile's label, outside the box the
   board measures — so a board scaled to exactly fill the pane has its
   badge clipped by the bottom edge, which is the one moment the badge is
   the whole point. `--ax-shrink` is the hook the sheet layout already uses
   for the same reason; this is the demonstration asking for the same
   favour, a little less of it. */
.ax-demo{--ax-shrink:.88}

/* ---- making room for the bubbles ---------------------------------
 * A bubble laid over the diagram must not hide the diagram, and the
 * placement code can only honour that if somewhere is actually free. With
 * the board centred there was nowhere: the two side margins were about
 * 210px each and the bubble is wider than that, so every candidate
 * position covered an asset and the code simply picked the least bad one.
 *
 * So the board is moved aside and the bubbles get the space it leaves.
 * On a wide panel it sits right, and the column down the left is theirs;
 * on a phone there is no width to give, so the board goes to the top and
 * they get the bottom. Nothing here positions a bubble — it only makes
 * the room that placeBubble() then finds.
 * ---------------------------------------------------------------- */
.ax-demo .ax-map{justify-content:flex-end;padding-right:18px}
/* No easing on the board's scale here. The bubbles are positioned against
   measured geometry, and a board that is still travelling towards its
   final size is geometry that will be wrong in 300ms.
 *
 * And it grows towards the space, not out of it. `transform: scale` does
 * not change the layout box, so a board pushed to the right edge and then
 * scaled about its centre spills a good 25px past that edge — which is
 * where the labels are. Anchoring the origin to the same edge the board is
 * aligned to keeps the growth pointing inwards. */
.ax-demo .ax-board{transition:none;transform-origin:right center}

@media (max-width:560px){
  .ax-demo{--ax-shrink:.72}
  .ax-demo .ax-map{
    justify-content:center;align-items:flex-start;
    padding:10px 10px 0;
  }
  .ax-demo .ax-board{transform-origin:center top}
}

/* ================================================================== *
 * AEGIS — the narrator
 *
 * The character is a design handoff: docs/AEGIS.md, with the original
 * SVGs kept in assets/aegis/. Geometry and colours live in
 * src/ui/aegis.js; what belongs here is the motion, and the handoff is
 * specific about it. The system's word for how it moves is "floating" —
 * no bounce, no squash beyond the blink, no rotation past ±4°.
 *
 * The easing is the design system's own, which is already --easing.
 * ================================================================== */

/* The idle. It keeps running underneath everything else, which is why it
   is on its own group rather than on the svg — the punch and the entrance
   need their own transform to play with. */
.ax-aegis-float{
  animation:ax-aegis-float 5.5s var(--easing) infinite;
  transform-box:view-box;transform-origin:90px 100px;
}
@keyframes ax-aegis-float{
  0%,100%{transform:translateY(0)}
  50%{transform:translateY(-6px)}
}

/* The blink. 5.2s round, with the squash in the last ~4% — which is what
   makes it read as a blink and not as a wink in slow motion. */
#aegis-eyes{
  transform-box:view-box;transform-origin:90px 96px;
  animation:ax-aegis-blink 5.2s var(--easing) infinite;
}
@keyframes ax-aegis-blink{
  0%,96%{transform:scaleY(1)}
  98%{transform:scaleY(.08)}
  100%{transform:scaleY(1)}
}

/* Looking where he points: the pupils shift 4px toward the hand. On its
   own wrapper because #aegis-eyes is spoken for by the blink, and two
   transforms on one element means one of them silently wins. */
.ax-aegis-look{transform-box:view-box;transition:transform 280ms var(--easing)}
.ax-aegis[data-pose="point-right"] .ax-aegis-look{transform:translateX(4px)}
.ax-aegis[data-pose="point-left"] .ax-aegis-look{transform:translateX(-4px)}

/* The alert punch: once, 240ms, on the face group only. */
.ax-aegis-face.is-punch{
  transform-box:view-box;transform-origin:90px 100px;
  animation:ax-aegis-punch 240ms var(--easing) 1;
}
@keyframes ax-aegis-punch{
  0%,100%{transform:scale(1)}
  50%{transform:scale(1.06)}
}

/* Arriving. The bubble has its own entrance; this is his, inside it. */
.ax-demo-who .ax-aegis{
  animation:ax-aegis-enter 400ms var(--easing) both;
}
@keyframes ax-aegis-enter{
  from{opacity:0;transform:translateY(12px)}
  to{opacity:1;transform:translateY(0)}
}

/* ------------------------------------------------------------------ *
 * The guided first game
 *
 * A caption on a stalk, an Aegis to say it, and a scrim that decides what
 * may be touched while it is up.
 *
 * HOW THE BLOCKING WORKS
 * The scrim covers the whole widget and takes every click. Whatever the
 * step allows is RAISED ABOVE it (`.ax-coach-live`) rather than having a
 * hole cut for it — a hole has to be kept in step with an element that
 * moves whenever the board is refitted, and a stale hole is a player
 * clicking on nothing and concluding the game is broken.
 *
 * The layers, in order: veils sit at 20 and the drag ghost at 60, so the
 * scrim goes between them at 41, anything permitted at 42, and the bubble
 * at 44 — above the permitted region, because a caption that can be
 * covered by the thing it is explaining is not a caption. The ghost stays
 * on top of all of it, which is right: a control being dragged is the
 * player's own hand.
 * ------------------------------------------------------------------ */
/* No z-index of its own, on purpose and against the instinct to give it
   one. A z-index here opens a stacking context, and everything inside is
   then trapped below it however high its own number is — which is how the
   caption ended up UNDERNEATH the report card it was pointing at, with the
   bubble on 44 and the card on 42. This layer is a transparent parent; the
   numbers that matter are on the scrim, the permitted region and the
   bubble, and they are compared against the rest of the widget. */
.ax-coach{position:absolute;inset:0;pointer-events:none}
.ax-coach-scrim{
  position:absolute;inset:0;z-index:41;pointer-events:auto;
  background:rgba(8,8,8,.34);
  animation:ax-coach-fade 220ms var(--easing) both;
}
@keyframes ax-coach-fade{from{opacity:0}to{opacity:1}}

/* Permitted while a step is up: raised above the scrim.
   The z-index is the whole rule. `position` is added only to the four
   regions that are static in the layout, because z-index does nothing to
   a statically positioned element — and it must NOT be added to anything
   else. Applying `position:relative` to everything took `.ax-velo`, which
   is `absolute; inset:0`, out of the overlay and dropped the report card
   into the flow at the bottom of the widget, where the step pointing at it
   was pointing at a card that was no longer on screen. */
.ax-game .ax-coach-live{z-index:42}
.ax-game .ax-hud.ax-coach-live,
.ax-game .ax-strip.ax-coach-live,
.ax-game .ax-map.ax-coach-live,
.ax-game .ax-dock.ax-coach-live{position:relative}

/* ---- the caption ------------------------------------------------- */
.ax-coach-bubble{
  position:absolute;z-index:44;pointer-events:auto;
  display:flex;align-items:flex-start;gap:10px;
  width:max-content;max-width:min(340px, calc(100% - 28px));
  padding:11px 14px 12px;
  background:var(--blanco);border:1px solid var(--linea);border-radius:14px;
  box-shadow:0 14px 32px -16px rgba(8,8,8,.55), 0 2px 6px -3px rgba(8,8,8,.25);
}
.ax-coach-bubble[hidden]{display:none}
.ax-coach-bubble.is-in{animation:ax-coach-pop 240ms var(--easing) both}
@keyframes ax-coach-pop{
  from{opacity:0;transform:scale(.96)}
  to{opacity:1;transform:scale(1)}
}
/* Capped on its own so the bubble grows by Aegis's width and not by his
   width plus a line that stretched to fill the space beside him. */
.ax-coach-said{display:block;max-width:230px}
/* He is the voice and the tail is the pointer, so they go on opposite
   edges. Both on the same one and the tail reads as coming out of his ear. */
.ax-coach-who{flex:0 0 auto;display:block;order:0}
.ax-coach-bubble.is-right .ax-coach-who{order:2}
.ax-coach-who .ax-aegis{display:block;overflow:visible}
.ax-coach-step{
  font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--oro-hondo);
}
.ax-coach-bubble h3{margin:3px 0 3px;font-size:15px;font-weight:500;line-height:1.2}
.ax-coach-bubble p{margin:0;font-size:13px;line-height:1.42;color:rgba(8,8,8,.74)}

.ax-coach-acts{display:flex;align-items:center;gap:12px;margin-top:9px}
/* Ink on paper, like every other primary button on the site (`.ax-b1`).
   It was gold on gold, which read as decoration rather than as the thing
   to press — and it is the thing to press on most of the steps. */
.ax-coach-go{
  flex:0 0 auto;
  font-family:var(--sans);font-size:13px;line-height:1;
  padding:9px 15px;border-radius:999px;border:1px solid var(--tinta);
  background:var(--tinta);color:var(--papel);cursor:pointer;
}
.ax-coach-go:hover{background:var(--oro-hondo);border-color:var(--oro-hondo)}
.ax-coach-go[hidden]{display:none}
/* Quiet, and always there. A step that waits for an action has no other
   way out, and a first game with no way out is a trap. */
.ax-coach-skip{
  flex:0 0 auto;
  font-family:var(--sans);font-size:12px;line-height:1;
  padding:6px 2px;border:0;background:none;cursor:pointer;
  color:rgba(8,8,8,.5);text-decoration:underline;text-underline-offset:2px;
}
.ax-coach-skip:hover{color:var(--tinta)}
.ax-coach-skip[hidden]{display:none}

/* The tail is a rotated square showing two of its borders, so the bubble's
   outline carries round the point. Its offset along the edge comes from
   JS; the offset across the edge is fixed here. */
.ax-coach-tail{
  position:absolute;width:12px;height:12px;
  background:var(--blanco);border:1px solid var(--linea);
  transform:rotate(45deg);
}
.ax-coach-bubble.is-right .ax-coach-tail{left:-7px;margin-top:-6px;border-right:0;border-top:0}
.ax-coach-bubble.is-left .ax-coach-tail{right:-7px;margin-top:-6px;border-left:0;border-bottom:0}
.ax-coach-bubble.is-below .ax-coach-tail{top:-7px;margin-left:-6px;border-right:0;border-bottom:0}
.ax-coach-bubble.is-above .ax-coach-tail{bottom:-7px;margin-left:-6px;border-left:0;border-top:0}
/* Nothing in particular to point at. */
.ax-coach-bubble.is-plain .ax-coach-tail{display:none}

/* While a caption about the report is up, the panel makes room for it.
   The caption is pinned along the bottom on a narrow screen — there is
   nowhere beside a panel that fills the window — and without this the
   last lines of the report would sit permanently underneath it. */
.ax-game.ax-coach-card .ax-tarjeta{padding-bottom:190px}

/* On a narrow screen the character costs width the sentence needs more. */
@media (max-width: 460px){
  .ax-coach-bubble{max-width:calc(100% - 20px);gap:8px;padding:10px 12px 11px}
  .ax-coach-said{max-width:200px}
}

@media (prefers-reduced-motion: reduce){
  .ax-coach-scrim,.ax-coach-bubble.is-in{animation:none}
}

/* ------------------------------------------------------------------ *
 * The marker, the breakdown and the best mark (0.34.0)
 *
 * Everything here is under `.ax-game`, which is not a style preference:
 * outside it none of the custom properties above exist, the rules apply
 * nothing, and the failure is silent — the game draws with the browser's
 * defaults and reads as dozens of unrelated layout faults. It has cost
 * this project two rounds of "it still does not work".
 *
 * The palette is the brand's, under the names this file has always used:
 * --oro is gold-500, --oro-hondo is gold-700, --verde is the repel colour
 * and --rojo the hit colour. Nothing is duplicated here.
 * ------------------------------------------------------------------ */

/* The figure is set in the display face and large, because it is a number
   and not an ornament — it is the thing the scoring system is teaching. */
.ax-puntos{
  display:flex;align-items:baseline;gap:6px;position:relative;
  padding-left:14px;margin-left:2px;border-left:1px solid var(--tenue);
}
.ax-puntos .n{
  font-family:var(--serif);font-size:26px;font-weight:300;line-height:1;
  color:var(--tinta);font-variant-numeric:tabular-nums;
  transition:color 280ms var(--easing);
}
.ax-puntos .u{
  font-family:var(--mono);font-size:10px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--oro-hondo);
}
/* Grey, not hidden. Seeing what you are leaving on the table is the point;
   hiding it would remove the only reason to register. */
.ax-puntos.is-anon .n{color:var(--suave)}
.ax-puntos.is-anon .u{color:rgba(8,8,8,.38)}

/* What the wave cost in points, inside the between-wave card.
   They were chips floating under the HUD first: they arrived at the same
   instant as this card and said the same loss twice, a few centimetres
   apart and in the same colour. Here the movement sits beside the sentence
   that explains it. */
.ax-flash-pts{
  display:flex;align-items:center;flex-wrap:wrap;gap:8px;
  margin:12px 0 0;padding-top:12px;border-top:1px solid var(--tenue);
}
.ax-flash-pts b{
  font-family:var(--mono);font-size:11px;font-weight:500;white-space:nowrap;
  padding:3px 9px;border-radius:999px;border:1px solid var(--tenue);
  background:var(--blanco);
}
.ax-flash-pts b.up{color:var(--verde);border-color:rgba(23,131,111,.34)}
.ax-flash-pts b.down{color:var(--rojo);border-color:rgba(196,69,47,.34)}
/* The running total, in the display face, so the eye connects it with the
   same figure in the HUD that has just moved to it. */
.ax-flash-pts .tot{
  margin-left:auto;font-family:var(--serif);font-size:26px;font-weight:300;
  line-height:1;color:var(--tinta);font-variant-numeric:tabular-nums;
}

/* The permanent notice for a game that banks nothing. Permanent, not a
   toast: somebody who arrives mid-game has to be able to see it too. */
.ax-anon-aviso{
  flex:0 1 auto;min-width:0;padding:4px 10px;border-radius:999px;
  background:rgba(8,8,8,.05);border:1px solid var(--tenue);
  font-family:var(--mono);font-size:10px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--suave);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* On a phone the HUD has no room for the sentence, and truncating a
   warning to three words is worse than not showing it there: the same
   thing is said in full on the title card and at the end. */
@media (max-width: 719.98px){ .ax-anon-aviso{display:none} }

/* What the scenario is worth, on the title card. Three facts, no
   explanation. */
.ax-vale{
  margin-top:10px;padding-top:12px;border-top:1px solid var(--tenue);
  font-family:var(--mono);font-size:12px;color:var(--oro-hondo);
  letter-spacing:.02em;
}

/* The breakdown: the same numbers the player already watched move. */
.ax-desglose{
  margin-top:14px;padding-top:6px;border-top:1px solid var(--tenue);
  font-variant-numeric:tabular-nums;
}
.ax-desglose .row{
  display:flex;justify-content:space-between;align-items:baseline;
  padding:9px 0;border-bottom:1px solid rgba(8,8,8,.06);
}
.ax-desglose .row .l{font-size:14px;color:var(--suave)}
.ax-desglose .row .v{font-family:var(--mono);font-size:13px;color:var(--tinta)}
.ax-desglose .total{
  display:flex;justify-content:space-between;align-items:baseline;padding:14px 0 2px;
}
.ax-desglose .total .n{
  font-family:var(--serif);font-size:40px;font-weight:300;line-height:1;color:var(--tinta);
}
/* The ceiling, quieter than the score. `112 / 120` says both what was
   earned and what was there to earn, without the second competing. */
.ax-desglose .total .max{font-size:20px;color:rgba(8,8,8,.38)}
.ax-desglose.is-anon .total .n{color:var(--suave)}

/* The best mark, in the one moment it matters: after a replay. */
.ax-marca{
  margin-top:12px;padding:12px 16px;border-radius:12px;
  background:rgba(201,168,76,.10);border:1px solid rgba(201,168,76,.30);
  font-size:14px;line-height:1.5;color:var(--suave);
}
.ax-marca.is-up{color:var(--tinta)}

.ax-anon-fin{
  margin:14px 0 0;font-size:14px;line-height:1.55;color:var(--suave);
}

/* Where the run left the player on the scale. One line under the mark,
   with the next rank's name linking to its card on the public page: the
   end of a game is the moment somebody most wants to know what an
   Argonaut is, and until 0.37.0 the only answer was the word. */
.ax-rango-sig{
  margin:12px 0 0;font-size:14px;line-height:1.55;color:var(--suave);
}
.ax-rango-sig b{font-weight:500;color:var(--tinta)}
.ax-rango-sig a{
  color:var(--oro-hondo);text-decoration:none;
  border-bottom:1px solid var(--oro-linea);
}
.ax-rango-sig a:hover{color:var(--tinta);border-color:var(--tinta)}

/* A number ticking up is exactly the motion this rule exists for. The
   figure is the information; the tick is not. `countTo()` also checks the
   same query in JavaScript and jumps straight to the final value. */
@media (min-width: 720px){
  .ax-puntos .n{font-size:32px}
}
