/* self-hosted latin faces (fontsource); Chinese rides native Song serifs (Songti SC / SimSun) */
@font-face { font-family: "Fraunces"; font-style: normal; font-weight: 500; font-display: swap; src: url("./assets/fonts/fraunces-latin-500-normal.woff2") format("woff2"); }
@font-face { font-family: "Fraunces"; font-style: normal; font-weight: 600; font-display: swap; src: url("./assets/fonts/fraunces-latin-600-normal.woff2") format("woff2"); }
@font-face { font-family: "Fraunces"; font-style: italic; font-weight: 600; font-display: swap; src: url("./assets/fonts/fraunces-latin-600-italic.woff2") format("woff2"); }
@font-face { font-family: "Space Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("./assets/fonts/space-mono-latin-400-normal.woff2") format("woff2"); }
@font-face { font-family: "Space Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url("./assets/fonts/space-mono-latin-700-normal.woff2") format("woff2"); }

:root {
  --paper: #f8f4ea;   /* paper white — Andersen's cuts are white paper on colored mounts */
  --ink:   #1b2a9e;   /* deep cobalt (UI / text) */
  --night: #1f31cc;   /* vivid cobalt ground (from reference) */
  /* .6 measured at 3.62:1 on --paper for every theme below — WCAG AA needs
     4.5:1 for text this small. .8 clears it everywhere, worst case (ochre)
     at 4.81:1. */
  --soft:  rgba(27,42,158,.8);
  --accent: var(--ink);
}

/* ---------- channel themes (Andersen-papercut mount colors) ---------- */
[data-theme="blue"]  { --ink: #1b2a9e; --night: #1f31cc; --soft: rgba(27,42,158,.8); }
[data-theme="black"] { --ink: #211d18; --night: #161616; --soft: rgba(33,29,24,.8); }
[data-theme="rust"]  { --ink: #7a3018; --night: #af4a26; --soft: rgba(122,48,24,.8); }
[data-theme="green"] { --ink: #1c4531; --night: #276b46; --soft: rgba(28,69,49,.8); }
[data-theme="plum"]  { --ink: #44214d; --night: #5b2d66; --soft: rgba(68,33,77,.8); }
[data-theme="ochre"] { --ink: #64450a; --night: #8a5f0e; --soft: rgba(100,69,10,.8); }
[data-theme="crimson"] { --ink: #701521; --night: #9c1f2d; --soft: rgba(112,21,33,.8); }

* { box-sizing: border-box; }

/* Nothing interactive in this app carried any focus indicator — several rules
   below actively strip the browser default (outline: none) with nothing put back,
   so a keyboard user has no way to tell where they are at all. One ring, applied
   to buttons and selects: paper first, then ink, so it reads on the paper
   background and on the ink-filled play button alike, and on any of the seven
   mount colors — it never has to match a specific one. :focus-visible keeps it
   out of a mouse click, matching how hover already carries the pointer's own
   feedback here. */
:where(button, select, [tabindex]):focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--ink);
}
/* A text field already has a blinking caret, and most already carry a dotted
   underline of their own — a ring on top of that is one indicator too many for
   something you are actively typing into. A solid, ink-colored underline says
   the same "you are here" without adding a new shape to the field. Anything
   with its own more specific focus treatment already (.track-name, .track-tag)
   keeps that instead — this is only a fallback for plain fields that had none. */
input:focus-visible {
  outline: none;
  border-bottom-style: solid;
  border-bottom-color: var(--ink);
}

body {
  margin: 0; min-height: 100vh; overflow: hidden;
  display: grid; place-items: center;
  font-family: "Space Mono", "Songti SC", "STSong", "SimSun", ui-monospace, monospace;
  color: var(--ink);
  background: #d7d3c8;   /* neutral desk — no color; transparent later in Tauri */
  -webkit-font-smoothing: antialiased;
}
html.in-tauri body { background: transparent; }
/* .win is position:fixed — it never participated in body's grid alignment
   in the first place, its own top/left is what actually places it. In the
   desktop shell the window is sized around the card, not a big browser
   viewport, so pin it near the physical top instead of the ~60px offset
   tuned for a spacious page (that offset is also where the real, native
   draggable region lives — leaving it there stranded the card's own
   titlebar in a spot the OS doesn't treat as draggable) */
html.in-tauri #winMain { top: 4px; }
/* paper-cut / print grain */
body::after {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 9999;
  opacity: .06; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- floating papercut windows, each draggable ---------- */
.win { position: fixed; z-index: 10; }
.win[hidden] { display: none; }
#winMain    { left: calc(50% - 150px); top: 60px; }
#winStation { left: calc(50% + 158px); top: 76px; }
#winLook    { left: calc(50% + 178px); top: 320px; }
#winShare   { left: calc(50% + 178px); top: 76px; }
#winAbout   { left: calc(50% + 158px); top: 76px; }
#sbfm.collapsed .win { display: none; }
#sbfm:not(.collapsed) .perch { display: none; }

/* ---------- collapsed: black chip + white bird ---------- */
.perch {
  position: fixed; left: calc(50% - 138px); top: 60px; z-index: 10;
  border: none; background: transparent; cursor: grab; padding: 0;
}
.perch:active { cursor: grabbing; }
.chip {
  display: grid; place-items: center; width: 78px; height: 78px;
  background: var(--night); border: 2.5px solid var(--night); border-radius: 16px;
}
.chip .bird { width: 62px; height: 54px; }

/* ---------- papercut cards: the radio + the two panels ----------
   The window IS a cut sheet: scalloped silhouette + punched holes via
   border-image; inner UI de-boxed — stitched dotted rules, bare glyph
   buttons, punched-hole play. */
.radio, .panel {
  position: relative;
  width: 300px;
  background: none;
  border: 20px solid transparent;
  border-radius: 0;
  overflow: visible;
  user-select: none;
  border-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cdefs%3E%3Cmask id='m'%3E%3Crect width='120' height='120' fill='black'/%3E%3Crect x='12' y='12' width='96' height='96' fill='white'/%3E%3Ccircle cx='15' cy='15' r='11' fill='white'/%3E%3Ccircle cx='105' cy='15' r='11' fill='white'/%3E%3Ccircle cx='15' cy='105' r='11' fill='white'/%3E%3Ccircle cx='105' cy='105' r='11' fill='white'/%3E%3Ccircle cx='35' cy='12.5' r='8.5' fill='white'/%3E%3Ccircle cx='55' cy='14' r='9.5' fill='white'/%3E%3Ccircle cx='75' cy='12.5' r='8' fill='white'/%3E%3Ccircle cx='35' cy='107.5' r='8.5' fill='white'/%3E%3Ccircle cx='55' cy='106' r='9.5' fill='white'/%3E%3Ccircle cx='75' cy='107.5' r='8' fill='white'/%3E%3Ccircle cx='12.5' cy='35' r='8.5' fill='white'/%3E%3Ccircle cx='14' cy='55' r='9.5' fill='white'/%3E%3Ccircle cx='12.5' cy='75' r='8' fill='white'/%3E%3Ccircle cx='107.5' cy='35' r='8.5' fill='white'/%3E%3Ccircle cx='106' cy='55' r='9.5' fill='white'/%3E%3Ccircle cx='107.5' cy='75' r='8' fill='white'/%3E%3Ccircle cx='40' cy='24' r='2' fill='black'/%3E%3Ccircle cx='60' cy='24' r='2.2' fill='black'/%3E%3Ccircle cx='80' cy='24' r='2' fill='black'/%3E%3Ccircle cx='40' cy='96' r='2' fill='black'/%3E%3Ccircle cx='60' cy='96' r='2.2' fill='black'/%3E%3Ccircle cx='80' cy='96' r='2' fill='black'/%3E%3Ccircle cx='24' cy='40' r='2' fill='black'/%3E%3Ccircle cx='24' cy='60' r='2.2' fill='black'/%3E%3Ccircle cx='24' cy='80' r='2' fill='black'/%3E%3Ccircle cx='96' cy='40' r='2' fill='black'/%3E%3Ccircle cx='96' cy='60' r='2.2' fill='black'/%3E%3Ccircle cx='96' cy='80' r='2' fill='black'/%3E%3C/mask%3E%3C/defs%3E%3Crect width='120' height='120' fill='%23f8f4ea' mask='url(%23m)'/%3E%3C/svg%3E") 30 fill / 20px round;
}
.panel .brand { font-size: 13px; }

/* woodcut striped title bar */
.titlebar {
  position: relative; height: 24px; cursor: grab;
  display: flex; align-items: center; justify-content: center;
  border-bottom: 2px dotted var(--soft);
  background: none;
}
.deco { color: var(--accent); font-size: 9px; font-style: normal; vertical-align: 1px; }
.titlebar:active { cursor: grabbing; }
.brand {
  background: none; border: none; padding: 0 10px; z-index: 1;
  font-family: "Fraunces", "Songti SC", "STSong", "SimSun", Georgia, serif; font-style: italic; font-weight: 600;
  font-size: 15px; color: var(--ink);
}
/* .brand also labels four plain, non-interactive panel titles (Station, Look,
   Share, and this panel's own header) — only #aboutBtn is a real <button>, so
   the clickable affordance is scoped to that one element specifically, not
   the shared class, or every other panel title would falsely look pressable
   too. No icon, no underline sitting there at rest — just a little give on
   hover, the same quiet register as the other title-bar buttons dimming. */
#aboutBtn { cursor: pointer; }
#aboutBtn:hover { opacity: .7; }
.brand em {
  font-style: normal; font-family: "Space Mono", monospace; font-size: 9px;
  text-transform: uppercase; letter-spacing: 1px;
  background: var(--accent); color: var(--paper); padding: 1px 4px; border-radius: 2px; margin-left: 2px; vertical-align: 1.5px;
}
.back-hint { display: none; }   /* shown only on narrow viewports — see the mobile media query */
/* Nothing anywhere styled :disabled before this — a button mid-upload or in
   its brief post-save cooldown looked pixel-identical to a live one, full
   opacity and a pointer cursor, for however long that upload actually takes
   over a real network. One rule, every button, so "busy" reads as busy
   instead of looking like the click just did not register. */
button:disabled { opacity: .45; cursor: not-allowed; }
.wb {
  width: 18px; height: 18px; border: none; border-radius: 3px;
  background: none; display: grid; place-items: center;
  font-family: "Space Mono", monospace; font-size: 13px; line-height: 1; color: var(--ink); cursor: pointer; padding: 0;
}
.wb.close { position: absolute; right: 7px; z-index: 1; }
.wb.min { position: absolute; right: 7px; z-index: 1; padding-bottom: 3px; }
/* the ❋ was simply too small to notice at 9px — it keeps the cut-paper feel a
   filled swatch loses, so it just gets a little more presence */
.wb.look { position: absolute; left: 7px; z-index: 1; font-size: 12px; }
.wb.lang { position: absolute; left: 29px; z-index: 1; font-size: 8px; width: auto; min-width: 15px; padding: 0 2px; }
.wb.min:hover, .wb.close:hover, .wb.look:hover, .wb.lang:hover { background: none; color: var(--ink); opacity: .55; }
.hd { color: var(--ink); font-size: 8px; font-style: normal; vertical-align: 2px; opacity: .85; }

/* ---------- the black screen ---------- */
.screen {
  position: relative; margin: 8px 0; height: 128px;
  background: var(--night); border: none; border-radius: 3px;
  display: grid; place-items: center; overflow: hidden;
}
.screen::after {
  content: ""; position: absolute; inset: 14px; border-radius: 4px;
  border: 2px dotted var(--paper); opacity: .28; pointer-events: none;
}
/* papercut sawtooth frame, hand-cut by the same rough filter as the forest */
.screen::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  filter: url(#roughF);
  background:
    conic-gradient(from 135deg at 50% 0,    var(--paper) 90deg, #0000 0) top/18px 9px repeat-x,
    conic-gradient(from -45deg at 50% 100%, var(--paper) 90deg, #0000 0) bottom/18px 9px repeat-x,
    conic-gradient(from 45deg at 0 50%,     var(--paper) 90deg, #0000 0) left/9px 18px repeat-y,
    conic-gradient(from 225deg at 100% 50%, var(--paper) 90deg, #0000 0) right/9px 18px repeat-y;
}
/* papercut forest: firs + sparkles flanking the bird */
.forest { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }
.forest-cut { fill: var(--paper); }
.bird { overflow: visible; }
.screen .bird { width: 124px; height: 124px; }
.silh { fill: var(--paper); }
.silh2 { fill: var(--paper); }
.belly { fill: var(--ink); }
.eye-w { fill: var(--paper); stroke: var(--ink); stroke-width: 2.5; }
.eye-p { fill: var(--ink); }
.branch { fill: none; stroke: var(--paper); stroke-width: 4; stroke-linecap: round; }
.legs2 { fill: none; stroke: var(--paper); stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; }
.chirp { fill: none; stroke: var(--paper); stroke-width: 3; stroke-linecap: round; }
.cut { fill: var(--night); }
.scallop { fill: none; stroke: var(--ink); stroke-width: 2.5; stroke-linecap: round; }
.eye-g { fill: var(--paper); }
.lash { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linecap: round; }
.legs { fill: none; stroke: var(--paper); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; }
.hp-oline { fill: none; stroke: var(--ink); stroke-width: 13; stroke-linecap: round; }
.hp-ocup { fill: var(--ink); }
.hp-band { fill: none; stroke: var(--paper); stroke-width: 6; stroke-linecap: round; }
.hp-cup { fill: var(--paper); }
.bird-body { transform-origin: 100px 150px; }
.radio.playing .bird-body { animation: bob .8s ease-in-out infinite; }
@keyframes bob { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-5px)} }

/* dial strip (channel switch + readout) */
.dial {
  display: flex; align-items: center; gap: 6px; margin: 0 0 3px; padding: 3px 0;
  border: none; border-radius: 0; background: none;
  border-top: 2px dotted var(--soft); border-bottom: 2px dotted var(--soft);
}
.dial .ch { border: none; background: transparent; color: var(--ink); opacity: .5; font-size: 12px; cursor: pointer; padding: 2px 5px; }
.dial .ch:hover { opacity: 1; }
/* a single quiet nudge toward the arrow that actually leads to your own empty
   slot — three short beats, then gone for good until the next fresh visit.
   Not a standing hint: this project has already turned one of those down once. */
.tprev.tune-hint { animation: tuneHintLeft .55s ease-in-out 3; }
.tnext.tune-hint { animation: tuneHintRight .55s ease-in-out 3; }
@keyframes tuneHintLeft  { 0%, 100% { transform: translateX(0); opacity: .5; } 50% { transform: translateX(-5px); opacity: 1; } }
@keyframes tuneHintRight { 0%, 100% { transform: translateX(0); opacity: .5; } 50% { transform: translateX(5px);  opacity: 1; } }
.dial-mid {
  position: relative;
  flex: 1; min-width: 0; text-align: center; font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace; font-size: 11px;
  color: var(--ink); letter-spacing: .5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: none; border: none; cursor: pointer; padding: 3px 0; border-radius: 4px;
}
.dial-mid:hover { background: none; opacity: .7; }
.dial-mid b { font-weight: 700; }
.dial-mid .fm { font-size: 8px; opacity: .55; margin: 0 6px 0 3px; }
.dial-mid #sname { opacity: .78; }
.dial-mid.cta b, .dial-mid.cta .fm { display: none; }   /* pre-creation CTA hides ★ FM */
/* absolutely positioned, not inline after #sname — a long station name would
   ellipsis-truncate an inline dot right along with the text clipped past it */
.new-dot { position: absolute; top: 1px; right: 3px; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
.dial-mid.cta .new-dot { display: none; }

/* one-line station intro under the dial */
.tagline {
  text-align: center; font-size: 9.5px; letter-spacing: .5px; color: var(--soft);
  margin: 0 20px 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tagline[hidden] { display: none; }

.note { position: absolute; top: 40px; font-size: 15px; color: var(--paper); opacity: 0; pointer-events: none; }
.n1 { left: 34%; } .n2 { left: 58%; font-size: 18px; }
.radio.playing .n1 { animation: noteFloat 1.8s ease-in infinite; }
.radio.playing .n2 { animation: noteFloat 1.8s ease-in .7s infinite; }
@keyframes noteFloat { 0%{opacity:0;transform:translateY(6px)} 25%{opacity:.9} 100%{opacity:0;transform:translateY(-30px) rotate(14deg)} }
.screen { cursor: grab; }
.screen:active { cursor: grabbing; }
.screenhint {
  position: absolute; inset: 0; display: grid; place-items: center; z-index: 4;
  color: var(--paper); font-size: 11px; letter-spacing: .5px;
  background: rgba(18,22,52,.6); opacity: 0; pointer-events: none; transition: opacity .12s;
}
.screen.dragging .screenhint { opacity: 1; }
.screen.dragging { outline: 2px dashed var(--paper); outline-offset: -8px; }

/* ---------- readout ---------- */
.readout { display: flex; align-items: center; gap: 10px; padding: 2px 6px 8px; }
.meta { min-width: 0; flex: 1; }
.cover { width: 42px; height: 42px; flex: none; border: 2px solid var(--ink); border-radius: 4px; background-size: cover; background-position: center; display: none; }
.cover.show { display: block; }
.title { font-family: "Fraunces", "Songti SC", "STSong", "SimSun", Georgia, serif; font-weight: 600; font-size: 15px; line-height: 1.2; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sub { font-size: 10px; color: var(--soft); margin-top: 3px; text-transform: uppercase; letter-spacing: .4px; }
.sub b { color: var(--ink); font-weight: 700; }
/* Chinese characters have no case, so text-transform:uppercase is a no-op for
   them — for Latin text it flattens ascenders/descenders that normally help
   read a word by its shape, on top of already being the smallest text in the
   interface. That combination costs English more than it costs Chinese at
   the same pixel size, so the fix is scoped to English rather than raising
   size for everyone. */
:lang(en) .sub { font-size: 11px; letter-spacing: .5px; }

/* ---------- progress ---------- */
.progress { display: flex; align-items: center; gap: 8px; padding: 0 6px 10px; }
.time { font-family: "Space Mono", monospace; font-size: 9.5px; color: var(--soft); width: 26px; }
.time:last-child { text-align: right; }
.bar { flex: 1; height: 6px; border: none; border-radius: 4px; background: none; overflow: visible; cursor: pointer; position: relative; }
.bar::before { content: ""; position: absolute; left: 0; right: 0; top: 2px; border-top: 2px dotted var(--soft); }
.fill { position: relative; height: 4px; width: 0%; margin-top: 1px; border-radius: 2px; background: var(--ink); }

/* ---------- controls ---------- */
.controls { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 0 6px 8px; }
.btn { width: 34px; height: 34px; border: none; border-radius: 50%; background: none; color: var(--ink); display: grid; place-items: center; cursor: pointer; padding: 0; transition: transform .07s ease; }
.btn:active { transform: translate(1px,1px); }
.btn svg { width: 22px; height: 22px; fill: currentColor; }
.btn.play { width: 44px; height: 44px; background: var(--ink); color: var(--paper); outline: none; }
.btn.play svg { width: 20px; height: 20px; }
.i-pause { display: none; }
.radio.playing .i-play { display: none; }
.radio.playing .i-pause { display: block; }

.vol { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.vol-ico { width: 16px; height: 16px; flex: none; fill: var(--soft); }
#vol { -webkit-appearance: none; appearance: none; flex: 1; width: auto; height: 6px; border: 2px solid var(--ink); border-radius: 4px; cursor: pointer; background: linear-gradient(90deg, var(--ink) 0 72%, var(--paper) 72% 100%); }
#vol::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--paper); border: 2px solid var(--ink); }

/* ---------- panel bodies (station / look windows) ---------- */
.panel-body { padding: 6px 4px 8px; }
.chsetup-label { font-size: 9.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--soft); margin-bottom: 5px; }
:lang(en) .chsetup-label { font-size: 11px; letter-spacing: .6px; }
/* full-contrast ink, not --soft — this is the panel's main reading content, not a caption */
.about-body { font-size: 13px; line-height: 1.6; color: var(--ink); margin: 0 0 12px; }
.about-body:last-child { margin-bottom: 4px; }
.about-download a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
/* already the desktop shell — offering to download it again is noise, not a hint */
.in-tauri .about-download { display: none; }
.chsetup-input {
  width: 100%; box-sizing: border-box; font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace; font-size: 12px;
  padding: 5px 2px 7px; margin-bottom: 13px; border: none; border-bottom: 2px dotted var(--ink); border-radius: 0;
  background: none; color: var(--ink);
}
.chsetup-input:focus { outline: none; }
.swatches { display: flex; justify-content: space-between; margin-bottom: 14px; }
.swatch { width: 28px; height: 28px; border-radius: 50%; border: 2.5px solid var(--ink); background: var(--sw); cursor: pointer; padding: 0; position: relative; }
.swatch.active::after {
  content: "✓"; position: absolute; inset: 0; display: grid; place-items: center;
  color: #fff; font-size: 12px; text-shadow: 0 1px 2px rgba(0,0,0,.5);
}
.chsetup-upload {
  width: 100%; padding: 7px; margin-bottom: 12px;
  border: 2px dashed var(--ink); border-radius: 6px; background: transparent;
  color: var(--ink); font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace; font-size: 11px; cursor: pointer;
}
.chsetup-upload:hover { background: rgba(0,0,0,.05); }
.chsetup-upload.share { margin: 10px 0 0; }
/* a rare way back, not a thing to do — quieter than the buttons above it, and set
   apart so it does not read as another step in making a station */
.chsetup-upload.restore {
  margin-top: 12px; font-size: 10px; padding: 6px;
  border-color: rgba(0, 0, 0, .22); color: var(--soft);
}
.chsetup-upload.restore:hover { border-color: var(--ink); color: var(--ink); }
.chsetup-upload.danger {
  margin-top: 8px; font-size: 10px; padding: 6px;
  border-color: rgba(153,27,27,.5); color: rgba(153,27,27,.85);
}
.chsetup-upload.danger:hover { background: rgba(153,27,27,.08); border-color: #991b1b; color: #991b1b; }
.chsetup-upload[disabled] { opacity: .5; cursor: wait; }

/* two equal ways to add a program: a carefully-picked file, or a held-down moment */
.add-row { display: flex; gap: 8px; margin-bottom: 12px; }
.add-row .chsetup-upload { margin-bottom: 0; }
.record { touch-action: none; -webkit-user-select: none; user-select: none; }
.record .record-live { color: inherit; }
.record .record-time { font-variant-numeric: tabular-nums; }
.record.recording {
  background: var(--ink); color: var(--paper); border-style: solid;
  animation: recPulse 1s ease-in-out infinite;
}
@keyframes recPulse { 0%, 100% { opacity: 1; } 50% { opacity: .65; } }
.share-out { margin-top: 8px; font-size: 10px; line-height: 1.6; color: var(--soft); word-break: break-all; white-space: pre-wrap; }
.share-out[hidden] { display: none; }
.share-link-box { margin-top: 14px; }
.share-link-box[hidden] { display: none; }
.share-link-text {
  font-family: "Space Mono", monospace; font-size: 9.5px; line-height: 1.5; color: var(--ink);
  word-break: break-all; padding: 8px; margin-bottom: 8px;
  border: 1.5px dotted rgba(0,0,0,.3); border-radius: 6px; max-height: 72px; overflow-y: auto;
}

/* ---------- listen stamps: earned, opt-in, never counted ---------- */
/* the CTA is a postmark, not a button with a label — round, inked, stamped into
   the corner of the card itself once it's been earned */
.stamp-cta {
  position: absolute; right: 10px; bottom: 10px; z-index: 3;
  width: 46px; height: 46px; padding: 0; border-radius: 50%;
  border: 2px dashed var(--paper); background: var(--night);
  display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 2px 7px rgba(0,0,0,.28);
  transform: scale(0) rotate(-14deg); opacity: 0;
  transition: transform .38s cubic-bezier(.34,1.56,.64,1), opacity .25s ease;
}
.stamp-cta[hidden] { display: none; }
.stamp-cta.show { transform: scale(1) rotate(-8deg); opacity: 1; }
.stamp-cta[disabled] { cursor: default; }
.stamp-bird { width: 20px; height: 17px; overflow: visible; }
/* 200 viewBox units squeezed into 20px, so a 3.5-wide stroke lands at a third of a
   pixel and the legs simply vanish — thicken them to survive the reduction */
.stamp-cta .stamp-bird .legs2 { stroke-width: 8; }
.stamp-cta.stamping { animation: stampHit .3s ease; }
@keyframes stampHit {
  0%   { transform: scale(1) rotate(-8deg); }
  45%  { transform: scale(.8) rotate(4deg); }
  100% { transform: scale(1) rotate(-8deg); }
}

.stamps-box { margin-top: 14px; }
.stamps-box[hidden] { display: none; }
.stamps-grid { display: flex; flex-wrap: wrap; gap: 8px; }
/* one round ink strike, tilted a little because a hand held the stamp */
.stamp-chip { width: 72px; height: 72px; transform: rotate(-7deg); }
.stamp-chip:nth-child(even) { transform: rotate(5deg); }   /* struck one at a time, not printed in a grid */
.stamp-mark { width: 100%; height: 100%; overflow: visible; }
.stamp-ink { fill: var(--sw); }
.stamp-ring { fill: none; stroke: var(--paper); stroke-width: 1.6; stroke-dasharray: 3 2.6; opacity: .9; }
.stamp-arc-text, .stamp-time {
  fill: var(--paper); font-family: "Space Mono", monospace; font-weight: 700;
  font-size: 9px; letter-spacing: .2px;
}
/* the bird is drawn at its own 200x172 scale then shrunk, so every stroke has to be
   thickened by roughly the same factor or it disappears at this size */
.stamp-chip .stamp-bird .silh2 { fill: var(--paper); }
.stamp-chip .stamp-bird .cut { fill: var(--sw); }
.stamp-chip .stamp-bird .chirp { stroke: var(--paper); stroke-width: 7; fill: none; stroke-linecap: round; }
.stamp-chip .stamp-bird .legs2 { stroke: var(--paper); stroke-width: 7; fill: none; stroke-linecap: round; }

/* the "did it actually work" list — every track you've added to your own station */
.track-list { margin: -4px 0 12px; max-height: 132px; overflow-y: auto; }
.track-list[hidden] { display: none; }
.track-row {
  display: flex; align-items: center; gap: 8px; padding: 6px 2px;
  border-bottom: 1.5px dotted rgba(0,0,0,.18);
  font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace; font-size: 11px; color: var(--ink);
}
.track-row:first-child { border-top: 1.5px dotted rgba(0,0,0,.18); }
.track-name {
  flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font: inherit; color: inherit; background: none; padding: 0; margin: 0;
  border: none; border-bottom: 1.5px dotted transparent; cursor: text;
}
.track-name:hover, .track-name:focus { border-bottom-color: var(--soft); outline: none; }
.track-remove {
  flex: none; width: 18px; height: 18px; border: none; background: none; cursor: pointer;
  color: var(--soft); font-size: 13px; line-height: 1; padding: 0;
}
.track-remove:hover { color: var(--ink); }
.track-tag {
  flex: none; max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace; font-size: 10px; color: var(--soft);
  background: transparent; border: 1.5px dotted rgba(0,0,0,.3); border-radius: 10px;
  padding: 3px 15px 3px 7px; cursor: pointer; margin-right: 2px;
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--soft) 50%), linear-gradient(135deg, var(--soft) 50%, transparent 50%);
  background-position: right 8px center, right 4px center; background-size: 4px 4px, 4px 4px; background-repeat: no-repeat;
}
.track-tag:hover, .track-tag:focus { color: var(--ink); border-color: var(--ink); outline: none; }
.chsetup-save {
  width: 100%; padding: 8px; border: 2.5px solid var(--ink); border-radius: 7px;
  background: var(--ink); color: var(--paper); font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace;
  font-size: 12px; font-weight: 700; letter-spacing: .5px; cursor: pointer;
}
.chsetup-save:active { transform: translate(1px,1px); }

@media (prefers-reduced-motion: reduce) { .bird-body, .note, .tprev, .tnext { animation: none !important; } }

/* ---------- mobile: bigger tap targets, a stacking ritual instead of free-floating cards ---------- */
@media (max-width: 600px) {
  /* the visual size stays put (it's part of the papercut system) — just the
     actual tappable area grows, since 18px alone is well under a comfortable
     touch target on a phone */
  .wb::after, .track-remove::after { content: ""; position: absolute; inset: -13px; }
  .track-remove { position: relative; }
  /* #aboutBtn is 21px tall at its visible size — same problem, same fix.
     Vertical-only inset: it is already wide, so widening further would
     overlap the language toggle beside it. */
  #aboutBtn { position: relative; }
  #aboutBtn::after { content: ""; position: absolute; top: -13px; bottom: -13px; left: 0; right: 0; }

  /* A phone is ~700px tall and two stacked cards are already taller than that, so
     the stack HAS to be reachable by scrolling: absolute (not fixed) makes the cards
     part of the document, and letting the page scroll is what puts "完成" and the
     upload button back within reach. Without this the lower half of the stack simply
     cannot be touched — no scroll, and dragging is off on phones. */
  html, body { overflow-y: auto; overflow-x: hidden; height: auto; }
  body { display: block; min-height: 100vh; }
  .win { position: absolute; }

  /* The desktop defaults park these cards as far right as calc(50% + 178px), which
     on a phone puts their right edge past 600px. A card revealed at that position —
     before restackMobile has had a chance to move it — widens the viewport enough
     that the "am I on a phone" check answers no, and the whole stack silently falls
     back to the desktop cascade. Start them somewhere harmless instead. */
  #winMain, #winStation, #winLook, #winShare, #winAbout { left: 8px; top: 8px; }

  /* cards auto-stack (see restackMobile in main.js) instead of being dragged —
     this is what actually produces the slide-into-place feel: only `top` ever
     changes here, so a newly-revealed card sliding down from just above its
     real slot, or an existing card shifting to close a gap when something above
     it closes, both animate the same simple way */
  .win { transition: top .32s cubic-bezier(.22, 1, .36, 1); }

  /* share is only ever opened from inside the station panel — a small
     breadcrumb marks that relationship, without changing what the × does */
  .back-hint {
    display: block; position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
    max-width: 76px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    background: var(--paper); padding: 0 4px; z-index: 1;
    font-family: "Space Mono", "Songti SC", "STSong", "SimSun", monospace; font-size: 9px; color: var(--soft);
  }
}
