/* Montserrat, self-hosted and subset to Latin + the punctuation this UI uses (3 weights,
 * ~50 KB total). Self-hosted rather than pulled from Google so the CSP stays at font-src 'self'
 * and no visitor is announced to a third party just for opening the tool.
 *
 * Latin only on purpose: the UI runs in five languages and subsetting a CJK face would add
 * megabytes. Korean, Japanese, Chinese and Thai fall through to the system stack, which already
 * renders them well — Montserrat carries the display type, which is where the identity lives. */
@font-face {
    font-family: 'CBK Display';
    src: url('../assets/fonts/montserrat-500.woff2?v=1') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'CBK Display';
    src: url('../assets/fonts/montserrat-700.woff2?v=1') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'CBK Display';
    src: url('../assets/fonts/montserrat-900.woff2?v=1') format('woff2');
    font-weight: 900; font-style: normal; font-display: swap;
}

/* CaseBeatKey — app styles.
 *
 * Visual language taken from pifofficial.com: flat charcoal ground, heavy uppercase display type,
 * gradient-edged cards and the signature red #f2112c as a neon accent. The red is rationed —
 * brand, live state, beat pulse, meters — and never fills the big numerals, because a saturated
 * red readout on black is punishing to stare at across a set. Colour on the Camelot badge and
 * wheel is functional: it encodes wheel position, so neighbouring keys look neighbouring.
 *
 * PERFORMANCE RULES (the reason this looks heavier than it costs)
 *  - No `filter: blur()` anywhere. PIF's ambient glows use blur(120px), which is a real
 *    compositing cost on a phone; the same picture comes free from a radial-gradient.
 *  - No `backdrop-filter`. On a flat dark ground a translucent fill looks identical and costs
 *    nothing.
 *  - Animation is limited to transform and opacity, so the beat pulse never triggers layout or
 *    paint — only compositing.
 *  - Gradient borders use the padding-box/border-box trick: pure paint, no extra elements.
 *  - Everything honours prefers-reduced-motion.
 *
 * The login screen carries its own nonce'd styles, so this file is only ever served to an
 * authenticated session.
 */

