/* Base and components. Composition, columns and breakpoints belong to scene.css. */

* { box-sizing: border-box; }
/* The wordmark carries the title visually; this keeps the words in the
   accessibility tree and in the document text where it belongs. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
html, body { min-height: 100%; }
/* Scrollbar hidden on request — scrolling itself stays fully intact (wheel,
   trackpad, touch, keyboard, and programmatic scroll all still work). */
html { scrollbar-width: none; -ms-overflow-style: none; }
html::-webkit-scrollbar { display: none; width: 0; height: 0; }
body {
  margin: 0;
  color: var(--paper);
  font-family: var(--body-font);
  line-height: 1.65;
  background:
    radial-gradient(ellipse 76% 52% at 82% -12%, color-mix(in srgb, var(--ember-dark) 62%, transparent), transparent 62%),
    radial-gradient(ellipse 62% 44% at 6% 104%, color-mix(in srgb, var(--ember-dark) 40%, transparent), transparent 58%),
    radial-gradient(ellipse 40% 30% at 50% 50%, color-mix(in srgb, var(--panel) 70%, transparent), transparent 70%),
    var(--coal);
  background-attachment: fixed;
}

/* Layer discipline: #app carries --z-content in scene.css, so the fixed
   atmosphere canvas at --z-atmosphere always paints underneath it. fx-static is
   written by motion/boot.js whenever WebGL is unavailable — the static smoke
   layer VISUAL.md requires as the fallback. */
html.fx-static body::before {
  content: '';
  position: fixed; inset: 0; z-index: var(--z-atmosphere);
  pointer-events: none; opacity: .3;
  background:
    var(--smoke-tile) center / 520px repeat,
    repeating-linear-gradient(0deg, transparent 0 3px, color-mix(in srgb, var(--ember-dark) 22%, transparent) 4px);
}
.scanlines {
  position: fixed; inset: 0; z-index: var(--z-scanlines);
  pointer-events: none; opacity: .06; mix-blend-mode: soft-light;
  background: repeating-linear-gradient(0deg, transparent 0 2px, color-mix(in srgb, var(--coal) 70%, transparent) 3px);
}
#fx-bg { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: var(--z-atmosphere); opacity: .5; }
#devtools-mask {
  position: fixed; inset: 0; z-index: var(--z-devtools);
  display: grid; place-items: center; padding: 24px;
  background: color-mix(in srgb, var(--coal) 94%, transparent);
}
.devtools-card {
  max-width: 420px; padding: 28px 24px; text-align: center;
  border: 1px solid var(--ember-dark); background: var(--panel); box-shadow: 0 0 0 1px var(--glow);
}
.devtools-card .hint { color: var(--muted); font-size: 13px; }
html.devtools-blocked #fx-bg, html.devtools-blocked .scanlines { display: none !important; }

/* Interaction chrome */
button, input, select, textarea { font: inherit; }
button, a, input, textarea { -webkit-tap-highlight-color: transparent; }
button, a { touch-action: manipulation; }
button { cursor: pointer; }
button:disabled { cursor: default; opacity: .42; }
:focus-visible { outline: 2px solid var(--paper); outline-offset: 4px; }
a { color: inherit; text-underline-offset: 4px; }

/* Masthead */
header {
  display: flex; justify-content: space-between; align-items: center; gap: 28px;
  min-height: 64px; border-bottom: 1px solid var(--line);
}
.brand {
  display: inline-flex; align-items: center; gap: 12px;
  font-size: 15px; font-weight: 900; letter-spacing: .28em;
}
.brand::before {
  content: ''; width: 28px; height: 28px; flex: none;
  background: url('/assets/icon-mark.png') center / contain no-repeat;
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--ember) 40%, transparent));
}
.text-button, header a {
  border: 0; background: none; color: var(--muted); font-size: 13px;
  padding: 10px 0; min-height: 48px; text-decoration: none;
}
.text-button:hover, header a:hover { color: var(--ember); }

.test-banner { padding: 9px 13px; margin-bottom: 18px; border-left: 3px solid var(--ember); background: var(--panel); }
.test-banner strong { font-family: var(--number-font); font-size: var(--text-sm); letter-spacing: .12em; color: var(--paper); }
.test-banner p { margin: 6px 0 0; font-size: var(--text-xs); color: var(--muted); }

