/* ═══════════════════════════════════════════════════════════
   VOID CANVAS · Smart Ctrl+V Sniffer Canvas
   Zen Mode → Transformers Morph → Split-Screen Workspace
   ═══════════════════════════════════════════════════════════ */

:root {
  --bg: #020306;
  --card: #0c0e14;
  --edge: #121624;
  --txt: #d7e0ea;
  --txt-dim: #5b6779;
  --mono: "JetBrains Mono", "Cascadia Code", Consolas, "Microsoft YaHei", monospace;
  --sans: Inter, "Segoe UI", system-ui, "Microsoft YaHei", sans-serif;

  /* Neon skin variables — JS switches these via [data-skin] */
  --glow: #4fd1c5;
  --glow-rgb: 79, 209, 197;
}

[data-skin="json"]     { --glow: #22d3ee; --glow-rgb: 34, 211, 238; }  /* glow-cyan   荧光青 */
[data-skin="mermaid"]  { --glow: #a78bfa; --glow-rgb: 167, 139, 250; } /* glow-purple 霓虹紫 */
[data-skin="csv"]      { --glow: #4ade80; --glow-rgb: 74, 222, 128; }  /* glow-green  毒液绿 */
[data-skin="code"]     { --glow: #38bdf8; --glow-rgb: 56, 189, 248; }  /* glow-azure  电光蓝 */
[data-skin="jwt"]      { --glow: #fb7185; --glow-rgb: 251, 113, 133; } /* glow-pink   电光红 */
[data-skin="markdown"] { --glow: #fbbf24; --glow-rgb: 251, 191, 36; }  /* glow-amber  琥珀金 */
[data-skin="text"]     { --glow: #e2e8f0; --glow-rgb: 226, 232, 240; } /* glow-slate  极简白 */

* { box-sizing: border-box; margin: 0; padding: 0; }

/* ═══ STRICT ONE-SCREEN LOCK ═══
   The document itself never scrolls: Zen landing, the morphing split-screen and
   every active dashboard all live inside exactly one viewport. Scrolling is
   delegated to individual inner panes (right canvas, left terminal, history bar).
   The SEO long-form footer stays in normal document flow below the fold — fully
   present in the DOM and indexable, but unreachable by the human viewport. */
html, body {
  height: 100vh;
  height: 100dvh;   /* dynamic viewport: iOS address-bar safe (falls back to vh) */
  overflow: hidden;
}

body {
  background: var(--bg);
  color: var(--txt);
  font-family: var(--sans);
  overflow: hidden;
}

/* faint global grid texture */
body::before {
  content: "";
  position: fixed; inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,.015) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.015) 1px, transparent 1px);
  background-size: 42px 42px;
  pointer-events: none;
  z-index: 0;
}

/* ═══════════ Main container: morphing grid ═══════════ */

#app-container {
  position: relative;
  z-index: 1;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  display: grid;
  /* minmax(0, 1fr) — NOT `1fr`. A bare `1fr` track keeps an automatic
     min-content floor, so one long unbreakable history preview pushed the
     whole hero column to a 620px min-width and the card got clipped on every
     viewport narrower than ~700px. minmax(0, …) lets the card shrink first and
     lets the inner panes ellipsize instead of overflowing. */
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
  padding: 24px;
  transition: grid-template-columns .6s ease-in-out, padding .6s ease-in-out;
}

#app-container.active {
  grid-template-columns: minmax(340px, 420px) minmax(0, 1fr);
  gap: 20px;
  align-items: stretch;
  padding: 20px;
}

/* ═══════════ Zen hero / left console ═══════════ */

.hero {
  width: 100%;
  min-width: 0;                 /* never larger than its grid track */
  min-height: 0;
  display: flex;
  align-items: center;          /* vertically centred inside the locked row */
  justify-content: center;
  transition: all .6s ease-in-out;
}

#app-container.active .hero {
  height: 100%;
  min-height: 0;
  justify-content: stretch;
}

.receiver-card {
  position: relative;
  width: min(680px, 100%);
  min-width: 0;                  /* shrink to the viewport, never to content */
  max-height: 100%;              /* never taller than the locked viewport */
  border-radius: 22px;
  background: var(--card);
  border: 1px solid var(--edge);
  padding: 56px 44px;
  text-align: center;
  cursor: pointer;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: width .6s ease-in-out, border-color .5s ease,
              box-shadow .5s ease, height .6s ease-in-out;
}

#app-container.active .receiver-card {
  width: 100%;
  height: 100%;
  padding: 22px;
  text-align: left;
  cursor: default;
  display: flex;
  flex-direction: column;
  border-color: rgba(var(--glow-rgb), .45);
  box-shadow:
    0 0 0 1px rgba(var(--glow-rgb), .12),
    0 0 42px rgba(var(--glow-rgb), .13),
    inset 0 0 60px rgba(var(--glow-rgb), .04);
}

/* breathing neon halo */
.breath-ring {
  position: absolute; inset: -2px;
  border-radius: 24px;
  pointer-events: none;
  box-shadow: 0 0 90px 6px rgba(var(--glow-rgb), .16);
  animation: breathe 3.6s ease-in-out infinite;
}
.breath-ring.ring-2 {
  inset: -14px;
  border-radius: 34px;
  box-shadow: 0 0 140px 22px rgba(var(--glow-rgb), .07);
  animation-delay: 1.2s;
}
@keyframes breathe {
  0%, 100% { opacity: .25; transform: scale(.995); }
  50%      { opacity: .9;  transform: scale(1.005); }
}

/* Inner scroll guard: if the landing content ever exceeds the locked viewport
   (tiny screens, a full 8-entry history strip) it scrolls inside the card
   instead of pushing the document.

   V4.10: the card body is now a flex column, and the history strip is the one
   shrinkable row. When the cache grows, the history list gives up height (and
   scrolls internally) so the type-rune tags and the legal links stay visible —
   the landing always resolves inside a single viewport, never just "scrolls a
   bit further down". */
.card-core {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  transition: opacity .4s ease, max-height .6s ease;
}

/* every sibling keeps its natural height; only the history block may shrink */
.card-core > * { flex: 0 0 auto; min-width: 0; }

#app-container.active .card-core {
  opacity: 0;
  max-height: 0;
  height: 0;
  pointer-events: none;
  overflow: hidden;
}

.sigil {
  font-size: 40px;
  color: var(--glow);
  text-shadow: 0 0 24px rgba(var(--glow-rgb), .8);
  animation: spin-sigil 14s linear infinite;
  margin-bottom: 20px;
}
@keyframes spin-sigil { to { transform: rotate(360deg); } }

.hero-title {
  font-family: var(--mono);
  font-size: clamp(20px, 3.2vw, 30px);
  font-weight: 700;
  letter-spacing: .06em;
  color: #f1f6fb;
  text-shadow: 0 0 18px rgba(var(--glow-rgb), .45);
}
.hero-title em {
  font-style: normal;
  color: var(--glow);
  text-shadow: 0 0 14px rgba(var(--glow-rgb), .9);
}

.hero-sub {
  margin-top: 16px;
  font-size: 13px;
  color: var(--txt-dim);
  letter-spacing: .04em;
}