:root {
    color-scheme: only dark;
    /* Sampled from the LION x PIF proposal deck: #151515 is 78% of every pixel in it, and the
       red on the wordmark measures exactly #F2112C. The ground is deliberately not pure black. */
    --bg: #151515;
    --panel: #101010;
    --panel-2: #191919;
    --line: #262626;
    --line-hi: #3d3d3d;
    --text: #EAEAEA;
    --dim: #8A8A8A;
    --dimmer: #5E5E5E;
    --display: 'CBK Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --red: #f2112c;
    --red-2: #ff4d6d;
    --red-glow: rgba(242, 17, 44, .45);
    --red-soft: rgba(242, 17, 44, .3);
    --ok: #5DBB83;
    --warn: #F59E0B;
    --radius: 10px;
    --edge: linear-gradient(150deg, #2e2e2e 0%, #1d1d1d 45%, #272727 100%);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic", Roboto,
          "Helvetica Neue", Arial, sans-serif;
    min-height: 100vh;
    padding-bottom: calc(40px + env(safe-area-inset-bottom));
    overflow-x: hidden;
}

/* Ambient glow. A gradient, not a blurred element — same picture, no compositing cost. */
.glow {
    position: fixed;
    inset: -20% -20% auto -20%;
    height: 70vh;
    pointer-events: none;
    z-index: 0;
    background:
        radial-gradient(60% 55% at 50% 0%, rgba(242, 17, 44, .07) 0%, transparent 68%);
}

.wrap {
    max-width: 980px;
    margin: 0 auto;
    padding: 0 max(18px, env(safe-area-inset-left));
    position: relative;
    z-index: 1;
}

/* ── top brand bar ──
 * Full-bleed strip above everything: the owner's mark is the first thing on screen, and it sits
 * on its own line so it reads as "Casepeat presents…" rather than competing with the product
 * wordmark below it. The Casepeat C mark that used to sit inside the header lockup was removed
 * when this arrived — two marks from the same brand stacked 60px apart just looked like a
 * mistake.
 * The PNG is 469px wide and renders at 150 (124 on phones), i.e. ~3x density, so it stays crisp
 * on any screen without shipping a second asset. */
.topbar {
    position: relative; z-index: 2;
    background: linear-gradient(180deg, #0b0b0b, #0e0e0e);
    border-bottom: 1px solid #1c1c1c;
    box-shadow: 0 1px 0 rgba(242, 17, 44, .16);
}
.topbar .wrap {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    padding-top: calc(12px + env(safe-area-inset-top)); padding-bottom: 12px;
}
.topbar-logo { display: inline-flex; align-items: center; min-width: 0; transition: opacity .18s; }
.topbar-logo img { display: block; height: auto; }

/* The two marks have very different aspect ratios — Casepeat is 6.4:1, the PIF one-line lockup
   is 15.5:1 — so matching their heights would leave PIF a third the width and looking like an
   afterthought. They are sized to roughly equal visual mass instead (≈3,100 px² each), which is
   what actually reads as balanced across the bar. */
.topbar-logo.cp  { opacity: .95; }
.topbar-logo.cp img  { width: 142px; }     /* → 22px tall */
.topbar-logo.pif { opacity: .78; }         /* secondary mark, held back a touch */
.topbar-logo.pif img { width: 214px; }     /* → 14px tall */
.topbar-logo:hover { opacity: 1; }

/* ── deck furniture ──
 * Three devices lifted from the LION x PIF proposal, because they are what makes those pages
 * recognisable at a glance rather than the colours alone:
 *   1. a short red rule hanging from the top-left of the page
 *   2. a rotated, widely-tracked label running up the left edge
 *   3. an oversized ghosted wordmark sitting in the bottom-right
 * All three are decoration: aria-hidden, pointer-events none, and dropped entirely on narrow
 * screens where the margins they live in do not exist. */
/* Anchored to the gutter, not to the viewport centre. The first version used 50vw offsets and
   landed the rule *inside* the 980px content column at 1280px wide — measured, not guessed.
   These derive the gutter the same way the layout does, so the furniture can never encroach. */
.rule {
    position: fixed; top: 0;
    left: calc(max(0px, (100vw - 980px) / 2) - 28px);
    width: 3px; height: 88px; background: var(--red); z-index: 3;
    pointer-events: none;
}
.sidelabel {
    position: fixed; bottom: 96px;
    left: calc(max(0px, (100vw - 980px) / 2) - 54px);
    transform: rotate(180deg); writing-mode: vertical-rl;
    font-family: var(--display);
    font-size: 9px; font-weight: 500; letter-spacing: .42em; text-transform: uppercase;
    color: var(--dimmer); z-index: 2; pointer-events: none; white-space: nowrap;
}
.ghost {
    position: fixed; right: 1.5vw; bottom: 1.5vh; z-index: 0;
    font-family: var(--display);
    font-size: clamp(34px, 5.2vw, 74px); font-weight: 900; letter-spacing: .05em;
    color: transparent; -webkit-text-stroke: 1px #232323;
    pointer-events: none; user-select: none; line-height: 1;
}
@media (max-width: 1180px) { .rule, .sidelabel { display: none; } }
@media (max-width: 760px)  { .ghost { display: none; } }


/* ── header ── */
header {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
    padding: 26px 0 18px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 20px;
}
/* Product wordmark. The owner's mark lives in the top bar, so this line is the product alone. */
.brand-lock { display: flex; align-items: flex-start; min-width: 0; }

.brand {
    font-family: var(--display);
    font-size: 22px; font-weight: 900; letter-spacing: .2em; line-height: 1;
    text-transform: uppercase; display: block;
}
.brand span { color: var(--red); text-shadow: 0 0 18px var(--red-glow); }
.brand small {
    display: block; font-size: 9.5px; font-weight: 600; letter-spacing: .26em;
    color: var(--dimmer); margin-top: 9px; text-transform: uppercase;
}
.head-right { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; justify-content: flex-end; }

.logout button, #installBtn {
    background: rgba(255, 255, 255, .02);
    border: 1px solid var(--line); color: var(--dim);
    padding: 7px 11px; border-radius: 6px; cursor: pointer;
    font-size: 11.5px; letter-spacing: .08em; white-space: nowrap;
    transition: color .15s, border-color .15s, background .15s;
}
.logout button:hover, #installBtn:hover {
    color: var(--text); border-color: var(--line-hi); background: rgba(255, 255, 255, .045);
}
#installBtn { border-color: var(--red-soft); color: var(--red-2); }
#installBtn:hover { border-color: var(--red); background: rgba(242, 17, 44, .08); }
#installBtn[hidden] { display: none; }