/* Typography */
h1 {
  margin: 22px 0 18px;
  font-size: clamp(44px, 9vw, 96px); font-weight: 900; line-height: 1.02;
  letter-spacing: -.035em; text-wrap: balance;
  text-shadow: 0 6px 60px var(--coal), 0 0 30px var(--glow);
}
h2 { margin: 8px 0 22px; font-size: clamp(28px, 6vw, 48px); font-weight: 900; line-height: 1.12; letter-spacing: -.02em; }
h3 { margin: 0 0 8px; font-size: 18px; }
.intro { margin: 0 0 28px; font-size: clamp(18px, 3.6vw, 30px); font-weight: 500; letter-spacing: .08em; }
.intro p { margin: 0; }
.intro p:first-child { color: color-mix(in srgb, var(--ember) 42%, var(--paper)); }
.hint { margin: 10px 0 0; color: var(--muted); font-size: var(--text-sm); }
.eyebrow {
  display: inline-block; color: var(--muted);
  font-family: var(--number-font); font-size: var(--text-xs); letter-spacing: .16em; text-transform: uppercase;
}

/* Buttons */
.button {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); padding: 12px 20px;
  border: 1px solid var(--line); border-radius: 0;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  color: var(--paper); font-weight: 700; text-decoration: none;
  transition: transform var(--dur-fast) ease, border-color var(--dur-fast) ease,
              background var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.button:hover { border-color: var(--ember); box-shadow: inset 0 0 0 1px var(--glow); }
.button:active { transform: translateY(2px) scale(.985); }
.button.primary {
  border-color: var(--ember); background: var(--ember);
  letter-spacing: .08em; box-shadow: 0 10px 34px color-mix(in srgb, var(--ember) 26%, transparent);
}
.button.primary:hover { background: color-mix(in srgb, var(--ember) 74%, var(--paper)); border-color: var(--paper); }
.button.primary::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 35%, color-mix(in srgb, var(--paper) 30%, transparent) 50%, transparent 65%);
  transform: translateX(-130%);
}
.button.primary:active::after { animation: sweep .34s ease-out; }
.button.selected {
  border-color: var(--ember); background: color-mix(in srgb, var(--ember-dark) 40%, var(--panel));
  box-shadow: inset 3px 0 var(--ember);
}
.button.selected::before {
  content: ''; position: absolute; top: 12px; right: 12px;
  width: 5px; height: 5px; background: var(--ember);
}
.actions { display: flex; flex-wrap: wrap; gap: 12px; }
.dock .actions { justify-content: center; }
.dock .actions .primary { flex: 1; }

/* Fields */
.field { display: block; margin: 0 0 22px; }
.field-label { display: block; margin-bottom: 8px; font-size: var(--text-md); font-weight: 700; }
.required { margin-left: 8px; color: var(--ember); font-size: 11px; letter-spacing: .08em; }
.field-shell { position: relative; display: block; }
.field.ok .field-shell::after {
  content: attr(data-check); position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  color: var(--ember); font-weight: 700;
}
input:not([type=checkbox]), textarea {
  width: 100%; min-height: 50px; padding: 13px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); color: var(--paper);
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
textarea { min-height: 110px; resize: vertical; }
input[readonly] { color: var(--muted); }
input:not([type=checkbox]):focus, textarea:focus { border-color: var(--ember); box-shadow: 0 0 0 1px var(--glow); outline: none; }
.field:has([aria-invalid=true]) input { border-color: var(--ember); }
.choices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.choices.platforms { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.choices .button { justify-content: flex-start; min-height: 64px; padding: 16px 20px; }
.choices.platforms .button { justify-content: center; }
fieldset { margin: 0 0 22px; padding: 0; border: 0; }
legend { padding: 0; }
.error {
  display: block; margin: 8px 0; padding-left: 10px;
  border-left: 2px solid var(--ember); color: var(--paper); font-size: var(--text-sm);
}
.error:empty { display: none; }
.check {
  display: flex; align-items: flex-start; gap: 12px; margin: 22px 0; padding: 14px;
  border: 1px solid var(--line); background: color-mix(in srgb, var(--panel) 80%, transparent);
}
.check input { flex-shrink: 0; width: 22px; height: 22px; margin-top: 2px; accent-color: var(--ember); }

/* Seat lamps and progress rails */
.seat-progress { margin: 0 0 22px; padding: 16px 0; border: 0; background: none; }
.seat-progress-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 12px; font-family: var(--number-font); font-size: 18px; letter-spacing: .08em;
}
.seat-progress-head span:last-child { color: var(--muted); font-size: var(--text-xs); letter-spacing: .12em; }
.seat-lamps { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
.seat-lamps i {
  display: block; height: 8px;
  background: color-mix(in srgb, var(--paper) 12%, transparent);
  box-shadow: inset 0 0 0 1px var(--line);
}
.seat-lamps i.on { background: var(--ember); box-shadow: 0 0 14px var(--glow); animation: pulse-lamp .9s ease-out; }
/* Seats four to six are substitutes. Dimmer when empty and unglowed when lit,
   so three filled lamps already look like a finished team. */
.seat-lamps i.advisory { background: color-mix(in srgb, var(--paper) 6%, transparent); }
.seat-lamps i.on.advisory { background: color-mix(in srgb, var(--ember) 50%, transparent); box-shadow: none; }

/* The captain gate. Set apart from the rest of the form because it is not the
   player's own details — it is a message the captain has to answer. */
.gate { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--line); }
.gate-note { margin: 12px 0 0; font-size: var(--text-sm); color: var(--ember); }
.gate-preview { margin: 10px 0 0; padding: 12px 14px; border: 1px dashed var(--line-hot); font-size: var(--text-xs); color: var(--muted); }
.gate-preview strong { display: block; margin-top: 6px; font-family: var(--number-font); font-size: 24px; letter-spacing: .22em; color: var(--paper); }
.gate[data-state="sent"] .actions { display: none; }
.seat-progress[data-state="ready"] .seat-progress-head strong { color: var(--ember); }
.seat-legend { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; font-size: var(--text-xs); letter-spacing: .1em; color: var(--muted); }
.completeness { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 10px 0 0; font-size: var(--text-xs); letter-spacing: .08em; color: var(--muted); }
.completeness .gap { color: var(--ember); }
progress.progress-rail {
  appearance: none; display: block; width: 100%; height: 3px;
  margin-bottom: 12px; border: 0; padding: 0; background: var(--panel); color: var(--ember);
}
progress.progress-rail::-webkit-progress-bar { background: var(--panel); }
progress.progress-rail::-webkit-progress-value { background: var(--ember); }
progress.progress-rail::-moz-progress-bar { background: var(--ember); }
.steps { margin-bottom: 12px; color: var(--muted); font-size: var(--text-xs); }
.steps .current { color: var(--ember); }