.type-rune {
  margin-top: 30px;
  display: flex; justify-content: center; flex-wrap: wrap; gap: 8px;
}
.type-rune span {
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .14em;
  color: var(--txt-dim);
  border: 1px solid var(--edge);
  border-radius: 999px;
  padding: 5px 12px;
  transition: color .3s, border-color .3s, box-shadow .3s, transform .2s;
}
.type-rune span:hover {
  color: var(--glow);
  border-color: rgba(var(--glow-rgb), .5);
  box-shadow: 0 0 14px rgba(var(--glow-rgb), .25);
  transform: translateY(-2px);
  animation: quickplay-pulse 1.6s ease-in-out infinite;
}
/* V4.6 · quick-play demo triggers: keyboard accessible + focus ring */
.type-rune span:focus-visible {
  outline: none;
  color: var(--glow);
  border-color: var(--glow);
  box-shadow: 0 0 0 2px rgba(var(--glow-rgb), .45), 0 0 18px rgba(var(--glow-rgb), .35);
}
@keyframes quickplay-pulse {
  0%, 100% { box-shadow: 0 0 14px rgba(var(--glow-rgb), .25); }
  50%      { box-shadow: 0 0 22px rgba(var(--glow-rgb), .5); }
}
.hero-hint {
  margin-top: 14px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .16em;
  color: #3a4354;
}

/* the one guaranteed way in: browsers can always open a file picker, even when
   the clipboard is empty or clipboard-read is blocked by permissions */
.link-btn {
  font: inherit;
  letter-spacing: .16em;
  color: var(--txt-dim);
  background: none;
  border: none;
  border-bottom: 1px dashed #2b3345;
  padding: 0 1px;
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease;
}
.link-btn:hover, .link-btn:focus-visible {
  color: var(--glow);
  border-bottom-color: rgba(var(--glow-rgb), .6);
  outline: none;
}

/* ═══════════ Local signal history strip (landing · hidden while empty) ═══════════ */
.hidden { display: none !important; }
.history-strip {
  margin-top: 26px;
  text-align: left;
  border-top: 1px dashed var(--edge);
  padding-top: 14px;
  max-width: min(620px, 100%);
  min-width: 0;
  margin-left: auto;
  margin-right: auto;
  /* the only shrinkable row in the card body → the cache can never push the
     rune tags / legal links out of the locked viewport */
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
  animation: history-in .4s ease;
}
@keyframes history-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.history-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
  margin-bottom: 8px;
}
.history-title {
  font-family: var(--mono);
  font-size: 10px; letter-spacing: .22em;
  color: var(--glow);
  text-shadow: 0 0 12px rgba(var(--glow-rgb), .5);
}
.history-clear {
  font-family: var(--mono);
  font-size: 9.5px; letter-spacing: .14em;
  color: var(--txt-dim);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: 999px;
  padding: 3px 10px;
  cursor: pointer;
  transition: all .25s ease;
}
.history-clear:hover {
  color: #ff8080;
  border-color: rgba(255, 90, 90, .5);
  box-shadow: 0 0 12px rgba(255, 90, 90, .25);
}
/* One axis only: the cache never scrolls sideways — long payloads are
   abbreviated with an ellipsis instead. Vertical scrolling appears only when
   the cache is actually deep, and the height cap shrinks with the viewport so
   the strip always leaves room for the rest of the landing. */
.history-list {
  display: flex; flex-direction: column; gap: 5px;
  flex: 0 1 auto;
  min-height: 34px;              /* keep at least one clickable row visible */
  max-height: min(236px, 30vh);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;      /* no layout shift when the bar appears */
}
.history-chip {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  min-width: 0;                  /* allow the text to be abbreviated, not to overflow */
  overflow: hidden;
  flex: 0 0 auto;                /* a row is never squashed — the list scrolls instead */
  /* The right inset is a permanent slot for the ✕. Reserving it up front is what
     keeps hovering from shifting the row: the ✕ fades in over empty padding
     instead of displacing the timestamp or covering it. */
  padding: 7px 36px 7px 11px;
  background: rgba(255, 255, 255, .015);
  border: 1px solid var(--edge);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  font-family: var(--mono);
  transition: border-color .25s ease, box-shadow .25s ease, background .25s ease, transform .2s ease;
}
/* A row wrapper exists only so the ✕ can be a sibling of the chip: the chip is a
   <button>, and a button may not contain another button. */
.chip-row {
  position: relative;
  display: flex;
  flex: 0 0 auto;
  min-width: 0;
}
.chip-row .history-chip { flex: 1 1 auto; }
.chip-x {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 21px; height: 21px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  color: #6b7787;
  font-family: var(--mono);
  font-size: 10px; line-height: 1;
  cursor: pointer;
  opacity: 0;
  /* Hidden means unusable, not just unseen: an invisible button sitting over the
     row's right edge would swallow clicks meant for the chip. */
  pointer-events: none;
  transition: opacity .18s ease, color .18s ease, background .18s ease, border-color .18s ease;
}
.chip-row:hover .chip-x,
.chip-row:focus-within .chip-x { opacity: 1; pointer-events: auto; }
.chip-x:hover {
  color: #ff8080;
  border-color: rgba(255, 90, 90, .45);
  background: rgba(255, 90, 90, .12);
}
.chip-x:focus-visible {
  opacity: 1;
  pointer-events: auto;
  outline: none;
  color: #ff8080;
  border-color: rgba(255, 90, 90, .6);
  box-shadow: 0 0 0 2px rgba(255, 90, 90, .2);
}
/* Collapsing row. pointer-events is cut so a second click on the fading row
   cannot fire the delete twice while the re-render is still pending. */
.chip-row.removing {
  opacity: 0;
  transform: translateX(10px);
  pointer-events: none;
  transition: opacity .17s ease, transform .17s ease;
}
/* Touch has no hover state, so the ✕ would be unreachable on a phone. It is
   shown unconditionally there and given a bigger tap target — the reserved slot
   means this needs no layout change at all. */
@media (hover: none) {
  .chip-x { opacity: 1; pointer-events: auto; width: 26px; height: 26px; font-size: 11px; }
}
.history-chip:hover {
  border-color: rgba(var(--glow-rgb), .5);
  background: rgba(var(--glow-rgb), .06);
  box-shadow: 0 0 16px rgba(var(--glow-rgb), .18);
  transform: translateX(2px);
}
.history-chip.locked:hover {
  border-color: rgba(255, 170, 60, .45);
  background: rgba(255, 170, 60, .05);
  box-shadow: 0 0 14px rgba(255, 170, 60, .16);
}
.history-chip.shake { animation: chip-shake .4s ease; }
/* a reopen in flight: the stored file handle is being permission-checked and
   read back off disk */
.history-chip.busy { opacity: .5; pointer-events: none; }
@keyframes chip-shake {
  0%,100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}