/* live indicator — pulses only while capturing */
.live-dot {
    width: 8px; height: 8px; border-radius: 50%; background: #2e2e2e; flex: none;
    position: relative; transition: background .2s;
}
.live-dot::after {
    content: ''; position: absolute; inset: -3px; border-radius: 50%;
    border: 1px solid var(--red); opacity: 0; transform: scale(.6);
}
body.running .live-dot { background: var(--red); box-shadow: 0 0 10px var(--red-glow); }
body.running .live-dot::after { animation: ping 1.6s cubic-bezier(.2, .7, .4, 1) infinite; }
@keyframes ping {
    0%   { opacity: .85; transform: scale(.6); }
    70%  { opacity: 0;   transform: scale(2.1); }
    100% { opacity: 0;   transform: scale(2.1); }
}

/* ── source buttons ── */
.sources { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 16px; }
.src {
    position: relative; display: grid; grid-template-columns: auto 1fr; gap: 3px 12px;
    align-items: center; text-align: left; cursor: pointer; color: var(--text);
    padding: 15px 16px; border-radius: var(--radius);
    font-size: 14px; font-weight: 700; letter-spacing: .01em;
    border: 1px solid transparent;
    background:
        linear-gradient(var(--panel), var(--panel)) padding-box,
        var(--edge) border-box;
    transition: transform .16s ease, box-shadow .16s ease, background .16s;
}
.src .ico {
    grid-row: 1 / 3; width: 22px; height: 22px; fill: none;
    stroke: var(--dim); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
    transition: stroke .16s;
}
.src .ico .wave { stroke: var(--red); opacity: .85; }
.src-t { grid-column: 2; }
.src em {
    grid-column: 2; font-style: normal; font-size: 11.5px; font-weight: 400;
    color: var(--dim); line-height: 1.5;
}
.src:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 6px 22px rgba(0, 0, 0, .5), inset 0 0 0 1px var(--red-soft);
    background:
        linear-gradient(var(--panel-2), var(--panel-2)) padding-box,
        linear-gradient(150deg, var(--red-soft), #1a1a1a 60%) border-box;
}
.src:hover:not(:disabled) .ico { stroke: var(--text); }
.src:disabled { opacity: .34; cursor: not-allowed; }
.src.file { overflow: hidden; }
.src.file input { position: absolute; inset: 0; opacity: 0; cursor: pointer; font-size: 0; }

/* ── readout ── */
.readout { display: grid; grid-template-columns: 1.05fr 1fr; gap: 12px; margin: 12px 0; }

.card {
    position: relative; padding: 18px 20px 16px; border-radius: var(--radius);
    border: 1px solid transparent;
    background:
        linear-gradient(180deg, #101010, var(--panel) 55%) padding-box,
        var(--edge) border-box;
}
/* top hairline — reads as light catching the panel edge */
.card::before {
    content: ''; position: absolute; left: 16px; right: 16px; top: 0; height: 1px;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .1), transparent);
}
/* The deck labels every block as red uppercase set in brackets — [GIGs], [MIXSETs], [Release].
   Same device here, so a card header reads as part of the same family of documents. */
.card > .label {
    font-family: var(--display);
    font-size: 10px; letter-spacing: .24em; color: var(--red); font-weight: 700;
    display: flex; justify-content: space-between; align-items: center; text-transform: uppercase;
}
.card > .label > span:first-child::before { content: '[ '; opacity: .55; }
.card > .label > span:first-child::after  { content: ' ]'; opacity: .55; }
.card > .label > span:last-child { color: var(--dimmer); font-weight: 500; letter-spacing: .14em; }
.card .value {
    font-family: var(--display);
    font-size: clamp(46px, 12vw, 70px); line-height: 1; font-weight: 900; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums; margin: 10px 0 2px; color: var(--text);
}