/* Sheets. What is left here is the skin. Where the sheet sits, how wide it is, how
   much it pads and where it scrolls are scene.css's, because those are the four
   values the two layouts disagree about: a phone gets one column of prose, a desk
   gets a contents rail beside it. */
dialog {
  border: 1px solid var(--line); border-top: 2px solid var(--ember);
  background: var(--panel); color: var(--paper);
  box-shadow: 0 24px 80px color-mix(in srgb, var(--coal) 85%, transparent);
}
dialog::backdrop { background: color-mix(in srgb, var(--coal) 78%, transparent); backdrop-filter: blur(4px); }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.sheet-title { margin: 12px 0 0; font-size: clamp(24px, 4.4vw, 36px); letter-spacing: -.02em; }
.sheet-rule { height: 1px; margin: 14px 0 2px; background: linear-gradient(90deg, var(--ember), color-mix(in srgb, var(--ember-dark) 55%, transparent) 42%, transparent 92%); }
.sheet-note { margin-bottom: 2px; }
.sheet-block { margin-top: 28px; }
.sheet-block h3 { display: flex; align-items: center; gap: 10px; margin: 0 0 4px; font-size: var(--text-md); font-weight: 900; }
.sheet-icon { width: 20px; height: 20px; color: var(--ember); }
/* The contents list. The guide is five short groups rather than one long column, and
   this is what makes that worth doing: a reader who came for one answer jumps to it
   instead of scrolling past four groups that are not it. */
/* Whether the contents sit in a row above the prose or in a column beside it is
   scene.css's; the chips themselves look the same either way. */
.sheet-toc { margin-top: 18px; }
.sheet-toc ul { margin: 8px 0 0; padding: 0; list-style: none; }
.sheet-toc a { display: inline-flex; align-items: center; gap: 7px; padding: 7px 12px; border: 1px solid var(--line-hot);
  border-radius: 999px; font-size: var(--text-sm); color: var(--paper); text-decoration: none; }
.sheet-toc a:hover, .sheet-toc a:focus-visible { border-color: var(--ember); color: var(--ember); }
.sheet-toc-icon { width: 15px; height: 15px; color: var(--ember); }
.sheet-lead { margin: 22px 0 0; }
/* Anchor jumps land flush against the dialog's top edge without this. */
.sheet-group { scroll-margin-top: 18px; }
.sheet-steps { margin: 0; padding: 0; list-style: none; counter-reset: sheet; }
.sheet-steps li { position: relative; padding: 13px 0 13px 46px; border-top: 1px solid var(--line); }
.sheet-steps li::before {
  /* `decimal-leading-zero` rather than a literal leading zero: the step count passed 9. */
  counter-increment: sheet; content: counter(sheet, decimal-leading-zero);
  position: absolute; left: 0; top: 14px;
  font: 500 17px var(--number-font); letter-spacing: .02em; color: var(--ember);
}
.sheet-steps p { margin: 0; font-size: var(--text-sm); line-height: 1.75; color: color-mix(in srgb, var(--paper) 88%, transparent); }
.sheet-step-icon { width: 18px; height: 18px; margin-right: 9px; vertical-align: -4px; color: var(--muted); }
/* Full-bleed is a width, so how far this band reaches past its column is scene.css's
   (`margin-inline`); the band's own look is here. */