.chip-tag {
  flex: 0 0 auto;
  font-size: 9px; letter-spacing: .12em;
  color: var(--glow);
  border: 1px solid rgba(var(--glow-rgb), .35);
  border-radius: 4px;
  padding: 2px 6px;
}
.history-chip.locked .chip-tag {
  color: #ffb45c;
  border-color: rgba(255, 180, 92, .35);
}
.chip-text {
  flex: 1 1 0;                   /* basis 0 → contributes nothing to intrinsic width */
  width: 0;
  min-width: 0;
  font-size: 11px;
  color: var(--txt-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chip-time {
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: 9px; letter-spacing: .1em;
  color: #39424f;
}
.history-note {
  margin-top: 8px;
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 9px; letter-spacing: .1em;
  color: #333b47;
}

/* editor auto-sniff status pill (replaces the manual RE-SNIFF button) */
.editor-status {
  font-family: var(--mono);
  font-size: 10px; letter-spacing: .16em;
  color: var(--glow);
  opacity: .8;
  align-self: center;
  transition: opacity .3s ease, color .3s ease;
}
.editor-status.pending {
  color: #ffb45c;
  opacity: 1;
  animation: status-blink 1s ease-in-out infinite;
}
@keyframes status-blink { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

/* transient toast (binary-history notice, history cleared, …) */
.vc-toast {
  position: fixed;
  left: 50%; bottom: 34px;
  transform: translate(-50%, 14px);
  max-width: min(560px, 88vw);
  padding: 11px 18px;
  background: rgba(12, 14, 20, .92);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(var(--glow-rgb), .35);
  border-radius: 10px;
  color: #cfe3d8;
  font-family: var(--mono);
  font-size: 11px; letter-spacing: .06em; line-height: 1.5;
  text-align: center;
  opacity: 0; pointer-events: none;
  z-index: 9999;
  transition: opacity .3s ease, transform .3s ease;
}
.vc-toast.show { opacity: 1; transform: translate(-50%, 0); }

/* left-panel editable source terminal — its own scroll pane, pinned in place */
.input-editor {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  margin-top: 4px;
  display: none;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  overflow: auto;
  overscroll-behavior: contain;
  color: #cfe3d8;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  white-space: pre;
}
#app-container.active .input-editor { display: block; }

/* ═══ Binary payload readout ═══
   Takes over the source terminal's slot (same flex:1 1 auto / min-height:0, so
   the left column keeps its exact proportions and the one-screen lock is
   untouched) but fills it with the file's real facts instead of an empty box. */
.binary-panel {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  margin-top: 4px;
  display: none;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  color: #cfe3d8;
  font-family: var(--mono);
  font-size: 12px;
}
#app-container.active .binary-panel:not(.hidden) { display: block; }

/* the two panes are mutually exclusive — a binary tab swaps the editable
   terminal out for the readout rather than stacking an empty box above it */
#app-container.active .receiver-card.is-binary .input-editor { display: none; }

.bp-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--edge);
}
.bp-badge {
  flex: 0 0 auto;
  font-size: 9px;
  letter-spacing: .18em;
  color: var(--glow);
  background: rgba(var(--glow-rgb), .1);
  border: 1px solid rgba(var(--glow-rgb), .38);
  border-radius: 5px;
  padding: 3px 6px;
}
.bp-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12.5px;
  color: var(--txt);
}

/* KEY/VALUE — two columns, values never push the panel wide */
.bp-facts {
  display: grid;
  grid-template-columns: minmax(0, 84px) minmax(0, 1fr);
  gap: 5px 10px;
  margin: 12px 0 4px;
}
.bp-facts dt {
  color: var(--txt-dim);
  font-size: 9.5px;
  letter-spacing: .14em;
  align-self: center;
}
.bp-facts dd {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11.5px;
  color: var(--glow);
}

.bp-sec { margin-top: 14px; }
.bp-sec-title {
  display: block;
  font-size: 9.5px;
  letter-spacing: .14em;
  color: var(--txt-dim);
  margin-bottom: 8px;
}
.bp-hexbox {
  border: 1px solid var(--edge);
  border-radius: 8px;
  background: rgba(0, 0, 0, .32);
  padding: 9px 8px;
  overflow-x: auto;
  overscroll-behavior: contain;
}
/* 8 bytes per row: offset · hex pairs · ASCII gutter.
   Sized to fit the narrowest supported left column (measured 292 px of usable
   width at a 390 px viewport) with a few px to spare, so the gutter — the part
   that makes a hexdump readable — is never pushed out of view. */
.bp-row {
  display: flex;
  gap: 8px;
  white-space: nowrap;
  line-height: 1.75;
  font-size: 11px;
}
.bp-off { flex: 0 0 auto; color: var(--txt-dim); }
.bp-hex { flex: 0 0 auto; color: #cfe3d8; }
.bp-asc { flex: 0 0 auto; color: rgba(var(--glow-rgb), .8); }

.bp-empty {
  border: 1px dashed var(--edge);
  border-radius: 8px;
  padding: 14px;
  text-align: center;
  color: var(--txt-dim);
  font-size: 11px;
}

.bp-note {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--edge);
  color: var(--txt-dim);
  font-size: 11px;
  line-height: 1.65;
}
.bp-note strong { color: var(--glow); font-weight: 600; }

.editor-bar {
  display: none;
  gap: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--edge);
  justify-content: space-between;
  align-items: center;
}
#app-container.active .editor-bar { display: flex; }

/* ═══════════ Cyber buttons ═══════════ */

.cyber-btn {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .12em;
  color: var(--glow);
  background: rgba(var(--glow-rgb), .07);
  border: 1px solid rgba(var(--glow-rgb), .4);
  border-radius: 8px;
  padding: 8px 14px;
  cursor: pointer;
  transition: all .25s ease;
}
.cyber-btn:hover {
  background: rgba(var(--glow-rgb), .18);
  box-shadow: 0 0 16px rgba(var(--glow-rgb), .35);
}
.cyber-btn.ghost {
  color: var(--txt-dim);
  border-color: var(--edge);
  background: transparent;
}
.cyber-btn.ghost:hover { color: var(--txt); border-color: var(--txt-dim); box-shadow: none; }

/* ═══════════ Right workspace: holographic canvas ═══════════ */

.workspace {
  position: relative;
  border-radius: 22px;
  background: var(--card);
  border: 1px solid rgba(var(--glow-rgb), .3);
  box-shadow: 0 0 60px rgba(var(--glow-rgb), .1);
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  opacity: 0;
  transform: translateX(48px) scale(.985);
  transition: opacity .6s ease .12s, transform .7s cubic-bezier(.22,.9,.24,1) .12s;
  overflow: hidden;
  /* height reserved by the multi-signal tab strip (0 while a single signal is
     open, so the single-file layout is byte-identical to the old design) */
  --tabbar-h: 0px;
}
.workspace.shown { opacity: 1; transform: translateX(0) scale(1); }
.workspace.hidden { display: none; }

.ws-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--edge);
}

.type-badge {
  display: flex; align-items: center; gap: 9px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .16em;
  color: var(--glow);
}
.badge-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--glow);
  box-shadow: 0 0 12px rgba(var(--glow-rgb), .9);
  animation: breathe 2.4s ease-in-out infinite;
}

/* ═══ Multi-signal tab strip ═══
   Opens a tab per sniffed payload. Hidden while a single signal is open so the
   original chrome-free workspace is untouched; appears the moment a batch of
   files (or a member opened from inside a ZIP) makes more than one signal.
   Tabs shrink down to 84px before the strip itself scrolls — the tab bar is the
   last thing allowed to grow a horizontal scrollbar, and only visually when it
   really has to. */
.ws-tabs {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--edge);
  background: rgba(0, 0, 0, .28);
  flex: 0 0 auto;
  min-width: 0;
  animation: tabs-in .32s ease;
}
.ws-tabs.hidden { display: none; }
@keyframes tabs-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

.ws-tab-list {
  display: flex;
  align-items: stretch;
  gap: 5px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scroll-behavior: smooth;
  padding-bottom: 1px;
}
.ws-tab-list::-webkit-scrollbar { height: 3px; }
.ws-tab-list::-webkit-scrollbar-thumb { background: rgba(var(--glow-rgb), .3); border-radius: 3px; }
.ws-tab-list::-webkit-scrollbar-track { background: transparent; }