/* beat pulse: a ring on the tempo card, opacity only */
#bpmCard::after {
    content: ''; position: absolute; inset: -1px; border-radius: var(--radius);
    border: 1px solid var(--red); opacity: 0; pointer-events: none;
    transition: opacity .22s ease;
}
body.beat #bpmCard::after { opacity: .55; transition: opacity 0s; }
#bpmValue { transition: text-shadow .18s ease; }
body.beat #bpmValue { text-shadow: 0 0 30px var(--red-glow); }

.meter { height: 3px; background: #1c1c1c; border-radius: 2px; overflow: hidden; margin-top: 14px; }
.meter i {
    display: block; height: 100%; width: 0; border-radius: 2px;
    background: linear-gradient(90deg, rgba(242, 17, 44, .55), var(--red));
    box-shadow: 0 0 8px var(--red-soft);
    transition: width .3s ease;
}
.meter-row {
    display: flex; justify-content: space-between; font-family: var(--display);
    font-size: 9.5px; color: var(--dimmer); font-weight: 500;
    margin-top: 7px; letter-spacing: .16em; text-transform: uppercase;
}

/* key card */
.key-main { display: flex; align-items: center; gap: 14px; }
.key-text { min-width: 0; flex: 1; }
#keyValue { font-size: clamp(38px, 9.5vw, 56px); }
.camelot {
    font-family: var(--display);
    display: inline-block; margin-top: 6px; padding: 4px 13px; border-radius: 5px;
    font-size: 20px; font-weight: 800; letter-spacing: .05em;
    background: hsl(var(--hue, 0) 45% 14%);
    color: hsl(var(--hue, 0) 70% 74%);
    border: 1px solid hsl(var(--hue, 0) 45% 30%);
    box-shadow: 0 0 16px hsl(var(--hue, 0) 60% 30% / .35);
}
.openkey {
    font-size: 10.5px; color: var(--dimmer); margin-top: 9px;
    letter-spacing: .1em; text-transform: uppercase;
}

/* Camelot wheel — active key lit, its three mixable neighbours dimly lit. */
.wheel { width: 104px; height: 104px; flex: none; }
.wheel .seg { transition: opacity .25s, fill .25s; }
.wheel .ring { fill: none; stroke: #1e1e1e; stroke-width: 1; }
.wheel .hub {
    font-family: var(--display);
    font-size: 13px; font-weight: 800; fill: var(--dim); text-anchor: middle;
    dominant-baseline: central;
}
@media (max-width: 420px) { .wheel { width: 84px; height: 84px; } }

#keyCands { list-style: none; margin: 13px 0 0; padding: 0; font-size: 12.5px; }
#keyCands li {
    display: flex; align-items: center; gap: 8px; padding: 4px 0; color: var(--dim);
    border-top: 1px solid #171717;
}
#keyCands li:first-child { border-top: 0; }
#keyCands li.top { color: var(--text); }
#keyCands b { min-width: 42px; font-weight: 700; }
#keyCands .cam {
    font-size: 10.5px; padding: 1.5px 7px; border-radius: 4px;
    background: hsl(var(--hue, 0) 42% 13%); color: hsl(var(--hue, 0) 62% 70%);
}
#keyCands .sc { margin-left: auto; font-variant-numeric: tabular-nums; opacity: .5; }

.row { display: flex; align-items: center; gap: 6px; margin-top: 13px; flex-wrap: wrap; }
.chip, .mini {
    background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
    border-radius: 5px; padding: 5px 10px; font-size: 11.5px; cursor: pointer;
    font-variant-numeric: tabular-nums; transition: border-color .15s, color .15s;
}
.chip:hover, .mini:hover { border-color: var(--red-soft); color: var(--red-2); }
.row .hint { font-size: 9.5px; color: var(--dimmer); letter-spacing: .16em; text-transform: uppercase; }