.sheet-disclaimer {
  padding: 18px var(--gutter) 20px;
  border-top: 1px solid var(--line-hot); border-bottom: 1px solid var(--line-hot);
  background: color-mix(in srgb, var(--ember-dark) 14%, transparent);
}
.sheet-terms { margin: 10px 0 0; padding: 0; list-style: none; }
.sheet-terms li { position: relative; padding: 8px 0 8px 20px; font-size: var(--text-sm); line-height: 1.75; color: var(--muted); }
.sheet-terms li::before { content: ''; position: absolute; left: 0; top: 18px; width: 9px; height: 1px; background: var(--ember); }

/* Surfaces */
.panel { padding: 18px; margin: 18px 0; overflow-wrap: anywhere; border: 1px solid var(--line-hot); background: var(--panel); }
.panel p { margin: 6px 0; }
/* The recap the last step of both flows opens with: a label and a value per row, on one
   line while they fit and on two when they do not. */
.review .eyebrow { display: block; margin-bottom: 10px; }
.review-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
.review-key { flex: none; color: var(--muted); font-size: var(--text-xs); letter-spacing: .08em; }
.review-value { font-weight: 700; }
/* The one optional field in the desk, folded. Same idiom as the FAQ's answers: the
   marker goes, a chevron takes its place, and the open state turns it over. */
.flow-more { margin: 24px 0 0; border-top: 1px solid var(--line); }
.flow-more summary { position: relative; padding: 18px 40px 18px 0; cursor: pointer; list-style: none; font-size: var(--text-sm); color: var(--muted); }
.flow-more summary::-webkit-details-marker { display: none; }
.flow-more summary::after {
  content: ''; position: absolute; right: 4px; top: 50%; width: 8px; height: 8px;
  border-right: 1px solid var(--line-hot); border-bottom: 1px solid var(--line-hot);
  transform: translateY(-70%) rotate(45deg); transition: transform .2s ease;
}
.flow-more[open] summary::after { transform: translateY(-30%) rotate(-135deg); }
.flow-more summary:hover { color: var(--paper); }
.flow-more .field { margin-top: 0; }
.link-box { margin: 26px 0; }
.link-box input { margin-bottom: 12px; font-family: var(--number-font); font-size: 18px; letter-spacing: .04em; }
.recovery-note { margin-bottom: 20px; color: var(--muted); font-size: var(--text-xs); }
.team-complete { padding-left: 10px; border-left: 3px solid var(--ember); font-size: var(--text-xs); animation: enter .25s ease-out; }
.copy-done { animation: copy-confirm .28s ease-out; }
.roster-heading { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; margin-top: 32px; }
.my-teams { display: flex; flex-direction: column; align-items: flex-start; }
.my-teams a { padding: 12px 0; text-decoration: none; font-weight: 700; }
.my-teams a:hover { color: var(--ember); }

/* The team-created screen. The roster and the link are the two things that matter,
   so the name is set large, the count sits under it, and the link gets its own
   block rather than being the third item in a list. */
.lineup { position: relative; padding-bottom: 40px; }
.lineup-name { margin: 6px 0 4px; font-size: clamp(34px, 5.4vw, 76px); line-height: 1.05; overflow-wrap: anywhere; }
.lineup-count { display: flex; align-items: baseline; gap: 6px; margin: 0 0 8px; color: var(--muted); }
.lineup-count strong { font-family: var(--number-font); font-size: clamp(30px, 3.6vw, 54px); font-weight: 500; line-height: 1; color: var(--ember); }
.lineup-count span { font-family: var(--number-font); font-size: var(--text-sm); letter-spacing: .1em; }
.lineup-count small { margin-left: 10px; font-size: var(--text-xs); letter-spacing: .08em; }
.lineup::before {
  content: ''; pointer-events: none; position: absolute; inset: -20px 0 auto; height: 160px;
  background: radial-gradient(ellipse, var(--scorch), transparent 65%);
  animation: smoke .7s ease-out both;
}
.share-block { margin-top: 34px; padding-top: 24px; border-top: 1px solid var(--line-hot); }
.share-lead { margin: 0; font-size: clamp(18px, 1.7vw, 26px); font-weight: 700; }
.cancel-line { margin: 30px 0 0; }
.ending { margin-top: 42px; font-size: 22px; }
.ending p:last-child { font-family: var(--number-font); font-size: var(--text-sm); letter-spacing: .2em; color: var(--muted); }