.ws-tab {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 0 1 190px;
  min-width: 84px;
  max-width: 220px;
  padding: 6px 8px 6px 9px;
  border-radius: 9px 9px 4px 4px;
  border: 1px solid var(--edge);
  border-bottom-color: transparent;
  background: rgba(255, 255, 255, .014);
  color: var(--txt-dim);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .06em;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.ws-tab::after {
  content: "";
  position: absolute; left: 0; right: 0; top: 0;
  height: 2px;
  background: var(--glow);
  opacity: 0;
  transition: opacity .2s ease;
}
.ws-tab:hover { background: rgba(var(--glow-rgb), .06); color: var(--txt); }
.ws-tab.on {
  background: rgba(var(--glow-rgb), .1);
  border-color: rgba(var(--glow-rgb), .45);
  color: var(--txt);
  box-shadow: 0 0 18px rgba(var(--glow-rgb), .16) inset;
}
.ws-tab.on::after { opacity: 1; box-shadow: 0 0 12px rgba(var(--glow-rgb), .9); }

.ws-tab-kind {
  flex: 0 0 auto;
  font-size: 8.5px;
  letter-spacing: .12em;
  padding: 2px 5px;
  border-radius: 4px;
  color: var(--glow);
  background: rgba(var(--glow-rgb), .12);
  border: 1px solid rgba(var(--glow-rgb), .22);
}
.ws-tab-name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
  font-size: 10px;
}
.ws-tab-x {
  flex: 0 0 auto;
  width: 15px; height: 15px;
  display: grid; place-items: center;
  border-radius: 4px;
  font-size: 9px;
  color: #4a5566;
  transition: all .18s ease;
}
.ws-tab-x:hover { color: #ff6b81; background: rgba(255, 107, 129, .14); }

.ws-tab-count {
  flex: 0 0 auto;
  align-self: center;
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: .16em;
  color: #39424f;
  white-space: nowrap;
  padding-left: 4px;
}

/* the strip eats into the canvas height, so the one-screen lock stays exact */
body.has-tabs .workspace { --tabbar-h: 43px; }

/* ═══ LAYER 3 · Internal content scrolling ═══
   The canvas is the ONLY scrolling surface of the active dashboard (spec:
   overflow-y: auto + max-height: calc(100vh - 180px)). Heavy nested JSON trees,
   infinite Excel sheets and long Markdown documents scroll here while the global
   layout, the left terminal and the sticky ad tank stay perfectly pinned.
   min-height:0 is mandatory — without it a flex child refuses to shrink and the
   scrollbar never appears. overscroll-behavior:contain stops scroll chaining
   from leaking to the page on wheel/touch. */
.panel-container {
  flex: 1 1 auto;
  min-height: 0;
  max-height: calc(100vh - 180px - var(--tabbar-h, 0px));
  max-height: calc(100dvh - 180px - var(--tabbar-h, 0px));
  overflow: auto;
  overscroll-behavior: contain;
  padding: 20px;
  position: relative;
}

.holo-waiting {
  height: 100%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 18px; color: var(--txt-dim);
  font-family: var(--mono); letter-spacing: .2em; font-size: 12px;
}
.holo-scan {
  width: 120px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--glow), transparent);
  animation: scan 1.2s ease-in-out infinite;
}
@keyframes scan { 0%,100% { opacity:.2; transform: scaleX(.4);} 50% { opacity:1; transform: scaleX(1);} }