/* ── canvases ── */
.scopes { display: grid; grid-template-columns: 1.6fr 1fr; gap: 12px; }
.scope-box {
    padding: 12px 14px 10px; border-radius: var(--radius); border: 1px solid transparent;
    background: linear-gradient(var(--panel), var(--panel)) padding-box, var(--edge) border-box;
}
.scope-box h3 {
    font-family: var(--display);
    margin: 0 0 9px; font-size: 10px; letter-spacing: .24em; color: var(--red);
    font-weight: 700; text-transform: uppercase;
}
.scope-box h3::before { content: '[ '; opacity: .55; }
.scope-box h3::after  { content: ' ]'; opacity: .55; }
canvas { display: block; width: 100%; height: 82px; }

/* ── controls ── */
.controls {
    display: flex; flex-wrap: wrap; gap: 7px; align-items: center;
    padding: 13px 15px; margin-top: 12px; border-radius: var(--radius);
    border: 1px solid transparent;
    background: linear-gradient(var(--panel), var(--panel)) padding-box, var(--edge) border-box;
}
.controls label { font-size: 9.5px; color: var(--dimmer); letter-spacing: .16em; text-transform: uppercase; }
select, .btn {
    background: var(--panel-2); color: var(--text); border: 1px solid var(--line);
    border-radius: 6px; padding: 9px 12px; font-size: 12.5px; cursor: pointer;
    transition: border-color .15s, color .15s, background .15s;
}
select:hover, .btn:hover:not(:disabled) { border-color: var(--line-hi); }
.btn:focus-visible, select:focus-visible, .src:focus-visible {
    outline: 2px solid var(--red); outline-offset: 2px;
}
.btn:disabled { opacity: .32; cursor: not-allowed; }
.btn[aria-pressed="true"] {
    border-color: var(--red); color: var(--red-2); background: rgba(242, 17, 44, .08);
    box-shadow: 0 0 12px rgba(242, 17, 44, .18);
}
.btn.danger { border-color: #331a1d; color: #c98d94; }
.btn.danger:hover:not(:disabled) { border-color: var(--red); color: var(--red-2); }
.spacer { flex: 1; min-width: 0; }
#tapValue {
    font-family: var(--display);
    font-variant-numeric: tabular-nums; color: var(--text); font-weight: 800;
    min-width: 46px; display: inline-block; letter-spacing: -.01em;
}
#elapsed { font-variant-numeric: tabular-nums; color: var(--dimmer); font-size: 10.5px; letter-spacing: .1em; }

/* ── status / progress ── */
.status {
    margin: 13px 0 0; padding: 12px 14px; border-radius: var(--radius); font-size: 12.5px;
    border: 1px solid var(--line); border-left: 2px solid var(--line);
    color: var(--dim); min-height: 42px; line-height: 1.65; background: var(--panel);
}
.status.ok { border-left-color: var(--ok); color: #93c9ac; }
.status.warn { border-left-color: var(--warn); color: #d8ac6b; }
.status.err { border-left-color: var(--red); color: #e08a94; }

#progress { height: 3px; background: #1c1c1c; border-radius: 2px; overflow: hidden; margin-top: 10px; }
#progress i {
    display: block; height: 100%; width: 0;
    background: linear-gradient(90deg, var(--red), var(--red-2));
    box-shadow: 0 0 10px var(--red-soft);
    transition: width .12s linear;
}

/* ── language picker ──
 * Was a bare <select> reading "한국어", which gives an English speaker nothing to aim at on a
 * page full of Korean. Now it is a labelled control with a globe: recognisable before you can
 * read any of the words around it. (Detection already picks the browser's language; this is for
 * the case where it guessed wrong or the visitor wants a different one.) */
.lang-pick {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 5px 9px 5px 10px; border-radius: 6px;
    border: 1px solid var(--red-soft); background: rgba(242, 17, 44, .05);
    cursor: pointer; transition: border-color .15s, background .15s;
}
.lang-pick:hover { border-color: var(--red); background: rgba(242, 17, 44, .1); }
.lang-pick svg {
    width: 15px; height: 15px; flex: none; fill: none;
    stroke: var(--red-2); stroke-width: 1.5;
}
.lang-cap {
    font-size: 8.5px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--red-2); font-weight: 700;
}
.lang-pick select {
    border: 0; padding: 2px 4px; font-size: 12.5px; cursor: pointer;
    /* The open dropdown is drawn by the browser, not by this stylesheet. `background: none` left
       it falling back to the default light popup, so the option list came out near-white with
       #EAEAEA text on it — invisible. A real background plus an explicit color-scheme is what
       makes the popup itself dark. */
    color-scheme: dark;
    background-color: #141414;
    color: var(--text);
    border-radius: 4px;
}
.lang-pick select:hover { border: 0; }
.lang-pick select:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }

/* Native dropdown popups. Chromium on Windows honours option background/colour, so these are set
   explicitly rather than trusting the inherited scheme; `color-scheme: dark` covers the popup
   chrome (scrollbar, borders) that CSS cannot reach. */
select { color-scheme: dark; }
select option {
    background-color: #141414;
    color: var(--text);
}
select option:checked,
select option:hover {
    background-color: #24151a;
    color: #fff;
}
select option:disabled { color: var(--dimmer); }

/* ── owner-only mode switch ── */
.modeform { display: inline-flex; }
.modebtn {
    font-family: var(--display);
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 12px; border-radius: 6px; cursor: pointer;
    font-size: 10px; font-weight: 800; letter-spacing: .18em;
    background: rgba(255, 255, 255, .02); border: 1px solid var(--line); color: var(--dim);
    transition: border-color .15s, color .15s, background .15s;
}
.modebtn .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.modebtn.off { color: var(--dim); }
.modebtn.off:hover { border-color: var(--line-hi); color: var(--text); }
.modebtn.on {
    color: #6ed49a; border-color: #23492f; background: rgba(93, 187, 131, .08);
}
.modebtn.on:hover { border-color: #5DBB83; }

/* ── funnel strip ──
 * Three quiet links, not a banner. Anyone analysing a promo here is in the exact population that
 * sends demos to a trance label, so this sits where the answer lands rather than in the footer. */
.funnel {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 18px;
}
.fn-item {
    display: block; padding: 13px 15px; border-radius: var(--radius);
    text-decoration: none; border: 1px solid transparent;
    background: linear-gradient(var(--panel), var(--panel)) padding-box, var(--edge) border-box;
    transition: transform .16s ease, box-shadow .16s ease, background .16s;
}
.fn-item:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
    background: linear-gradient(var(--panel-2), var(--panel-2)) padding-box,
                linear-gradient(150deg, var(--red-soft), #1a1a1a 60%) border-box;
}
.fn-k {
    display: block; font-size: 9px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--dimmer); font-weight: 700; margin-bottom: 6px;
}
.fn-v { display: block; font-size: 12.5px; color: var(--dim); line-height: 1.45; }
.fn-item:hover .fn-v { color: var(--text); }
.fn-item:hover .fn-k { color: var(--red-2); }

/* ── batch analysis ── */
.batch {
    margin-top: 12px; padding: 14px 16px 12px; border-radius: var(--radius);
    border: 1px solid transparent;
    background: linear-gradient(var(--panel), var(--panel)) padding-box, var(--edge) border-box;
}
.batch[hidden] { display: none; }
.batch-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 10px; }
.batch-head h3 {
    font-family: var(--display);
    margin: 0; font-size: 10px; letter-spacing: .24em; color: var(--red);
    font-weight: 700; text-transform: uppercase;
}
.batch-head h3::before { content: '[ '; opacity: .55; }
.batch-head h3::after  { content: ' ]'; opacity: .55; }
.batch-count { font-size: 11px; color: var(--dim); font-variant-numeric: tabular-nums; }
/* The table is the one thing here that can genuinely be wider than a phone, so it scrolls inside
   its own box rather than pushing the page sideways. */