/* Entrance sequence */
.entrance {
  position: fixed; inset: 0; z-index: var(--z-entrance);
  display: grid; place-items: center; overflow: hidden; background: var(--coal);
}
.entrance::before {
  content: ''; position: absolute; inset: -20%; opacity: .34; pointer-events: none;
  background: var(--arena) center / cover;
}
.entrance-veil {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 40%, var(--scorch), transparent 55%),
    linear-gradient(180deg, transparent, var(--coal));
}
.entrance-marks { position: absolute; inset: clamp(16px, 2.4vw, 40px); pointer-events: none; }
.entrance-marks::before, .entrance-marks::after {
  content: ''; position: absolute; width: clamp(40px, 4.4vw, 76px); height: clamp(40px, 4.4vw, 76px);
  background: var(--warn-mark) center / contain no-repeat; opacity: .75;
}
.entrance-marks::before { top: 0; left: 0; }
.entrance-marks::after { right: 0; bottom: 0; transform: rotate(180deg); }
.entrance-content { position: relative; z-index: 1; display: grid; justify-items: center; gap: 16px; padding: 20px; text-align: center; }
.entrance-content > p, .entrance-brand { margin: 0; font-size: var(--text-sm); letter-spacing: .3em; }
.entrance-cup { font-size: clamp(64px, 15vw, 220px); line-height: 1.02; letter-spacing: -.055em; text-shadow: 0 0 90px color-mix(in srgb, var(--ember) 30%, transparent); }
.entrance-rule { width: min(320px, 62vw); height: 1px; background: linear-gradient(90deg, transparent, var(--ember), transparent); }
.entrance-reward { padding-top: 18px; border-top: 1px solid var(--ember-dark); font: var(--text-sm) var(--number-font); letter-spacing: .1em; }
.entrance-status { min-height: 1.2em; color: var(--muted); font: var(--text-sm) var(--number-font); letter-spacing: .22em; }
.entrance-skip { position: absolute; z-index: 2; bottom: max(30px, env(safe-area-inset-bottom)); right: 30px; min-height: 48px; }
html.entrance-active { overflow: hidden; }
html.entrance-active #app { filter: blur(8px) scale(1.02); }

/* Footer */
footer {
  display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 16px 28px;
  padding-block: 20px 28px; border-top: 1px solid var(--line);
  font-size: var(--text-xs); letter-spacing: .08em; text-align: center; color: var(--dim);
}
footer a { color: inherit; text-decoration: none; }
footer a:hover { color: var(--muted); }

/* Admin */
.admin-login { animation: enter .22s ease-out; }
.admin-login h1 { margin-bottom: 24px; }
.admin-toolbar { display: flex; flex-wrap: wrap; gap: 10px; }
.admin-members { margin: 14px 0; }
.admin-member { padding: 12px 0; border-top: 1px solid var(--line); font-size: var(--text-sm); }
.admin-member strong { display: block; margin-bottom: 4px; }
/* 全場報到 (§23.6): every team's count on one wrapping line, so the desk takes in the
   whole field at a glance the way the bot's panel does. The entries are spans because
   the separation is layout and not a character — a copy string between two team names
   would be a string that had to be right in two places. */
.roll-call-teams { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 10px 0 0; font-size: var(--text-sm); }
.roll-call-team { color: var(--muted); }
/* Whether this person is still in the guild. Only the state that needs acting on
   is coloured; "in the server" is the expected case and stays quiet. */
.membership { color: var(--muted); }
.membership[data-state="not_member"] { color: var(--ember); }
.membership[data-state="failed"] { color: var(--paper); }

/* The `.admin-toolbar` rules above are shared with `.admin-devtools` no longer —
   that block and the two buttons it held are gone (§9.1). `.admin-toolbar` itself
   stays: the page's main toolbar uses it, and so does `scene.css`. */

/* Inline SVG figures from graphics.js. Colour comes from currentColor and
   data-state; no figure ever emits a style attribute (CSP forbids it). */
.icon, .stage-icon, .step-icon, .step-mark-icon, .crest-icon { flex: none; }
.stage-icon { width: 26px; height: 26px; }

.seat-board { display: block; width: 100%; max-width: 320px; }
.seat-board .seat path { fill: color-mix(in srgb, var(--coal) 62%, transparent); stroke: var(--line); stroke-width: 1; transition: fill var(--dur-base), stroke var(--dur-base); }
.seat-board .seat text { fill: var(--muted); font: 500 9px var(--number-font); letter-spacing: .05em; }
.seat-board .seat-link { fill: none; stroke: color-mix(in srgb, var(--muted) 34%, transparent); stroke-width: 1; }
.seat-board .seat[data-state="verified"] path { stroke: var(--ember); fill: color-mix(in srgb, var(--ember-dark) 55%, transparent); }
.seat-board .seat[data-state="verified"] text { fill: var(--paper); }
.seat-board .seat[data-state="connected"] path { stroke: var(--ember); stroke-width: 1.5; fill: color-mix(in srgb, var(--ember) 70%, var(--coal)); }
.seat-board .seat[data-state="connected"] text { fill: var(--paper); font-weight: 700; }
.seat-board .seat[data-state="connected"] { animation: node-in .5s var(--ease-out) both; }
.seat-board .seat[data-state="connected"] path { filter: drop-shadow(0 0 6px color-mix(in srgb, var(--ember) 55%, transparent)); }
/* The substitute pair is advice, not a requirement. Held back in weight so the
   three seats that actually complete a team read first. */