/* ═══ V4.6 · Cyberpunk Scrollbar Styling (neon glow thumb, panel + global) ═══ */
* {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--glow) 55%, transparent) #0a0c12;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track {
  background: #0a0c12;
  border-left: 1px solid #12151f;
}
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--glow) 70%, #12151f),
    color-mix(in srgb, var(--glow) 35%, #12151f));
  border: 1px solid color-mix(in srgb, var(--glow) 45%, transparent);
  border-radius: 6px;
  box-shadow: 0 0 8px color-mix(in srgb, var(--glow) 35%, transparent);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--glow);
  box-shadow: 0 0 14px var(--glow);
}
/* no arrow buttons at the ends of any bar — the neon thumb is the whole UI */
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
::-webkit-scrollbar-corner { background: #0a0c12; }

/* Ad Slot ① · Sticky Floating Tank — pinned at the workspace baseline.
   V4.7 one-screen lock: the tank now reserves its own row (no negative margin)
   so it stays perfectly static while the canvas above it scrolls, and never
   covers rendered content (sticky + bottom:0 kept for spec compliance). */
.ghost-ad {
  position: sticky;
  bottom: 0;
  flex-shrink: 0;
  z-index: 10;
  height: 100px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(12, 14, 20, .6);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-top: 1px solid #181b28;
  opacity: 0;
  transition: opacity 1s ease 1s;
}
.workspace.shown .ghost-ad { opacity: 1; }
.ghost-ad-label {
  font-family: var(--mono);
  font-size: 10px; letter-spacing: .3em;
  color: var(--txt-dim);
}
/* Ad Slot ② · Footer Banner (below SEO long-form) */
.footer-ad {
  position: static;
  border-top: 1px dashed var(--edge);
  margin-top: 30px;
  opacity: 1;
  height: auto;
  min-height: 90px;
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: transparent;
}

/* ═══════════ Renderers shared ═══════════ */

.tool-tabs { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }

.json-tree, .jwt-box, .md-body, .text-tool, .csv-wrap, .mermaid-stage {
  font-family: var(--mono);
  animation: rise .45s ease both;
}
@keyframes rise { from { opacity: 0; transform: translateY(12px);} to { opacity:1; transform: none;} }

/* --- JSON tree --- */
.json-tree { font-size: 12.5px; line-height: 1.75; }
.jt-row { display: flex; align-items: baseline; border-radius: 4px; padding: 0 6px; }
.jt-row:hover { background: rgba(255,255,255,.04); }
.jt-toggle {
  cursor: pointer; user-select: none;
  width: 18px; flex: none;
  color: var(--txt-dim);
  transition: transform .15s ease;
}
.jt-toggle.open { transform: rotate(90deg); }
.jt-key { color: #7dd3fc; margin-right: 6px; }
.jt-brace { color: var(--txt-dim); }
.jt-str { color: #a5f3a0; }
.jt-num { color: #fbbf24; }
.jt-bool { color: #f472b6; }
.jt-null { color: #64748b; font-style: italic; }
.jt-kids { margin-left: 20px; border-left: 1px solid rgba(var(--glow-rgb), .18); padding-left: 8px; }
.jt-collapse-note { color: var(--txt-dim); font-size: 11px; cursor: pointer; }
.jt-toolbar { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }

/* pretty-print 格式化视图 */
.json-pretty { position: relative; }
.json-pre {
  max-height: calc(100vh - 200px);
  white-space: pre;
  font-size: 12.5px;
}
.json-pretty-tip {
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  color: var(--txt-dim);
}

/* --- Mermaid stage --- */
.mermaid-stage {
  position: relative;
  height: 100%;
  min-height: 420px;
  border: 1px solid var(--edge);
  border-radius: 14px;
  background: radial-gradient(circle at 50% 30%, rgba(var(--glow-rgb), .05), transparent 70%);
  overflow: hidden;
  cursor: grab;
}
.mermaid-stage.dragging { cursor: grabbing; }
.mermaid-stage svg { max-width: none !important; }
.mermaid-stage .node { cursor: move; }
.mermaid-stage .node:hover rect, .mermaid-stage .node:hover polygon {
  filter: drop-shadow(0 0 6px rgba(var(--glow-rgb), .8));
}
.mm-toolbar {
  position: absolute; top: 10px; right: 10px; z-index: 5;
  display: flex; gap: 6px;
}
.mm-hint {
  position: absolute; bottom: 10px; left: 12px; z-index: 5;
  font-size: 10px; letter-spacing: .1em; color: var(--txt-dim);
}

/* --- CSV --- */
.csv-wrap { display: flex; flex-direction: column; gap: 18px; height: 100%; }
.csv-chart-box {
  position: relative;
  height: 320px; flex: none;
  border: 1px solid var(--edge);
  border-radius: 14px;
  padding: 14px;
  background: rgba(var(--glow-rgb), .025);
}
.csv-table-box { overflow: auto; border: 1px solid var(--edge); border-radius: 14px; flex: 1; min-height: 120px; }
.csv-table { border-collapse: collapse; width: 100%; font-size: 12px; }
.csv-table th, .csv-table td {
  border-bottom: 1px solid var(--edge);
  padding: 7px 12px;
  text-align: left;
  white-space: nowrap;
}
.csv-table thead th {
  position: sticky; top: 0;
  background: #0b0e15;
  color: var(--glow);
  font-family: var(--mono);
  letter-spacing: .08em;
}
.csv-table tbody tr:hover { background: rgba(var(--glow-rgb), .05); }

/* ═══ ZIP sandbox · navigable central-directory browser ═══
   The archive listing is a folder tree, not a flat dump: folders open, the
   filter searches the whole archive, and every file row extracts locally and
   hands the member to the sniffer so it lands in its own tab. */
.zip-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.zip-search {
  flex: 0 1 210px;
  min-width: 150px;
  background: rgba(0, 0, 0, .4);
  border: 1px solid var(--edge);
  border-radius: 9px;
  padding: 7px 11px;
  color: var(--txt);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .06em;
  outline: none;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.zip-search::placeholder { color: #3a4354; }
.zip-search:focus {
  border-color: rgba(var(--glow-rgb), .5);
  box-shadow: 0 0 0 2px rgba(var(--glow-rgb), .12);
}
.zip-search::-webkit-search-cancel-button { display: none; }

.zip-crumbs {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
}
.zip-crumb {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .1em;
  color: var(--txt-dim);
  background: none;
  border: none;
  border-radius: 6px;
  padding: 4px 7px;
  cursor: pointer;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color .18s ease, background .18s ease;
}
.zip-crumb:hover { color: var(--txt); background: rgba(255, 255, 255, .045); }
.zip-crumb.on { color: var(--glow); }
.zip-crumb-sep { color: #2c3444; font-size: 10px; }
.zip-status {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .14em;
  color: #39424f;
  white-space: nowrap;
}

.zip-table-box { max-height: min(46vh, 460px); }
.zip-table td { vertical-align: top; }
.zip-table .zip-name {
  white-space: normal;
  overflow-wrap: anywhere;
  max-width: 420px;
}
.zip-row-file { cursor: pointer; }
.zip-row-file:hover { background: rgba(var(--glow-rgb), .07); }
.zip-row-file.busy { opacity: .45; pointer-events: none; }
.zip-row-dir { cursor: pointer; }
.zip-row-dir .zip-name { color: var(--glow); letter-spacing: .06em; }
.zip-row-dir:hover { background: rgba(var(--glow-rgb), .09); }
.zip-table .zip-open {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .12em;
  color: #333b47;
  text-align: right;
  white-space: nowrap;
}
.zip-row-file:hover .zip-open { color: var(--glow); }

.zip-notice {
  position: sticky;
  bottom: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .06em;
  color: #fbbf24;
  border: 1px solid rgba(251, 191, 36, .3);
  background: rgba(251, 191, 36, .07);
  border-radius: 9px;
  padding: 8px 12px;
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transition: opacity .25s ease, max-height .25s ease;
}
.zip-notice.show { opacity: 1; max-height: 80px; }

/* --- JWT / Base64 --- */
.jwt-box { display: flex; flex-direction: column; gap: 16px; font-size: 13px; }
.jwt-countdown {
  display: flex; align-items: baseline; gap: 14px;
  border: 1px solid rgba(var(--glow-rgb), .35);
  border-radius: 14px;
  padding: 16px 20px;
  background: rgba(var(--glow-rgb), .05);
}
.jwt-clock {
  font-size: 34px; font-weight: 700;
  color: var(--glow);
  text-shadow: 0 0 16px rgba(var(--glow-rgb), .6);
  font-variant-numeric: tabular-nums;
}
.jwt-clock.expired { color: #ef4444; text-shadow: 0 0 16px rgba(239,68,68,.6); }
.jwt-meta { color: var(--txt-dim); font-size: 11.5px; line-height: 1.9; }
.jwt-meta b { color: var(--txt); font-weight: 600; }
/* Time-zone transparency: local wall-clock + explicit zone badge + UTC reference
   so an iat/exp is never ambiguous across collaborating time zones */
.jwt-time-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
}
.jt-local { color: var(--txt); font-variant-numeric: tabular-nums; }
.jt-zone {
  font-family: var(--mono);
  font-size: 9.5px; letter-spacing: .08em;
  color: var(--glow);
  background: rgba(var(--glow-rgb), .12);
  border: 1px solid rgba(var(--glow-rgb), .3);
  border-radius: 999px;
  padding: 0 6px;
  line-height: 1.5;
}
.jt-utc { color: #55607a; font-variant-numeric: tabular-nums; }
.b64-image { text-align: center; }
.b64-image img {
  max-width: 100%; max-height: 60vh;
  border-radius: 14px;
  border: 1px solid rgba(var(--glow-rgb), .35);
  box-shadow: 0 0 40px rgba(var(--glow-rgb), .15);
}
.b64-decoded {
  white-space: pre-wrap; word-break: break-all;
  background: #05070c;
  border: 1px solid var(--edge);
  border-radius: 12px;
  padding: 16px;
  font-size: 12.5px; line-height: 1.7;
  max-height: 50vh; overflow: auto;
}

/* --- Code (highlight.js) --- */
.code-view {
  height: 100%;
  display: flex;
  flex-direction: column;
  font-family: var(--mono);
  animation: rise .45s ease both;
}
.code-bar {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 16px;
  border: 1px solid rgba(var(--glow-rgb), .3);
  border-bottom: none;
  border-radius: 14px 14px 0 0;
  background: rgba(var(--glow-rgb), .06);
}
.code-lang {
  font-size: 11px; letter-spacing: .22em;
  color: var(--glow);
  text-shadow: 0 0 10px rgba(var(--glow-rgb), .5);
}
.code-meta { font-size: 10px; letter-spacing: .15em; color: var(--txt-dim); }
.hljs-code {
  flex: 1;
  overflow: auto;
  margin: 0;
  border: 1px solid var(--edge);
  border-radius: 0 0 14px 14px;
  background: #05070c;
  padding: 12px 0;
  font-size: 12.5px;
  line-height: 1.75;
}
.cl { display: flex; white-space: pre; }
.cl:hover { background: rgba(var(--glow-rgb), .05); }
.ln {
  flex: none;
  width: 52px;
  text-align: right;
  padding-right: 14px;
  color: #2c3547;
  user-select: none;
}
.lc { flex: 1; padding-right: 18px; color: #cfe0ee; }

/* neon dark token palette */
.hljs-code .hljs-keyword, .hljs-code .hljs-selector-tag, .hljs-code .hljs-literal,
.hljs-code .hljs-section, .hljs-code .hljs-doctag { color: #f472b6; }
.hljs-code .hljs-string, .hljs-code .hljs-regexp, .hljs-code .hljs-addition,
.hljs-code .hljs-attribute, .hljs-code .hljs-meta .hljs-string { color: #a5f3a0; }
.hljs-code .hljs-number, .hljs-code .hljs-symbol, .hljs-code .hljs-bullet { color: #fbbf24; }
.hljs-code .hljs-title, .hljs-code .hljs-title.function_, .hljs-code .hljs-name,
.hljs-code .hljs-selector-id, .hljs-code .hljs-selector-class { color: #7dd3fc; }
.hljs-code .hljs-comment, .hljs-code .hljs-quote, .hljs-code .hljs-deletion { color: #4b586b; font-style: italic; }
.hljs-code .hljs-variable, .hljs-code .hljs-template-variable, .hljs-code .hljs-attr,
.hljs-code .hljs-property { color: #e8c07d; }
.hljs-code .hljs-type, .hljs-code .hljs-class .hljs-title, .hljs-code .hljs-built_in,
.hljs-code .hljs-builtin-name { color: #67e8f9; }
.hljs-code .hljs-meta, .hljs-code .hljs-tag { color: #8b97a8; }
.hljs-code .hljs-tag .hljs-name { color: #f472b6; }
.hljs-code .hljs-emphasis { font-style: italic; }
.hljs-code .hljs-strong { font-weight: 700; }

/* --- Markdown --- */
.md-layout { display: flex; gap: 24px; align-items: flex-start; }
.md-toc {
  flex: none; width: 190px;
  position: sticky; top: 4px;
  border-left: 1px solid rgba(var(--glow-rgb), .3);
  padding-left: 12px;
  font-size: 11.5px;
}
.md-toc a {
  display: block;
  color: var(--txt-dim);
  text-decoration: none;
  padding: 4px 0;
  transition: color .2s;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.md-toc a:hover, .md-toc a.on { color: var(--glow); text-shadow: 0 0 8px rgba(var(--glow-rgb), .5); }
.md-body {
  flex: 1; min-width: 0;
  font-family: var(--sans);
  font-size: 14.5px; line-height: 1.85;
  color: #c9d4de;
}
/* GitHub-flavoured typography */
.md-body h1, .md-body h2, .md-body h3, .md-body h4 {
  color: #eef3f8; line-height: 1.35; margin: 1.6em 0 .7em; scroll-margin-top: 12px;
}
.md-body h1 { font-size: 1.9em; border-bottom: 1px solid var(--edge); padding-bottom: .35em; }
.md-body h2 { font-size: 1.45em; border-bottom: 1px solid var(--edge); padding-bottom: .3em; }
.md-body h3 { font-size: 1.2em; }
.md-body p, .md-body ul, .md-body ol, .md-body blockquote, .md-body table, .md-body pre { margin: .9em 0; }
.md-body a { color: var(--glow); text-decoration: none; }
.md-body a:hover { text-decoration: underline; }
.md-body code {
  font-family: var(--mono); font-size: .88em;
  background: rgba(var(--glow-rgb), .09);
  border: 1px solid rgba(var(--glow-rgb), .18);
  border-radius: 5px; padding: 2px 6px;
}
.md-body pre { background: #05070c; border: 1px solid var(--edge); border-radius: 12px; padding: 16px; overflow: auto; }
.md-body pre code { background: none; border: none; padding: 0; line-height: 1.7; }
.md-body blockquote {
  border-left: 3px solid rgba(var(--glow-rgb), .5);
  color: var(--txt-dim);
  padding: 2px 0 2px 14px;
}
.md-body table { border-collapse: collapse; width: 100%; }
.md-body th, .md-body td { border: 1px solid var(--edge); padding: 7px 12px; }
.md-body th { background: rgba(var(--glow-rgb), .06); }
.md-body img { max-width: 100%; border-radius: 10px; }
.md-body hr { border: none; border-top: 1px solid var(--edge); margin: 1.8em 0; }

/* --- Plain text tools --- */
.text-tool { font-size: 13px; line-height: 1.8; }
.text-out {
  white-space: pre-wrap; word-break: break-word;
  background: #05070c;
  border: 1px solid var(--edge);
  border-radius: 12px;
  padding: 16px;
  max-height: 55vh; overflow: auto;
}
.sens-hit {
  background: rgba(239,68,68,.22);
  color: #fca5a5;
  border-radius: 3px;
  padding: 0 2px;
  box-shadow: 0 0 8px rgba(239,68,68,.35);
}
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 12px;
}
.stat-card {
  border: 1px solid var(--edge);
  border-radius: 12px;
  padding: 14px;
  text-align: center;
  background: rgba(var(--glow-rgb), .03);
}
.stat-num {
  font-family: var(--mono);
  font-size: 26px; font-weight: 700;
  color: var(--glow);
  text-shadow: 0 0 12px rgba(var(--glow-rgb), .4);
}
.stat-label { margin-top: 5px; font-size: 11px; color: var(--txt-dim); letter-spacing: .1em; }
.punct-list { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 8px; }
.punct-chip {
  font-family: var(--mono); font-size: 12px;
  border: 1px solid var(--edge); border-radius: 8px; padding: 4px 10px;
  color: var(--txt-dim);
}
.punct-chip b { color: var(--glow); }

/* ═══════════ SEO footer (below the fold) ═══════════ */

.seo-footer {
  position: relative; z-index: 1;
  max-width: 900px;
  margin: 0 auto;
  padding: 70px 24px 50px;
  color: var(--txt-dim);
  font-size: 13.5px;
  line-height: 2;
}
.seo-block { margin-bottom: 34px; }
.seo-footer h2 { color: #aab6c4; font-size: 19px; margin-bottom: 12px; }
.seo-footer h3 { color: #97a3b1; font-size: 15.5px; margin-bottom: 10px; }
.seo-footer strong { color: #cfd8e2; }
.seo-tail { text-align: center; font-family: var(--mono); font-size: 11px; letter-spacing: .2em; }

/* ═══════════ Legal SPA views (#/privacy-policy, #/about) ═══════════ */

.legal-page { max-width: 760px; }
.legal-page h1 { font-size: 1.5em; color: #eef3f8; margin: 0 0 .8em; }
.legal-page h2 { font-size: 1.15em; color: var(--glow); margin: 1.6em 0 .6em; }
.legal-page p { margin: .8em 0; color: #b9c4d0; line-height: 1.9; }
.legal-page strong { color: #e2e8f0; }
.legal-page a { color: var(--glow); }
.legal-page .legal-update { color: var(--txt-dim); font-size: 12px; }

.legal-nav {
  text-align: center;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .12em;
  margin-bottom: 14px;
}
.legal-nav a { color: var(--glow); text-decoration: none; }
.legal-nav a:hover { text-shadow: 0 0 10px rgba(var(--glow-rgb), .6); }

/* ═══ On-screen legal entry (one-screen-lock companion) ═══
   The long SEO footer now lives below the locked viewport, i.e. out of human
   reach. Compliance links must therefore surface inside the first screen:
   Google AdSense review explicitly requires a discoverable Privacy Policy.
   Kept deliberately quiet so it never competes with the Zen receiver. */
.zen-legal {
  margin-top: 24px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .14em;
}
.zen-legal a {
  color: #4a5464;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color .3s, border-color .3s;
}
.zen-legal a:hover, .zen-legal a:focus-visible {
  color: var(--glow);
  border-bottom-color: rgba(var(--glow-rgb), .6);
}
.zen-legal .zl-sep { color: #2a313d; }

.faq-block h3 { color: #97a3b1; font-size: 14px; margin: 18px 0 6px; }
.faq-block p { color: var(--txt-dim); }

/* markdown rich-text actions bar */
.md-actions { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }

/* ═══════════ V4.0 shared controls ═══════════ */

.cyber-select, .cyber-input {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--txt);
  background: #05070c;
  border: 1px solid rgba(var(--glow-rgb), .35);
  border-radius: 8px;
  padding: 7px 10px;
  outline: none;
  transition: border-color .25s, box-shadow .25s;
}
.cyber-select:focus, .cyber-input:focus {
  border-color: rgba(var(--glow-rgb), .8);
  box-shadow: 0 0 12px rgba(var(--glow-rgb), .25);
}
.cyber-input:disabled { opacity: .35; }
.cyber-select option { background: #05070c; }

.schema-pre { max-height: 60vh; }

/* cron builder */
.cron-expr-line {
  display: flex; align-items: center; gap: 14px;
  margin: 12px 0;
}
.cron-expr {
  font-family: var(--mono);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: .08em;
  color: var(--glow);
  text-shadow: 0 0 14px rgba(var(--glow-rgb), .5);
}
.cron-readout { margin-bottom: 14px; }
.cron-desc {
  font-size: 13.5px;
  color: #d7e0ea;
  line-height: 1.8;
  margin-bottom: 12px;
}
.cron-breakdown {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
}
.cron-cell {
  border: 1px solid var(--edge);
  border-radius: 10px;
  padding: 10px;
  background: rgba(var(--glow-rgb), .03);
}
.cron-cell-expr { font-family: var(--mono); font-size: 15px; color: var(--glow); }
.cron-cell-label { font-size: 9.5px; letter-spacing: .18em; color: var(--txt-dim); margin: 4px 0; }
.cron-cell-desc { font-size: 11px; color: #b9c4d0; }
.cron-grid { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.cron-row { display: flex; align-items: center; gap: 12px; }
.cron-row label { flex: none; width: 130px; font-family: var(--mono); font-size: 10px; letter-spacing: .16em; color: var(--txt-dim); }
.cron-row select { width: 150px; }
.cron-row input { flex: 1; }

/* editable spreadsheet */
.cell-input {
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  color: #cfe0ee;
  font-family: var(--mono);
  font-size: 12px;
  padding: 2px 4px;
  border-radius: 4px;
}
.cell-input:focus { background: rgba(var(--glow-rgb), .12); box-shadow: 0 0 8px rgba(var(--glow-rgb), .3); }
.cell-input.head { color: var(--glow); font-weight: 600; }

/* crypto station */
.conv-row { margin-bottom: 12px; }
.conv-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 5px; }
.conv-label { font-family: var(--mono); font-size: 10.5px; letter-spacing: .18em; color: var(--glow); }
.conv-out { max-height: 120px; padding: 10px 12px; }
.lab-input {
  width: 100%;
  min-height: 90px;
  resize: vertical;
  font-size: 12.5px;
  line-height: 1.6;
  white-space: pre;
}
.lab-out { font-family: var(--mono); word-break: break-all; }

/* purifier */
.purifier-warn {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .08em;
  color: #fca5a5;
  background: rgba(239, 68, 68, .12);
  border: 1px solid rgba(239, 68, 68, .4);
  border-radius: 10px;
  padding: 12px 16px;
  box-shadow: 0 0 18px rgba(239, 68, 68, .15);
}
.purifier-warn.clean {
  color: #86efac;
  background: rgba(34, 197, 94, .1);
  border-color: rgba(34, 197, 94, .4);
  box-shadow: 0 0 18px rgba(34, 197, 94, .12);
}

/* keycode finder */
.keycode-stage {
  min-height: 70vh;
  display: flex; align-items: center; justify-content: center;
  border: 1px dashed rgba(var(--glow-rgb), .3);
  border-radius: 14px;
  cursor: pointer;
  transition: border-color .3s, box-shadow .3s;
}
.keycode-stage.armed {
  border-style: solid;
  border-color: rgba(var(--glow-rgb), .7);
  box-shadow: inset 0 0 60px rgba(var(--glow-rgb), .06);
}
.keycode-stage.pop .keycode-key { animation: kc-pop .3s ease; }
@keyframes kc-pop { 0% { transform: scale(.7); opacity: .3; } 100% { transform: scale(1); opacity: 1; } }
.keycode-info { text-align: center; padding: 30px; }
.keycode-key {
  font-family: var(--mono);
  font-size: 96px;
  font-weight: 700;
  color: var(--glow);
  text-shadow: 0 0 40px rgba(var(--glow-rgb), .6);
  line-height: 1.2;
}
.keycode-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 26px;
}
.kc-cell {
  border: 1px solid var(--edge);
  border-radius: 12px;
  padding: 14px;
  background: rgba(var(--glow-rgb), .03);
}
.kc-cell.wide { grid-column: 1 / -1; }
.kc-cell span { display: block; font-family: var(--mono); font-size: 9.5px; letter-spacing: .2em; color: var(--txt-dim); margin-bottom: 6px; }
.kc-cell b { font-family: var(--mono); font-size: 22px; color: #eef3f8; }
.keycode-hint { margin-top: 24px; font-family: var(--mono); font-size: 10.5px; letter-spacing: .2em; color: var(--txt-dim); }

/* ═══════════ V4.5 Document / Media workbenches ═══════════ */

.docx-wrap { display: flex; flex-direction: column; gap: 14px; animation: rise .45s ease both; }
.docx-paper {
  background: #f5f2ec;
  color: #26221c;
  border-radius: 12px;
  padding: 42px 52px;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 15px;
  line-height: 1.9;
  max-height: 55vh;
  overflow: auto;
  box-shadow: 0 4px 30px rgba(0,0,0,.5);
}
.docx-paper h1, .docx-paper h2, .docx-paper h3 { color: #1a1712; margin: 1.2em 0 .5em; }
.docx-paper p { margin: .8em 0; }
.docx-paper table { border-collapse: collapse; width: 100%; }
.docx-paper td, .docx-paper th { border: 1px solid #b9b2a4; padding: 6px 10px; }
.docx-paper img { max-width: 100%; }
.docx-paper ul, .docx-paper ol { padding-left: 26px; }
.docx-paper.auto-fit { font-size: 13px; line-height: 1.6; padding: 24px 28px; max-height: 75vh; }
.docx-paper.auto-fit img { max-width: 60%; height: auto; }

.pdf-frame { width: 100%; height: 68vh; border: 1px solid var(--edge); border-radius: 12px; background: #fff; }

.sheet-preview { max-height: 55vh; }

.media-box { align-items: stretch; }
.media-player { width: 100%; border-radius: 12px; background: #000; }
audio.media-player { height: 44px; }
.waveform { width: 100%; height: 120px; border: 1px solid var(--edge); border-radius: 12px; background: #05070c; }
.media-meta { font-family: var(--mono); font-size: 12px; line-height: 2; }
.media-meta b { color: var(--glow); }

.frames-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; }
.frame-cell { border: 1px solid var(--edge); border-radius: 10px; overflow: hidden; }
.frame-cell img { width: 100%; display: block; cursor: zoom-in; }
.frame-time { font-family: var(--mono); font-size: 9.5px; color: var(--txt-dim); padding: 6px 8px; }

/* AI box + settings modal */
.ai-box { border: 1px dashed rgba(var(--glow-rgb), .35); border-radius: 14px; padding: 14px; margin-top: 6px; }
.ai-box-bar { display: flex; gap: 8px; margin-bottom: 10px; }
.ai-box-out { font-family: var(--mono); font-size: 12px; color: var(--txt-dim); line-height: 1.7; }
.ai-answer { white-space: pre-wrap; color: #cfe0ee; }

.ai-modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0,0,0,.7);
  backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  overflow-y: auto;              /* tall form scrolls inside the overlay */
  overscroll-behavior: contain;  /* never chains onto the locked page */
}
.ai-modal {
  width: min(520px, 92vw);
  max-height: calc(100vh - 32px); /* the save button must stay reachable */
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #0b0e15;
  border: 1px solid rgba(var(--glow-rgb), .4);
  border-radius: 18px;
  padding: 22px;
  box-shadow: 0 0 60px rgba(var(--glow-rgb), .15);
}
.ai-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.ai-modal-title { font-family: var(--mono); letter-spacing: .18em; color: var(--glow); font-size: 13px; }
.ai-privacy-note {
  font-size: 11.5px; line-height: 1.8;
  color: #b9c4d0;
  background: rgba(34, 197, 94, .07);
  border: 1px solid rgba(34, 197, 94, .3);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 14px;
}
.ai-privacy-note strong { color: #86efac; }
.ai-field { display: block; margin-bottom: 10px; }
.ai-field span { display: block; font-family: var(--mono); font-size: 9.5px; letter-spacing: .2em; color: var(--txt-dim); margin-bottom: 5px; }
.ai-field input { width: 100%; }
.ai-modal-actions { display: flex; gap: 10px; margin-top: 14px; }
.ai-modal-status { margin-top: 10px; font-family: var(--mono); font-size: 11px; color: var(--glow); min-height: 16px; }

/* ═══════════ Export PDF: isolate the right rendering panel ═══════════ */

@media print {
  /* release the one-screen lock — otherwise every PDF export is clipped to a
     single viewport and the "EXPORT PDF" / print actions silently lose pages */
  html, body {
    height: auto !important;
    overflow: visible !important;
  }
  body { background: #fff !important; }
  body::before { display: none !important; }
  .hero, .seo-footer, .ghost-ad, .footer-ad, .ws-bar, .paste-catcher,
  .tool-tabs, .mm-toolbar, .jt-toolbar, .code-bar, .editor-bar,
  .json-pretty-tip, .holo-waiting { display: none !important; }

  #app-container {
    display: block !important;
    padding: 0 !important;
    height: auto !important;
    min-height: auto !important;
    overflow: visible !important;
  }
  .workspace {
    position: static !important;
    height: auto !important;
    border: none !important;
    box-shadow: none !important;
    border-radius: 0 !important;
    opacity: 1 !important;
    transform: none !important;
    display: block !important;
    overflow: visible !important;
    background: #fff !important;
  }
  .panel-container {
    overflow: visible !important;
    height: auto !important;
    max-height: none !important;
    padding: 4px 0 !important;
  }
  /* neutralize dark-theme ink for print, keep syntax/diagram vector colors */
  .panel-container :is(.md-body, .md-body p, .md-body li, .text-out, .jwt-meta,
    .jt-row, .stat-label, .csv-table td, .legal-page p, .b64-decoded) {
    color: #111 !important;
  }
  .panel-container :is(.md-body h1, .md-body h2, .md-body h3, .md-body h4, .legal-page h1, .legal-page h2) {
    color: #000 !important;
    text-shadow: none !important;
  }
  .panel-container :is(.md-body pre, .text-out, .b64-decoded, .csv-table-box) {
    background: #f7f7f8 !important;
    border-color: #ddd !important;
    color: #111 !important;
  }
  .panel-container .mermaid-stage {
    border-color: #ddd !important;
    background: none !important;
  }
  .panel-container svg { filter: none !important; }
}

/* hidden helpers */
.paste-catcher {
  position: fixed; left: -9999px; top: 0;
  width: 10px; height: 10px;
  opacity: 0;
}

/* ═══ Boot watchdog notice ═══
   Shown when the module engine never executed (page opened over file://, or
   js/router.js blocked/missing). Fixed to the viewport on purpose: it must be
   readable even if the card layout is the very thing that failed. */
.boot-warning {
  position: fixed;
  left: 50%; bottom: 20px;
  transform: translateX(-50%);
  z-index: 9999;
  width: min(640px, calc(100vw - 32px));
  padding: 13px 17px;
  border-radius: 12px;
  background: rgba(30, 13, 8, .96);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 150, 60, .5);
  box-shadow: 0 0 34px rgba(255, 150, 60, .2);
  color: #ffd9a8;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.6;
  letter-spacing: .02em;
  text-align: left;
  animation: history-in .4s ease;
}
.boot-warning strong {
  display: block;
  margin-bottom: 5px;
  color: #ffb45c;
  font-size: 11px;
  letter-spacing: .16em;
  text-shadow: 0 0 12px rgba(255, 170, 60, .55);
}

/* drag-hover:拖文件悬停时的全屏接收态 */
#app-container.drag-hover .receiver-card {
  border-color: rgba(var(--glow-rgb), .85);
  box-shadow:
    0 0 0 1px rgba(var(--glow-rgb), .25),
    0 0 70px rgba(var(--glow-rgb), .3),
    inset 0 0 80px rgba(var(--glow-rgb), .08);
  transform: scale(1.02);
}
#app-container.drag-hover .hero-title { text-shadow: 0 0 26px rgba(var(--glow-rgb), .8); }
#app-container.drag-hover .breath-ring { opacity: 1; }

/* ═══════════ Responsive ═══════════ */

@media (max-width: 900px) {
  /* stacked split-screen must still fit inside the locked viewport:
     the grid rows (not fixed vh heights) own the height budget */
  #app-container.active {
    grid-template-columns: 1fr;
    grid-template-rows: 30vh minmax(0, 1fr);
    gap: 12px;
  }
  #app-container.active .hero { height: 100%; min-height: 0; }
  .workspace { height: 100%; min-height: 0; }
  .md-toc { display: none; }
  .receiver-card { padding: 40px 24px; }
}

/* Below ~400 px the left column gets too narrow for an 11 px hex row, so the
   readout steps down once. That keeps offset + hex + ASCII gutter all on screen
   down to 360 px. Narrower than that (a 320 px Galaxy Fold cover screen) the
   hexbox falls back to horizontal scrolling — degraded, but nothing is lost or
   misreported. */
@media (max-width: 400px) {
  .bp-row { font-size: 10px; gap: 6px; }
  .bp-hexbox { padding: 8px 6px; }
}