.batch-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.batch-table { width: 100%; border-collapse: collapse; font-size: 12.5px; min-width: 460px; }
.batch-table th {
    text-align: left; font-size: 9px; letter-spacing: .18em; text-transform: uppercase;
    color: var(--dimmer); font-weight: 700; padding: 6px 10px 6px 0; border-bottom: 1px solid var(--line);
}
.batch-table td {
    padding: 7px 10px 7px 0; border-bottom: 1px solid #161616; color: var(--text);
    font-variant-numeric: tabular-nums;
}
.batch-table td.fname {
    color: var(--dim); max-width: 320px; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; font-variant-numeric: normal;
}
.batch-table td.cam {
    color: hsl(var(--hue, 0) 62% 70%); font-weight: 700;
}
.batch-table tr.failed td { color: var(--dimmer); }
.batch-note { font-size: 11px; color: var(--dimmer); line-height: 1.7; margin: 12px 0 8px; }
.batch-path { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.batch-path span {
    font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--dimmer);
}
.batch-path input {
    flex: 1 1 240px; min-width: 0; background: #141414; border: 1px solid var(--line);
    color: var(--text); border-radius: 6px; padding: 8px 11px; font-size: 12.5px;
}
.batch-path input:focus { outline: none; border-color: var(--red-soft); }

/* ── support strip ── */
.support {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    flex-wrap: wrap; margin-top: 12px; padding: 12px 16px; border-radius: var(--radius);
    border: 1px solid transparent;
    background: linear-gradient(var(--panel), var(--panel)) padding-box, var(--edge) border-box;
}
.sup-donate {
    display: inline-flex; align-items: center; gap: 9px; text-decoration: none;
    color: var(--red-2); font-size: 12.5px; font-weight: 600;
    padding: 7px 13px; border-radius: 999px;
    border: 1px solid var(--red-soft); background: rgba(242, 17, 44, .06);
    transition: border-color .15s, background .15s;
}
.sup-donate:hover { border-color: var(--red); background: rgba(242, 17, 44, .12); }
.sup-donate svg { width: 15px; height: 15px; fill: currentColor; stroke: none; flex: none; }
.sup-sponsor { display: inline-flex; align-items: center; gap: 9px; font-size: 12px; color: var(--dim); }
.sup-k {
    font-size: 8.5px; letter-spacing: .2em; text-transform: uppercase; color: var(--dimmer);
    font-weight: 700; padding: 2px 7px; border: 1px solid var(--line); border-radius: 4px;
}
.sup-sponsor a { color: var(--dim); text-decoration: none; border-bottom: 1px solid var(--line); }
.sup-sponsor a:hover { color: var(--red-2); border-bottom-color: var(--red-soft); }

/* ── owner panel ── */
.ownerpanel {
    margin-top: 12px; padding: 2px 18px; border-radius: var(--radius);
    border: 1px solid #2a1d20;
    background: linear-gradient(var(--panel), var(--panel)) padding-box;
}
.ownerpanel summary {
    cursor: pointer; padding: 12px 0; font-size: 10px; font-weight: 700; color: var(--dimmer);
    letter-spacing: .18em; text-transform: uppercase; list-style: none;
}
.ownerpanel summary::-webkit-details-marker { display: none; }
.ownerpanel summary::before { content: '⚙ '; color: var(--red-soft); }
.ownerpanel summary:hover { color: var(--dim); }
.ownerpanel form { display: grid; gap: 12px; padding: 4px 0 16px; max-width: 520px; }
.ownerpanel label { display: grid; gap: 5px; font-size: 12px; color: var(--dim); }
.ownerpanel label small { color: var(--dimmer); font-size: 10.5px; line-height: 1.5; }
.ownerpanel input {
    background: #141414; border: 1px solid var(--line); color: var(--text);
    border-radius: 6px; padding: 9px 11px; font-size: 12.5px;
}
.ownerpanel input:focus { outline: none; border-color: var(--red-soft); }
.ownerpanel button { justify-self: start; }
.saved-note { font-size: 12px; color: var(--ok); margin: 0 0 4px; }