.seat-board .seat[data-tier="advisory"] { opacity: .55; }
.seat-board .seat[data-tier="advisory"] text { font-size: 8px; }
.seat-board .seat[data-tier="advisory"][data-state="connected"] { opacity: .82; }
.seat-board .seat[data-tier="advisory"][data-state="connected"] path { filter: none; }

/* One cell per team, twelve columns wide: the field reads as a single row, so
   "how much room is left" is a glance rather than a count. */
.capacity-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 3px; width: 100%; }
.capacity-grid i { display: block; height: 7px; background: color-mix(in srgb, var(--paper) 9%, transparent); box-shadow: inset 0 0 0 1px var(--line); }
.capacity-grid i.on { background: var(--ember); box-shadow: none; }

/* The team page (/t/<code>): the same roster the result screen draws, plus the
   three things only a member needs — who is still outside the server, what is
   left to do, and a join password the captain can read out loud. Scoped to
   `.team-page` so it wins over reactive.css, which loads after this file. */
.team-board { display: flex; justify-content: center; margin: 10px 0 0; }
.team-seats { display: grid; gap: 10px; margin: 18px 0 28px; }
/* 本場上場 (§11): the three seats this match plays. A badge beside the seat label,
   not a block of its own — a lineup is a fact about seats that are already here. */