/* ── guide ── */
.guide {
    margin-top: 22px; padding: 2px 20px; border-radius: var(--radius);
    border: 1px solid transparent;
    background: linear-gradient(var(--panel), var(--panel)) padding-box, var(--edge) border-box;
}
.guide summary {
    cursor: pointer; padding: 14px 0; font-size: 11px; font-weight: 700; color: var(--dim);
    letter-spacing: .18em; text-transform: uppercase; list-style: none;
}
.guide summary::-webkit-details-marker { display: none; }
.guide summary::before { content: '+ '; color: var(--red); font-weight: 800; }
.guide[open] summary::before { content: '− '; }
.guide summary:hover { color: var(--text); }
.guide h4 {
    margin: 16px 0 6px; font-size: 11px; color: var(--red-2);
    letter-spacing: .16em; text-transform: uppercase;
}
.guide p, .guide li { font-size: 12.5px; color: var(--dim); line-height: 1.8; }
.guide ol, .guide ul { padding-left: 18px; margin: 4px 0; }
.guide li::marker { color: var(--red-soft); }
.guide code {
    background: #080808; border: 1px solid var(--line); border-radius: 4px;
    padding: 1px 5px; font-size: 11.5px; color: var(--text);
}

body.dragging { outline: 2px dashed var(--red); outline-offset: -10px; }
body.running .src[data-live] { opacity: .5; }

/* ── footer ── */
footer {
    margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--line);
    display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center;
    justify-content: space-between; font-size: 10.5px; color: var(--dimmer); line-height: 1.9;
}
footer .owner { letter-spacing: .05em; }
footer .owner strong { color: var(--dim); font-weight: 700; }

/* The ownership marks live in the top bar now; the footer keeps the written credit only. */

footer a {
    color: var(--dim); text-decoration: none; letter-spacing: .06em;
    border-bottom: 1px solid transparent; transition: color .15s, border-color .15s;
}
footer a:hover { color: var(--red-2); border-bottom-color: var(--red-soft); }
footer .rights { width: 100%; color: #4a4a4a; font-size: 10px; }

/* ════════════════ mobile ════════════════ */
@media (max-width: 860px) {
    .sources { grid-template-columns: 1fr 1fr; }
    .src.file { grid-column: 1 / -1; }
    .readout, .scopes { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
    .wrap { padding: 0 14px; }
    .topbar .wrap { padding-top: calc(10px + env(safe-area-inset-top)); padding-bottom: 10px; gap: 10px; }
    .topbar-logo.cp img  { width: 104px; }
    .topbar-logo.pif img { width: 158px; }
    header { padding: 18px 0 14px; margin-bottom: 16px; flex-wrap: wrap; }
    .brand { font-size: 19px; }
    .head-right { width: 100%; justify-content: flex-start; }
    .sources { grid-template-columns: 1fr; gap: 8px; }
    .src.file { grid-column: auto; }
    .src { padding: 13px 14px; }

    /* Controls become a grid of full-height touch targets rather than a cramped wrap. */
    .funnel { grid-template-columns: 1fr; gap: 8px; }
    .support { flex-direction: column; align-items: stretch; }
    .sup-donate { justify-content: center; }
    .batch-table td.fname { max-width: 180px; }
    .lang-pick { padding: 6px 9px; }
    .lang-cap { display: none; }        /* the globe carries it once space is tight */
    .lang-pick select { font-size: 16px; }
    .modebtn { min-height: 40px; }
    .controls { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; padding: 12px; }
    .controls label, .controls select { grid-column: 1 / -1; }
    .controls .btn { width: 100%; padding: 12px 10px; font-size: 13px; }
    .spacer { display: none; }
    #tapValue { grid-column: 1 / -1; text-align: center; padding: 4px 0; font-size: 17px; }
    /* 16px keeps iOS from zooming the viewport when a control takes focus. */
    select { font-size: 16px; }

    /* Touch targets. Measured at 390px: the header controls came out 30–32px tall and the
       ÷2/×2/candidate chips 24px — all well under a fingertip. Height is raised rather than
       font size so the layout does not reflow. */
    .logout button, #installBtn {
        min-height: 40px; padding: 10px 13px; font-size: 12.5px;
    }
    .chip, .mini { min-height: 38px; padding: 8px 14px; font-size: 13px; }
    .row { gap: 8px; }
    .card { padding: 16px 16px 14px; }
    canvas { height: 68px; }
    footer { flex-direction: column; align-items: flex-start; gap: 4px; }
}

/* Installed PWA: the OS draws its own chrome, so reclaim the space. */
@media (display-mode: standalone) {
    header { padding-top: 14px; }   /* the top bar already absorbs the safe-area inset */
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
    .src:hover:not(:disabled) { transform: none; }
}