.roster-node .node-lineup { margin-left: 8px; font-style: normal; font-size: 11px; letter-spacing: .12em; color: var(--ember); }
.team-line { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.team-line .team-platform { width: 17px; height: 17px; color: var(--muted); }
.team-line em { font-style: normal; font-size: 12px; letter-spacing: .08em; }
.team-line em[data-state="member"] { color: var(--ember); }
.team-line em[data-state="not_member"] { color: var(--paper); }
/* A seat someone has left is still taken — it is not lit. The person has to come
   back to the server before the light means anything. */
.team-page .roster-node[data-membership="not_member"][data-phase="verified"] { border-color: var(--line-hot); background: var(--panel); box-shadow: none; }
.team-page .roster-node[data-membership="not_member"] .node-signal { background: var(--muted); box-shadow: none; animation: none; }
.team-page .team-invite .share-lead { margin: 8px 0 12px; }
.team-page .team-invite .actions { margin: 16px 0 0; }
.team-page .team-password { margin: 16px 0 0; padding: 14px 18px; border: 1px solid var(--ember); background: color-mix(in srgb, var(--ember-dark) 26%, var(--panel)); }
.team-page .team-password strong { display: block; margin-bottom: 6px; font-family: var(--number-font); font-size: clamp(24px, 3vw, 34px); font-weight: 700; letter-spacing: .3em; color: var(--ember); }
.team-page .team-checks { display: grid; gap: 9px; margin: 12px 0 0; padding: 0; list-style: none; }
.team-page .team-checks li { display: flex; align-items: center; gap: 10px; font-size: var(--text-sm); }
.team-page .team-checks li[data-state="idle"] { color: var(--muted); }
.team-page .check-mark { display: grid; place-items: center; flex: none; width: 18px; height: 18px; border: 1px solid var(--line-hot); color: var(--ember); }
.team-page .team-checks li[data-state="done"] .check-mark { border-color: var(--ember); }
.team-page .team-check-icon { width: 12px; height: 12px; }
.team-page .team-schedule .schedule-track { margin-top: 14px; }
/* Where this team stands in the queue (2026-09-14). The number is the whole point of the
   block, so it is the one thing sized like a headline; the two lines under it are the
   rule and what to do about it. Drawn only for a team that is actually waiting. */
.team-page .team-queue { border-color: var(--line-hot); }
.team-page .queue-position { margin: 10px 0 8px; }
.team-page .queue-position strong {
  font-family: var(--number-font); font-size: clamp(22px, 3vw, 32px); font-weight: 700;
  letter-spacing: .08em; color: var(--ember);
}
/* The to-do list. It empties itself, and a team with nothing left to do is told so
   rather than shown an empty box — which is why the done state is a sentence and not a
   list with no items in it. */
.team-page .todo-list { display: grid; gap: 8px; margin: 12px 0; padding: 0 0 0 18px; font-size: var(--text-sm); }
.team-page .team-todo[data-state="done"] { border-color: var(--line); color: var(--muted); }
.team-gate { max-width: 640px; margin-inline: auto; }

.prize-crest {
  display: grid; place-items: center; margin-bottom: 14px;
  width: clamp(54px, 6.5vw, 104px); height: clamp(54px, 6.5vw, 104px);
  border: 1px solid var(--ember); color: var(--ember);
  box-shadow: 0 0 40px color-mix(in srgb, var(--ember) 18%, transparent);
}
.crest-icon { width: 54%; height: 54%; }

.step-mark {
  display: grid; place-items: center; width: 48px; height: 48px; margin-bottom: 14px;
  border: 1px solid var(--line-hot); color: var(--ember);
  background: color-mix(in srgb, var(--coal) 60%, transparent);
}
.step-mark-icon { width: 26px; height: 26px; }
.entry-morph .step-icon { display: block; width: 28px; height: 28px; margin-bottom: 12px; color: var(--ember); }

@keyframes breathe { 50% { opacity: .62; } }
@keyframes enter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes smoke { from { opacity: .55; transform: translateY(10px); } to { opacity: 0; transform: translateY(-18px); } }
@keyframes sweep { to { transform: translateX(130%); } }
@keyframes pulse-lamp { from { filter: brightness(1.6); } to { filter: none; } }
@keyframes copy-confirm { 50% { box-shadow: inset 0 0 0 2px var(--paper); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
  .lineup::before { display: none !important; }
  .entrance { display: none; }
  html.fx-static body::before { opacity: .18; }
}

/* The join password beside the short link on the created screen (2026-09-12). The
   two belong together: a link without the password is a link a teammate is refused
   at, which is the bug this block exists to make impossible to walk into. */
.link-box-password { margin-top: 10px; }
.link-tag {
  flex: none; padding: 0 10px; font-size: var(--text-xs); letter-spacing: .12em;
  color: var(--muted); align-self: center;
}
.link-box-password input { font-family: var(--number-font); letter-spacing: .22em; }
/* A team with no password cannot accept anybody. That is not a hint, it is a fault
   to fix, so it is coloured like one. */
[data-password="missing"] { color: var(--ember); }

/* The gate, already answered by the captain's link (2026-09-12). It reads as a
   settled fact rather than an empty field, but the field is still one fold away —
   a retired password has to be correctable here or the visitor is stuck. */
.gate-passed [data-invite="passed"] { color: var(--verified, var(--muted)); }
.gate-passed .flow-more { margin-top: 8px; }

/* ── Apex Tag, launcher, and the three blocks added on 2026-09-13 ──────────────
   The tag is the one value on this site meant to be read by a stranger across a
   room: it is set in three or four characters, worn by every member of the team in
   front of their in-game name, and the organiser reads it out of a killfeed. So it
   is typeset like a number, not like prose — the display face, wide tracking, upper
   case — and the example beside it is the whole point of the field. */
.team-tag-line {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px;
  margin: 4px 0 14px; font-size: var(--text-sm); letter-spacing: .14em; color: var(--muted);
}
.team-tag-line strong {
  font-family: var(--display-font); font-size: clamp(20px, 2.4vw, 30px);
  letter-spacing: .26em; color: var(--ember); text-transform: uppercase;
}
.team-tag-line small { color: var(--dim); letter-spacing: .06em; }
.team-tag-line[data-tag="none"] strong { color: var(--muted); }
.team-tag input[name="apexTag"] {
  font-family: var(--display-font); font-size: 22px; letter-spacing: .28em; text-transform: uppercase;
}
[data-tag="blocked"], [data-profile="blocked"], [data-withdraw="blocked"] { color: var(--ember); }
/* The launcher beside the platform on a roster row: a second small mark, not a
   second line — it answers "which shop", which is a footnote to "which machine". */
.node-launcher { color: var(--dim); letter-spacing: .08em; }
/* Leaving for good sits last and looks like it: the panel is banded in the warning
   colour so it cannot be mistaken for one of the settings above it. */
.team-withdraw { border-color: var(--line-hot); }
.team-withdraw .share-lead { color: var(--muted); }

/* The organiser's in-game identification table. One card per live team, the tag
   large enough to find at a glance, and the members under it as launcher｜name —
   which is the order the organiser reads them in the lobby search. Nothing here
   counts anything: the scores are worked out by hand. */
.identify-grid {
  display: grid; gap: 12px; margin-top: 12px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.identify-team { padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius); }
.identify-team h3 { margin: 0 0 6px; font-size: var(--text-md); }
.identify-tag { display: flex; align-items: baseline; gap: 8px; margin: 0 0 8px; }
.identify-tag span { font-size: var(--text-xs); letter-spacing: .2em; color: var(--muted); text-transform: uppercase; }
.identify-tag strong {
  font-family: var(--display-font); font-size: 26px; letter-spacing: .24em; color: var(--ember);
}
.identify-tag[data-tag="none"] strong { font-size: var(--text-sm); letter-spacing: .08em; color: var(--muted); }
.identify-members { margin: 0; padding: 0; list-style: none; font-size: var(--text-sm); }
.identify-members li { display: flex; gap: 8px; padding: 3px 0; border-top: 1px solid var(--line); }
.identify-seat { flex: none; min-width: 5.5em; color: var(--muted); }
/* The EA account name beside the in-game one, and only when the two differ (2026-09-13).
   It is the second of the two names the organiser needs — one to read off the screen,
   one to type into the lobby search — so it sits at the end of the row, quieter. */
.identify-account { margin-left: auto; padding-left: 10px; color: var(--dim); font-style: normal; }

/* The waiting list on the desk (2026-09-14), in the order a released place is handed
   out. The rows carry their own reason — a team with a substitute is marked, because
   that is what moved it up — so the organiser can answer "why them" from the screen. */
.waiting-list { margin: 12px 0 0; padding-left: 22px; font-size: var(--text-sm); }
.waiting-list li { display: flex; align-items: baseline; gap: 10px; padding: 4px 0; border-top: 1px solid var(--line); }
.waiting-list li:first-child { border-top: 0; }
.waiting-list li[data-benched="yes"] em {
  font-style: normal; font-size: var(--text-xs); letter-spacing: .12em;
  padding: 1px 7px; border: 1px solid var(--line-hot); color: var(--ember);
}

/* 正式賽積分 desk (2026-09-26): the organiser's half of /point. The pending block is the
   one thing on the desk that must not be missed, so it is the only loud one. */
.admin-points .pts-block { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); }
.admin-points .pts-sub { margin: 0 0 6px; font-weight: 900; font-size: var(--text-md); }
.admin-points details > summary.pts-sub { cursor: pointer; }
.admin-points .pts-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.admin-points .pts-table th, .admin-points .pts-table td { padding: 6px 10px 6px 0; text-align: left; border-bottom: 1px solid var(--line); }
.admin-points .pts-table th { font-weight: 500; color: var(--muted); font-size: var(--text-xs); }
.admin-points .pts-table select, .admin-points .pts-table input { min-height: 40px; width: 100%; max-width: 110px; padding: 6px 8px;
  background: var(--coal); color: var(--paper); border: 1px solid var(--line); font: inherit; }
.admin-points tr[data-state='error'] select { border-color: var(--ember); }
.admin-points .pts-audit { font-size: var(--text-sm); color: var(--muted); }
.admin-points .pts-audit[data-state='error'] { color: var(--ember); }
.admin-points .pts-pick { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 4px 16px; margin: 10px 0; }
.admin-points .pts-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.admin-points .pts-chips span { padding: 2px 8px; border: 1px solid var(--line); font-size: var(--text-xs); }
.admin-points .pts-pending { margin-top: 16px; padding: 18px; border: 1px solid var(--line-hot); background: color-mix(in srgb, var(--ember-dark) 22%, transparent); }
.admin-points .pts-pending.is-champion { border: 2px solid var(--ember); background: color-mix(in srgb, var(--ember-dark) 45%, transparent); }
.admin-points .pts-pending-title { margin: 0; font-weight: 900; font-size: clamp(20px, 2vw, 28px); }
.admin-points .pts-pending-champion { margin: 8px 0 0; font-weight: 900; font-size: clamp(26px, 3vw, 44px); color: var(--ember); }
.admin-points .pts-checks, .admin-points .pts-facts, .admin-points .pts-log { margin: 6px 0 0; padding-left: 18px; font-size: var(--text-sm); }
.admin-points .pts-checks li[data-state='warn'] { color: var(--ember); font-weight: 700; }
.admin-points tr[data-dq='yes'] { opacity: .55; }
.admin-points .pts-penalties { display: grid; gap: 10px; margin-top: 10px; }
.admin-points .pts-penalty { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 8px 0; border-bottom: 1px solid var(--line); font-size: var(--text-sm); }
.admin-points .pts-penalty input:not([type='checkbox']) { margin-left: 6px; width: 7em; min-height: 36px; padding: 4px 8px; background: var(--coal); color: var(--paper); border: 1px solid var(--line); font: inherit; }
.admin-points .pts-penalty input[data-pen='note'] { width: 14em; }
.admin-points .pts-games { display: grid; gap: 8px; margin: 6px 0 0; padding-left: 20px; font-size: var(--text-sm); }
.admin-points .pts-games li { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.admin-points .pts-games em { color: var(--muted); font-style: normal; }
.admin-points .pts-message { min-height: 1.5em; margin: 16px 0 0; font-size: var(--text-sm); color: var(--paper); }
.admin-points .pts-table-wrap, .admin-points .pts-pending { overflow-x: auto; }
/* The scoreboard link sits beside the guide button in the masthead (2026-09-26). */
.header-links { display: inline-flex; align-items: center; gap: 22px; }
.header-links a { text-decoration: none; }
