/* landing.css — content styling for landing2.php. Loaded via <link> in head via shell ctx. */
:root{--muted:rgba(255,255,255,.6);--gold:#e7c873}
/* pulled up to share the ‹ › history-nav row instead of sitting on its own
   line below it (operator 2026-07-09: "این قسمت سلام... بیاد بالا توی ردیف
   با جهت‌ها"). .ir2-histnav (ui.css) is a SEPARATE element (shell-level,
   sticky, outside #appMain) with pointer-events:none on its own box and
   background:none, so it doesn't visually block this text -- only the
   circular buttons themselves (pointer-events:auto) paint anything, and
   those sit at the opposite (start) edge from this RTL-right-aligned
   greeting, so they don't collide. The negative margin equals histnav's
   own rendered height (padding 10+4 + button 40 = 54px desktop, 8+2+38=48
   mobile) -- if that box ever resizes, this needs to move with it. */
.hwel{font-size:clamp(1.4rem,2.6vw,1.7rem);font-weight:800;margin:-54px 0 4px}
.hsub{color:var(--muted);font-size:.88rem;margin-bottom:22px}
@media (max-width: 767px) { .hwel { margin-top: -48px; } }   /* matches .ir2-histnav's smaller mobile box (ui.css) */
.lsect{margin-top:22px}
.lsect:first-of-type{margin-top:0}
.lsect .lh{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:13px}
.lsect .lh h2{font-size:1.02rem;font-weight:800}
.lsect .lh .more{color:var(--muted);font-size:.78rem}
.lsect .lh .more:hover{color:var(--gold)}

/* horizontal strip */
.strp{display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;padding:2px 0 6px}
.strp::-webkit-scrollbar{display:none}
.strp>*{scroll-snap-align:start;flex:0 0 auto}

/* click ‹ › arrows for every horizontally-scrollable row (operator 2026-07-09:
   "با این اسلایدی‌ها بره بهتره تا گرب" -- a mouse user doesn't discover
   click-and-drag as readily as a visible button; touch keeps native swipe,
   see the pointer:fine gate below). strip-nav.js wraps each .strp/.redisc
   in .strp-navwrap and injects two buttons with class="ir2-hn-btn" -- the
   EXACT class the ‹ › history-nav buttons use (ui.css) -- so these are
   pixel-identical to that reference by construction, not a re-implementation
   that could drift. Only the wrapper + position/visibility rules live here. */
.strp-navwrap{position:relative}
.strp-navwrap .ir2-hn-btn.strp-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:5;display:none;transition:opacity .15s}
/* PHYSICAL left/right, not inset-inline-start/end -- those are direction-
   AWARE and flip on this RTL page (inline-start = physical RIGHT here), which
   put each button on the wrong edge (operator 2026-07-10 caught it: the
   btn meant for the list's end landed at its start). Everything else about
   these buttons -- the class names, the click-scroll math -- already thinks
   in physical left/right, so the positioning needs to match, not the other
   way around. */
.strp-nav-left{left:-14px}
.strp-nav-right{right:-14px}
/* fine-pointer + hover = a real mouse/trackpad; touch devices never match this,
   regardless of viewport width, so phones/tablets keep plain native swipe.
   :not([hidden]) matters: this selector's specificity (4 classes) already
   beat the UA stylesheet's plain "[hidden]{display:none}" (an attribute
   selector), so on rows strip-nav.js correctly marked hidden (nothing to
   scroll) this display:flex was winning anyway and showing them fully lit
   up regardless -- only fixed itself once a scroll/click re-ran sync() for
   THAT row specifically (operator 2026-07-10 caught it: every row lit up
   active before the first click). Excluding [hidden] here means this rule
   simply doesn't apply to a hidden row, so the native hiding wins instead. */
@media (hover: hover) and (pointer: fine) {
  .strp-navwrap .ir2-hn-btn.strp-nav:not([hidden]){display:flex}
}

.ctile{display:block;width:132px;color:#fff}
.ctile .ccov{display:block;width:132px;height:132px;border-radius:11px;background:#1b1b24 center/cover no-repeat;box-shadow:0 8px 18px -8px rgba(0,0,0,.6);margin-bottom:7px;transition:transform .2s}
.ctile:hover .ccov{transform:translateY(-3px)}
.ctile .ctt{font-size:.84rem;font-weight:700;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ctile .cta{font-size:.7rem;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* history mosaic — 1 big + 4 small in 2x2 */
.redisc{display:grid;grid-template-columns:240px 1fr;gap:10px;align-items:start}
.smgrid{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:10px;height:240px}
.bigtile,.smtile{display:block;position:relative;overflow:hidden;background:#1b1b24 center/cover no-repeat;color:#fff;transition:transform .2s;box-shadow:0 10px 22px -10px rgba(0,0,0,.6)}
.bigtile{width:240px;height:240px;border-radius:15px}
.smtile{height:100%;border-radius:10px}
.bigtile:hover,.smtile:hover{transform:translateY(-3px)}
.bigtile::after,.smtile::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,0) 45%,rgba(0,0,0,.8) 100%)}
.bigtile .btxt,.smtile .stxt{position:absolute;inset-inline-start:0;inset-inline-end:0;bottom:11px;padding:0 13px;z-index:2}
.smtile .stxt{bottom:8px;padding:0 10px}
.bigtile .btt{font-size:1rem;font-weight:800;text-shadow:0 2px 10px rgba(0,0,0,.85);line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bigtile .bta{font-size:.78rem;opacity:.85;margin-top:2px;text-shadow:0 2px 8px rgba(0,0,0,.7);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.smtile .sttt{font-size:.74rem;font-weight:800;text-shadow:0 1px 6px rgba(0,0,0,.85);line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.smtile .stta{font-size:.64rem;opacity:.85;margin-top:1px;text-shadow:0 1px 5px rgba(0,0,0,.7);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media(max-width:900px){.redisc{grid-template-columns:1fr;gap:8px}.bigtile{width:100%;height:auto;aspect-ratio:1}.smgrid{height:auto;grid-template-rows:auto auto}.smtile{aspect-ratio:1}}

/* album + artist tiles */
.albtile{display:block;width:150px;color:#fff}
.albtile .alcov{display:block;width:150px;height:150px;border-radius:11px;background:#1b1b24 center/cover no-repeat;box-shadow:0 10px 20px -10px rgba(0,0,0,.6);margin-bottom:7px;transition:transform .2s}
.albtile:hover .alcov{transform:translateY(-3px)}
.albtile .alt{font-size:.84rem;font-weight:700;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.albtile .als{font-size:.7rem;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

.artile{width:106px;text-align:center;color:#fff}
.artile .acirc{width:106px;height:106px;border-radius:50%;background:#1b1b24 center/cover no-repeat;box-shadow:0 8px 18px -8px rgba(0,0,0,.5);margin:0 auto 7px;display:flex;align-items:center;justify-content:center;font-size:2.2rem;font-weight:800;color:rgba(255,255,255,.85);transition:transform .2s}
.artile:hover .acirc{transform:scale(1.05)}
.artile .att{font-size:.8rem;font-weight:700;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 4px}

/* featured podcast card — flat row, no boxed card / glow button (operator
   2026-07-06, «مینیمال‌کردن»): cover + text + a plain ▶ glyph, like an eprow. */
.ep-feat{display:flex;align-items:center;gap:14px;padding:8px 2px;color:inherit;transition:opacity .15s;text-decoration:none}
.ep-feat:hover{opacity:.72}
.ep-feat .ef-cov{display:block;width:64px;height:64px;border-radius:10px;background:#1b1b24 center/cover no-repeat;flex-shrink:0}
.ep-feat .ef-meta{flex:1;min-width:0}
.ep-feat .ef-k{color:var(--gold);font-size:.7rem;font-weight:800;letter-spacing:.04em;margin-bottom:4px}
.ep-feat .ef-t{font-size:1rem;font-weight:800;line-height:1.3;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px}
.ep-feat .ef-s{font-size:.78rem;color:#cdd0db}
.ep-feat .ef-p{flex-shrink:0;font-size:1.3rem;line-height:1;color:var(--gold);opacity:.7}

/* فالِ ترانه promo card — same row shape as the podcast card above (icon +
   kicker/title/description), just linking to /fal instead of playing
   anything, so no trailing ▶ glyph (operator 2026-07-07: "با فضای مشابه
   برای آیکون و عنوان و توضیح"). Colors use the theme variables directly
   (var(--gold)/var(--muted), inherited ink for the title) instead of the
   old hardcoded dark-mode values .ep-feat above still carries, so this
   needs no separate paper.css override to read correctly on the paper skin.
   ff-ic started as a plain 🔮 emoji, swapped 2026-07-07 for the operator's
   own "فال ترانه" bubble-letter logo (matches the Boofi Classic wordmark
   below it) once /img/fal.png landed. */
.fal-feat{display:flex;align-items:center;gap:14px;padding:8px 2px;color:inherit;transition:opacity .15s;text-decoration:none}
.fal-feat:hover{opacity:.72}
.fal-feat .ff-ic{display:block;width:64px;height:64px;border-radius:var(--pp-potato,50%);border:1.6px solid var(--line,transparent);background:var(--panel,#1b1b24) url('/img/fal.png') center/contain no-repeat;flex-shrink:0}
.fal-feat .ff-meta{flex:1;min-width:0}
.fal-feat .ff-k{color:var(--gold);font-size:.7rem;font-weight:800;letter-spacing:.04em;margin-bottom:4px}
.fal-feat .ff-t{font-size:1rem;font-weight:800;line-height:1.3;margin-bottom:2px}
.fal-feat .ff-s{font-size:.78rem;color:var(--muted)}

/* CTA */
.brcta{display:flex;align-items:center;gap:13px;background:#15151c;border:1px solid rgba(255,255,255,.08);border-radius:13px;padding:12px 16px;color:inherit;transition:border-color .2s;text-decoration:none}
.brcta:hover{border-color:var(--gold)}
.brcta .brico{font-size:1.5rem}
.brcta .brtxt{flex:1}
.brcta .brtt{font-weight:800;font-size:.92rem}
.brcta .brsub{color:var(--muted);font-size:.74rem;margin-top:2px}
.brcta .brarr{color:var(--gold);font-weight:800}

/* guest login banner */
.gbanner{background:linear-gradient(120deg,rgba(231,200,115,.12),rgba(231,200,115,.03));border:1px solid rgba(231,200,115,.22);border-radius:13px;padding:12px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.gbanner .gbtxt{flex:1;min-width:160px;font-size:.86rem}
.gbanner .gbtxt b{color:var(--gold)}
.gbanner .gbtn{background:var(--gold);color:#1a1a1a;border:none;border-radius:22px;padding:8px 16px;font:inherit;font-weight:800;font-size:.8rem}

/* ==== Live radio hero cards (landing) ==== */
/* Two big buttons: VIP + قدیمی. Tap = shell.js playLive. Cover + track update
   from AzuraCast polling (shell.js writes into [data-live-cover] / [data-live-track]). */
.live-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin: 18px 0 8px;
}
.live-card {
  display: grid;
  grid-template-columns: 78px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 12px 14px;
  border: 1px solid rgba(231,200,115,.28);
  border-radius: 16px;
  background: linear-gradient(120deg, rgba(231,200,115,.08), rgba(21,21,28,.6));
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: start;
  transition: border-color .18s, transform .12s;
}
.live-card:hover { border-color: rgba(231,200,115,.55); }
.live-card:active { transform: scale(.98); }
.live-card.on { border-color: var(--gold, #e7c873); background: linear-gradient(120deg, rgba(231,200,115,.16), rgba(21,21,28,.7)); }

.live-card .lc-cov {
  display: block;
  width: 78px; height: 78px;
  border-radius: 12px;
  background: #1b1b24 center/cover no-repeat;
  box-shadow: 0 10px 22px -8px rgba(0,0,0,.6);
}
.live-card .lc-meta { min-width: 0; }
.live-card .lc-badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .66rem; font-weight: 800; color: var(--gold, #e7c873);
  letter-spacing: .04em; margin-bottom: 3px;
}
.live-card .lc-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: #ff2d55;
  animation: pulse 1.6s infinite;
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.35} }
.live-card .lc-name { font-size: 1.02rem; font-weight: 800; color: #fff; line-height: 1.2; }
.live-card .lc-track {
  font-size: .78rem; color: rgba(255,255,255,.62); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.live-card .lc-play {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--gold, #e7c873); color: #1a1a1a;
  display: flex; align-items: center; justify-content: center;
  font-size: 1rem; font-weight: 900;
  box-shadow: 0 10px 22px -8px rgba(231,200,115,.55);
}
.live-card.on .lc-play { color: #1a1a1a; }

@media (max-width: 640px) {
  .live-row { grid-template-columns: 1fr; gap: 10px; }
  .live-card { padding: 10px 12px; grid-template-columns: 64px 1fr auto; gap: 10px; }
  .live-card .lc-cov { width: 64px; height: 64px; border-radius: 10px; }
  .live-card .lc-name { font-size: .96rem; }
  .live-card .lc-track { font-size: .72rem; }
  .live-card .lc-play { width: 36px; height: 36px; font-size: .9rem; }
  .live-card.lc-v1 { padding: 10px 2px; grid-template-columns: 64px 1fr; }
}

/* Radio Boofi Classic (secondary station): quieter than the VIP hero next to
   it — flat row, no boxed card / glow button (operator 2026-07-06,
   «مینیمال‌کردن»). Shares .live-card's grid/behavior, just de-decorated. */
.live-card.lc-v1 { border: none; background: none; box-shadow: none; border-radius: 0; padding: 12px 2px; grid-template-columns: 78px 1fr; }
.live-card.lc-v1 .lc-cov { box-shadow: none; }
/* classic radio's cover used to follow the station's live "now playing" art
   (paintLiveCards() writing into [data-live-cover]), but that metadata's
   artwork isn't reliable, so it's now a fixed logo instead (operator
   2026-07-07: "عکس کاور های ترانه ها درست نیست... یک لوگو میدم اونو ثابت
   بذار"). boot.js no longer touches this element at all; .lc-cov-static
   needs the extra class to outrank paper.css's generic ".live-card .lc-cov"
   panel-background rule (html-prefixed, same specificity trap as the VIP
   tile's accent background). contain (not cover) so an arbitrary logo shape
   never gets cropped — moot here since the operator's actual logo
   (/img/radio-classic-logo.png, live 2026-07-07) is itself square. */
.live-card.lc-v1 .lc-cov.lc-cov-static { background: var(--panel, #1b1b24) url('/img/radio-classic-logo.png') center/contain no-repeat; }
.live-card.lc-v1 .lc-play { width: 30px; height: 30px; background: none; box-shadow: none; color: inherit; transform: none; }
.live-card.lc-v1 .lc-play-ic { width: 18px; height: 18px; }

/* Fix live-card play/pause toggle: use two SVG icons that CSS toggles cleanly.
   The previous text-node ▶ inside the span couldnt be hidden with > * because
   text nodes arent element children — so both play and pause icons stacked. */
.live-card .lc-play { position: relative; overflow: hidden; }
.live-card .lc-play-ic { width: 20px; height: 20px; display: block; }
.live-card .lc-ic-pause { display: none; }
.live-card.on .lc-ic-play { display: none; }
.live-card.on .lc-ic-pause { display: block; }

/* Live-card connecting state: golden spinner on the play button + a soft border
   pulse on the whole card. Added the instant the user taps a station; removed
   the moment newAu emits `playing` (audio flowing) or the cross-fade completes. */
.live-card.connecting .lc-play-ic { display: none; }
.live-card.connecting .lc-play::before {
  content: "";
  display: block;
  width: 18px; height: 18px;
  border: 2.5px solid rgba(26,26,26,.22);
  border-top-color: #1a1a1a;
  border-radius: 50%;
  animation: irSpin .75s linear infinite;
}
.live-card.connecting {
  animation: irCardPulse 1.4s ease-in-out infinite;
}
@keyframes irSpin { to { transform: rotate(360deg); } }
@keyframes irCardPulse {
  0%, 100% { border-color: rgba(231,200,115,.35); box-shadow: 0 0 0 0 rgba(231,200,115,0); }
  50%      { border-color: rgba(231,200,115,.85); box-shadow: 0 0 0 3px rgba(231,200,115,.14); }
}

/* ==== VIP live radio — a LOUD "I'm live, tap me" hero + a quiet, clearly
   LABELLED grid of previously-played tracks. Rendered by boot.js
   renderVipHist() from IR2.liveMeta('vip'); hidden until it has data.
   Redesigned 2026-07-07: the old single merged strip read as ambiguous (which
   tile is live vs history?), so the live track is now a distinct accent-filled
   hero that visibly shouts "live", and the history is a separate labelled grid
   below it. The contrast — one loud accent island over a cream page, then a
   set of quiet paper-toned cards — IS the clarity. ==== */

/* --- the live hero: the one loud element on the cream page. Its background is
   the current track's auto-extracted accent (--vh-accent, set inline by
   boot.js via /api/track_info.php), with YIQ auto-contrast text
   (--vh-fg/-soft/--vh-chip) so it stays readable against ANY accent. The WHOLE
   card is the play/stop button (data-live-station="vip"). --- */
/* v3, 2026-08-27. The surface is FIXED ink, not the cover's extracted accent:
   the accent made the page's most important element loud or muddy depending on
   which cover happened to be playing, and a first-time visitor never learned
   there was a live station. The artwork colour is still set (--vh-accent) but is
   demoted to the tuner bar in ::after. See boot.js renderVipHist for the rest. */
.vh-hero {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 16px;
  width: 100%; text-align: start; cursor: pointer;
  padding: 16px 18px 18px; border: none; border-radius: 18px;
  background: #1f1f1f; color: var(--vh-fg, #fbfaf5);
  box-shadow: 0 12px 30px -14px rgba(31,31,31,.55), 0 2px 5px rgba(31,31,31,.10);
  transition: transform .18s ease, box-shadow .18s ease;
  -webkit-tap-highlight-color: transparent;
}
/* the tuner bar: the one place the current cover's colour still speaks */
.vh-hero::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0; height: 5px;
  /* --vh-bar, not --vh-accent: boot.js lifts the cover's colour to a lightness
     floor so this never disappears into the ink slab (a dark cover used to make
     it invisible). --vh-accent is the raw value and stays available. */
  background: var(--vh-bar, var(--vh-accent, #e7c873));
}
.vh-hero:hover { transform: translateY(-2px); box-shadow: 0 18px 40px -14px rgba(31,31,31,.6), 0 3px 8px rgba(31,31,31,.12); }
.vh-hero:active { transform: translateY(0); }
/* FLUSH TO THE BANNER, not floating inside it (operator 2026-09-10).

   It was an 84px square with 16-18px of padding around it, which made the
   artwork the smallest thing on a banner whose whole subject is the track
   playing. Negative margins let it escape that padding on three sides and
   align-self:stretch takes the banner's full height, so the cover grows with
   the banner instead of sitting in a hole inside it.

   Logical margins, not physical: the cover is the first flex child of an RTL
   row, so its outer edge is inline-start - which is the physical RIGHT here.
   Writing margin-right would have been correct today and wrong the moment
   anything renders this LTR.

   The corner radius follows: the banner's own 18px on the two outer corners,
   square on the two that now meet the text. */
.vh-hero-cov {
  /* Width fixed, height from the stretch. aspect-ratio was the first attempt and
     it fought the stretch: with both the ratio and the cross size unresolved the
     box settled on the CONTENT height, 92px, and stopped 32px short of the
     banner's bottom edge - flush on two sides and floating on the third. A fixed
     width leaves nothing to negotiate. */
  flex: 0 0 auto; align-self: stretch;
  width: 118px; height: auto;
  margin-inline-start: -18px; margin-block: -16px -18px;
  border-start-start-radius: 18px; border-end-start-radius: 18px;
  border-start-end-radius: 0; border-end-end-radius: 0;
  background: rgba(0,0,0,.18) center/cover no-repeat;
}
.vh-hero-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.vh-hero-badge {
  display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
  font-size: .68rem; font-weight: 800; letter-spacing: .06em;
  padding: 4px 11px; border-radius: 999px; margin-bottom: 5px;
  background: #ff2d55; color: #fff;
}
.vh-hero-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: #fff; animation: pulse 1.6s infinite; flex-shrink: 0; }
/* --- the inverted hierarchy: station first, track last --------------------
   Before, .vh-hero-t (the TRACK) was the biggest type on the banner and «پخش
   زنده» was a chip, so the whole thing read as a song card. The station name is
   now the headline and the track is a subtitle under a «الان روی آنتن» label. */
.vh-station {
  font-size: clamp(1.25rem, 2.4vw, 1.5rem); font-weight: 800; line-height: 1.1;
  color: var(--vh-fg, #fbfaf5); letter-spacing: -.01em;
}
.vh-nowline { display: flex; align-items: baseline; gap: 7px; min-width: 0; margin-top: 3px; }
.vh-nowk {
  flex: 0 0 auto; font-size: .62rem; font-weight: 800; letter-spacing: .05em;
  color: var(--vh-fg-soft, rgba(251,250,245,.72));
  border: 1px solid var(--vh-chip, rgba(251,250,245,.22));
  border-radius: 999px; padding: 2px 7px;
}
/* the SONG outranks the artist when the line runs out: flex-grow so the title
   takes any spare width, and the artist shrinks three times as fast beside it.
   Left to the default both gave way equally and the title read «Deld…». */
.vh-hero-t { font-size: .92rem; font-weight: 700; line-height: 1.25; color: var(--vh-fg, #fbfaf5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0; }
.vh-hero-a { font-size: .8rem; font-weight: 500; line-height: 1.25; color: var(--vh-fg-soft, rgba(251,250,245,.72)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 3 auto; min-width: 0; }
/* On a phone the «الان روی آنتن» chip cost ~90px of a 300px line and cut the
   track down to «Deld…». The red ON AIR pill above already says the same thing,
   so the chip is the part that goes — the song keeps the room. */
@media (max-width: 520px) {
  .vh-nowk { display: none }
  /* and let the artist drop to its own line rather than be squeezed to «M..» —
     the title wins the first line (flex-grow above), so on a phone the artist
     was left with two characters, which is noise, not information. */
  .vh-nowline { gap: 2px 6px; flex-wrap: wrap }
  .vh-nowline .vh-hero-a { flex-basis: 100% }
}

/* --- ON AIR: the signal that has to be legible before anything else is read.
   The equaliser runs ALWAYS, not only while this browser is playing — it is
   saying "the station is broadcasting", not "you are listening". --- */
.vh-live-eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 10px; margin-inline-start: 4px; }
.vh-live-eq i { width: 3px; height: 100%; border-radius: 2px; background: currentColor;
  transform-origin: bottom; will-change: transform; animation: irEq 1s ease-in-out infinite; }
.vh-live-eq i:nth-child(1) { animation-duration: .92s; animation-delay: -.70s }
.vh-live-eq i:nth-child(2) { animation-duration: 1.18s; animation-delay: -.35s }
.vh-live-eq i:nth-child(3) { animation-duration: .78s;  animation-delay: -.55s }
.vh-live-eq i:nth-child(4) { animation-duration: 1.06s; animation-delay: -.15s }
@media (prefers-reduced-motion: reduce) {
  .vh-live-eq i, .vh-hero-play .lc-eq i, .vh-hero-badge .dot { animation: none }
  .vh-live-eq i:nth-child(odd),  .vh-hero-play .lc-eq i:nth-child(odd)  { transform: scaleY(1) }
  .vh-live-eq i:nth-child(even), .vh-hero-play .lc-eq i:nth-child(even) { transform: scaleY(.55) }
}

/* The «بشنو» label that used to sit under the play square is gone (2026-08-27)
   and the square took its height instead. The whole banner is still the button
   (data-live-station), so the affordance is the big square itself. */
/* Operator 2026-08-27: «دایره آیکون play و اکولایزر رو هم‌اندازه کاور ترانه و
   مربعی کن، عین همون کاور». So this is no longer a circle — it mirrors
   .vh-hero-cov exactly, size and corner radius, at every breakpoint. The two
   ends of the banner now read as a matched pair: artwork on one side, the
   control on the other. Keep the two in step if either size ever changes. */
.vh-hero-play {
  flex: 0 0 auto; width: 84px; height: 84px; border-radius: 15px;
  display: flex; align-items: center; justify-content: center;
  /* the square wears the track's accent too (operator 2026-08-27), so the
     cover's colour now appears exactly twice on a fixed slab: this control and
     the tuner bar. --vh-bar, NOT --vh-accent — the raw value goes dark or grey
     often enough that the square would sink into the ink. The lifted colour is
     clamped light, so the glyph on top is ink, not cream. */
  background: var(--vh-bar, var(--vh-accent, #e7c873)); color: #1f1f1f;
  transition: transform .15s ease, filter .15s ease;
}
.vh-hero:hover .vh-hero-play { filter: brightness(1.07); }
.vh-hero:hover .vh-hero-play { transform: scale(1.06); }
/* THE PLAY GLYPH IS THE OPERATOR'S OWN ARTWORK (2026-09-10).

   play-glass.png is a frosted white triangle with PLAY beneath it, on full
   transparency - so it does not carry a colour, it borrows one. Sitting on this
   square, which already wears the track's lifted accent, it takes whatever
   colour the current cover happens to give. Every track repaints the button.

   It shows ONLY while this station is not playing. The moment the banner is
   tapped the square switches to the equaliser and the artwork steps out of the
   way, which is the swap he asked for - the invitation disappears once it has
   been accepted.

   The old ▶ svg is hidden rather than removed: the same markup feeds other
   surfaces, and .lc-play-ic is what they use. */
.vh-hero-play {
  background-image: url('/assets/play-glass.png');
  background-repeat: no-repeat;
  background-position: center;
  background-size: 62% auto;
}
.vh-hero-play .lc-play-ic { display: none; }
.vh-hero.on .vh-hero-play, .vh-hero.connecting .vh-hero-play { background-image: none; }
.vh-hero-play .lc-ic-pause { display: none; }
/* the animated equalizer swaps in the instant this station is tapped and
   stays for as long as it's the one playing (connecting OR on) — the same
   affordance the mini-player uses; reverts to ▶ when paused/switched away. */
/* the playing-state equaliser fills the same square, so nothing jumps when the
   icon swaps for it */
.vh-hero-play .lc-eq { display: none; align-items: flex-end; gap: 5px; height: 36px; }
.vh-hero-play .lc-eq i { width: 6px; height: 100%; border-radius: 3px; background: currentColor;
  transform-origin: bottom; will-change: transform; animation: irEq 1s ease-in-out infinite; }
.vh-hero-play .lc-eq i:nth-child(1) { animation-duration: .92s; animation-delay: -.70s }
.vh-hero-play .lc-eq i:nth-child(2) { animation-duration: 1.18s; animation-delay: -.35s }
.vh-hero-play .lc-eq i:nth-child(3) { animation-duration: .78s;  animation-delay: -.55s }
.vh-hero-play .lc-eq i:nth-child(4) { animation-duration: 1.06s; animation-delay: -.15s }
.vh-hero-play .lc-eq i:nth-child(5) { animation-duration: .86s;  animation-delay: -.90s }
/* while vumeter.js is driving the bars from the real audio it sets height
   inline every frame, so the CSS loop has to get out of the way. If it ever
   gives up, the class comes off and this animation takes over again mid-beat. */
.vh-hero-play .lc-eq.vu-live i { animation: none; }
.vh-hero.on .lc-play-ic, .vh-hero.connecting .lc-play-ic { display: none; }
.vh-hero.on .lc-eq, .vh-hero.connecting .lc-eq { display: flex; }
/* Equalisers. Two of the three fixes from 2026-08-27 stand: whole-pixel widths
   (2.5px and 4.5px landed between device pixels and blurred), and five bars on
   five durations instead of three sharing one, which read as a mechanical loop.

   THE THIRD ONE IS REVERTED, AND MUST STAY REVERTED. I had switched these from
   transform to animating HEIGHT, because scaling a bar with a rounded cap
   squashes the cap. That is true and it is also unaffordable: this is a
   perpetual animation of a LAYOUT property, running forever, inside the shell's
   nested scroll container. On desktop Chrome it costs nothing. On iOS WebKit it
   stopped the scroller painting past the first screen — the homepage came up
   half-drawn, a reload reproduced it every time, and only a scroll or an app
   switch fixed it, which is the signature of a paint problem rather than a
   sizing one. /browse, which has no equaliser, was never affected.

   transform: scaleY() is compositor-only and costs no layout at all. The cap
   distortion is a rounding artefact on a 3px bar. Do not trade it back. */
@keyframes irEq { 0%, 100% { transform: scaleY(.26); } 50% { transform: scaleY(1); } }

/* --- the history: a quiet, LABELLED grid of what played before. Paper tones
   (faint ink tint on cream, ink text) so it reads as clearly secondary to the
   loud hero — no ambiguity about which is live. Each card → its /song page. --- */
.vh-histlabel {
  margin: 16px 2px 9px; font-size: .8rem; font-weight: 800;
  color: var(--muted, rgba(31,31,31,.55)); letter-spacing: .01em;
}
/* A RAIL since 2026-09-10. .strp brings the flex row, the snap and the ‹ ›
   arrows; this only has to give the cards a width, because a flex item with no
   width collapses to its content and a long title would stretch one card across
   the whole row. */
.vh-histgrid { gap: 9px; }
/* 179px is what the four-across grid was already giving each card, measured
   before the change - the operator asked for the rail without the tiles growing
   with it, and a rail is free to make them any width, so it has to be told. */
.vh-histgrid .vh-hcard { flex: 0 0 179px; width: 179px; scroll-snap-align: start; }
/* A PHONE HAS TO SEE THAT THERE IS MORE.

   At 375px the rail is 347 wide and two 180px cards fill 369 of it - the second
   is clipped by eleven pixels, which reads as "it fits" rather than as "it
   continues". A row that looks complete is a row nobody swipes.

   Sized as a fraction of the rail instead of in pixels, so a third card always
   shows an edge whatever the screen: 46% leaves roughly a quarter of one
   visible, which is enough to say the row goes on and not enough to look like
   a broken column. */
@media (max-width: 640px) {
  .vh-histgrid .vh-hcard { flex: 0 0 calc(46% - 6px); width: calc(46% - 6px); }
}
.vh-hcard {
  display: flex; align-items: center; min-width: 0; min-height: 60px;
  text-decoration: none; color: inherit; overflow: hidden;
  background: rgba(31,31,31,.045); border: 1px solid rgba(31,31,31,.07);
  border-radius: 12px;
  transition: background .15s ease, transform .15s ease, border-color .15s ease;
}
.vh-hcard:hover { background: rgba(31,31,31,.08); border-color: rgba(31,31,31,.12); transform: translateY(-1px); }
.vh-hcard-cov { display: block; flex: 0 0 auto; width: 60px; align-self: stretch; background: rgba(31,31,31,.10) center/cover no-repeat; }
/* THREE LINES IN THE HEIGHT THE CARD ALREADY HAD (operator 2026-09-10:
   «لازم نیست کاشی رو بلندتر کنی. پدینگ اطراف دو ردیف رو کمتر کن»).

   Nothing here grows. The side padding comes in from 10px to 8px, which is
   also the only padding this column has - the lines are centred in the card,
   never pushed apart by one - and the two pixels go to the text, which now has
   a third line to fit and one more thing to truncate. */
.vh-hcard-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; padding: 0 8px; }
.vh-hcard-main { display: flex; flex-direction: column; gap: 1px; min-width: 0;
                 text-decoration: none; color: inherit; }
.vh-hcard-t { font-size: .82rem; font-weight: 700; line-height: 1.2; color: var(--line, #1f1f1f); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* The singer's name was at .55 alpha and read as a caption under the title -
   the operator asked for it stronger, and it deserves to be: on a card whose
   whole job is "what did the station just play", who sang it is half the
   answer. Written out rather than taken from --muted, which is the shell's
   grey for a different surface and is what put it at .55 in the first place. */
.vh-hcard-a { font-size: .7rem; color: rgba(31,31,31,.74); line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* The دسته the track came in through, and a way into it. Deliberately the
   quietest line on the card: it answers a question the reader has not asked
   yet, so it should be there when they look and invisible when they don't.
   The dot marks it as a different KIND of thing from the two lines above -
   those name the track, this one names where it came from. */
.vh-hcard-cat { font-size: .64rem; line-height: 1.25; margin-top: 1px;
                color: rgba(31,31,31,.5); text-decoration: none;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.vh-hcard-cat::before { content: "●"; font-size: .5em; vertical-align: .25em;
                        margin-inline-end: 4px; opacity: .5; }
.vh-hcard-cat:hover { color: rgba(31,31,31,.82); text-decoration: underline;
                      text-underline-offset: 2px; }
/* desktop / tablet: a roomier hero and a 4-up history grid. The base above is
   mobile-first (2-col grid, already reading well on a phone), so this only
   scales things up for wider screens. */
@media (min-width: 721px) {
  .vh-hero { padding: 16px 20px; gap: 16px; border-radius: 20px; }
  /* height keeps coming from the stretch; only the width and the escape change,
     and the escape has to match THIS breakpoint's padding or the cover stops
     short of an edge again */
  .vh-hero-cov { width: 124px; height: auto;
                 margin-inline-start: -20px; margin-block: -16px;
                 border-start-start-radius: 20px; border-end-start-radius: 20px; }
  .vh-hero-t { font-size: 1.28rem; }
  .vh-hero-a { font-size: .9rem; }
  .vh-hero-play { width: 92px; height: 92px; border-radius: 17px; }   /* = .vh-hero-cov above */
  .vh-hero-play .lc-play-ic { width: 38px; height: 38px; }
  .vh-hero-play .lc-eq { height: 40px; gap: 6px; }
  .vh-hero-play .lc-eq i { width: 7px; border-radius: 4px; }
  .vh-histlabel { margin-top: 18px; font-size: .84rem; }
  .vh-histgrid { gap: 11px; }
  .vh-hcard { min-height: 66px; }
  .vh-hcard-cov { width: 66px; }
}


/* classic radio + featured episode + فال — placeholder position below the
   strip, pending a real placement decision (operator: «یک روش دیگه باید پیدا
   کنیم»). 3 items now (operator 2026-07-07 added فال), one row on desktop,
   stacked on mobile — minmax(0,1fr) so a long episode title can't blow out
   the column widths. */
/* the dashed divider used to live on .lc-v1 alone, so it only underlined
   the classic-radio card's own column instead of the row (operator
   2026-07-08: "خط چین... رو ادامه بده تا انتهای این ردیف") — moved here so
   it spans the full row regardless of how many cards are in it. */
/* TWO cards now, not three — the 🎙 podcast card was removed 2026-08-27 (the
   hosts stopped and no new episodes are being made). Left at repeat(3,…) it
   would have held an empty third column open. */
.hero-secondary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; border-bottom: 1.6px dashed var(--line, rgba(255,255,255,.14)); padding-bottom: 12px; }

/* --- the hero band: live banner + the two cards on one row (2026-08-27) ------
   Source order inside .heroband is hero, cards, history — because the cards had
   to move INSIDE #vipHistWrap to sit beside the banner. That order is wrong for
   narrow screens, where the operator explicitly wanted nothing to move: «برای
   موبایل همین جایی که الان هستند خوبه». So the narrow case is a flex column
   that re-orders back to hero → history → cards, and only the wide case turns
   into the two-column grid he asked for. Container query, not viewport, for the
   same reason as .spotouter: the playing panel eats 320–400px. */
.heroband { display: flex; flex-direction: column; gap: 0; }
.heroband > #vipHeroSlot   { order: 1; }
.heroband > #vipHistStrip  { order: 2; }

.heroband > .hero-secondary{ order: 3; margin-top: 22px; }

/* NOTE ON ORDER: this block must stay below BOTH @media (min-width: 721px) and
   the base .heroband rules directly above. Same specificity throughout, so the
   later rule wins. Placed above the media query, its banner sizes were silently
   overridden; placed above the base .heroband rules, the flex column won and the
   two-column row stopped happening at all. Cost me two rounds — leave it here.

   VIEWPORT, NOT CONTAINER. This was a @container query on .vip-hist, which is
   more correct in principle — the playing panel eats 320-400px, so content width
   is what actually matters. But the homepage rendered broken on his iPhone
   (content stopped dead at the first element carrying container-type, everything
   below it gone) while /browse, which has none, was fine. Safari has a history of
   collapsing elements with container-type, and a shop window that fails on the
   operator's own phone is not worth a more elegant breakpoint. 1400px viewport is
   the measured equivalent of ~930px of content. */
@media (min-width: 1400px) {
  .heroband {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    /* wide column-gap on purpose (operator 2026-08-27: «فاصله رادیو کلاسیک رو از
       بنر زیاد کن») — the two cards already reach the left edge of the row, so
       the only thing that separates them from the banner IS this gap. Row-gap
       stays small so the «قبلاً پخش شد» grid below does not drift away too. */
    column-gap: 46px; row-gap: 16px; align-items: center;
  }
  .heroband > #vipHeroSlot    { order: 0; }
  .heroband > .hero-secondary { order: 0; margin-top: 0; border-bottom: none; padding-bottom: 0; }
  .heroband > #vipHistStrip   { order: 1; grid-column: 1 / -1; }
  /* "کوچکترش کن": the banner is already half the width it had, so it only needs
     to lose the height that made it a full-bleed hero. */
  .vh-hero { padding: 14px 15px 16px; gap: 13px; }
  .vh-hero-play { width: 78px; height: 78px; border-radius: 13px; }
  .vh-hero-cov { width: 100px; height: auto;
                 margin-inline-start: -15px; margin-block: -14px -16px;
                 border-start-start-radius: 18px; border-end-start-radius: 18px; }
  .vh-hero-play .lc-play-ic { width: 31px; height: 31px; }
  .vh-hero-play .lc-eq { height: 34px; gap: 5px; }
  .vh-hero-play .lc-eq i { width: 6px; border-radius: 3px; }
  .vh-station { font-size: 1.24rem; }
  .vh-hero-t { font-size: .88rem; }
}

/* mobile: stacked single column, so a divider under the SECTION only shows
   up once at the very bottom — the operator wants each of the 3 cards
   separated instead (2026-07-08: "توی موبایل هم میخوام باشه زیر هر کدوم از
   ۳ قطعه"). Move the line onto each card itself and turn off the section's
   own one so the last card doesn't get a doubled-up line under it. .lc-v1's
   copy of this lives in paper.css instead — its general (non-media) brutal
   rule resets `border` to none at a specificity this can't beat here. */
@media (max-width: 720px) {
  .hero-secondary { grid-template-columns: 1fr; border-bottom: none; padding-bottom: 0; }
  .hero-secondary .fal-feat, .hero-secondary .ep-feat { border-bottom: 1.6px dashed var(--line, rgba(255,255,255,.14)); }
}

/* ==== Recent block: 5 equal-size tiles, no bigtile/smtile split ==== */
/* The old layout stretched 4 tiny thumbs next to a 240px hero — covers cropped
   ugly and the small ones were too tight to read. Now: 5-across horizontal strip
   on desktop, all the same generous size. Same rhythm as trending/albums. */
.redisc {
  display: flex;
  gap: 12px;
  flex-wrap: nowrap;
}
.redisc .bigtile,
.redisc .smtile {
  flex: 0 0 auto;
  width: 160px !important;
  height: 160px !important;
  aspect-ratio: 1;
  border-radius: 12px;
}
.redisc .smgrid { display: contents; }
/* Mobile: keep all 6 (operator 2026-07-09: "هم همون 6 تا کن"), same
   swipeable-strip mechanics as .strp, but sized as a peek carousel — tile 1
   full width, tile 2 only ~90% in view, so the cut-off edge invites the
   user to swipe for the rest ("دومی حدود 10% از عرضش جا نشه... تحریک بشه
   ورق بزنه"). calc() derives the width from the CONTAINER's own %, not a
   hardcoded px/vw guess, so the same 1-full+1-peeking rhythm holds on any
   phone width. Previous behaviour (hide the 5 smalls, blow the bigtile up
   to 100vw) was the ~4x-too-big version the operator flagged. */
@media (max-width: 640px) {
  .redisc { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .redisc::-webkit-scrollbar { display: none; }
  .redisc .bigtile,
  .redisc .smtile {
    flex: 0 0 calc((100% - 12px) / 1.9) !important;
    width: calc((100% - 12px) / 1.9) !important;
    height: auto !important;
    aspect-ratio: 1;
    scroll-snap-align: start;
  }
}
.redisc .bigtile,
.redisc .smtile { box-shadow: 0 10px 22px -10px rgba(0,0,0,.6); }
/* Same overlay + label sizing for every tile — no more "big vs small" distinction. */
.redisc .bigtile::after,
.redisc .smtile::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 45%, rgba(0,0,0,.8) 100%);
}
.redisc .bigtile .btxt,
.redisc .smtile .stxt {
  position: absolute; inset-inline-start: 0; inset-inline-end: 0; bottom: 10px;
  padding: 0 12px; z-index: 2;
}
.redisc .bigtile .btt,
.redisc .smtile .sttt { font-size: .88rem; }
.redisc .bigtile .bta,
.redisc .smtile .stta { font-size: .72rem; }


/* ==== Personal radio suggestions =========================================
   6 seed-a-station cards, each drawn from the user's own top plays. Colored
   card body (accent pulled from the seed track's bg_colors), circular seed
   portrait centered, RADIO badge in the corner — same identity language as
   Spotify's "Recommended stations" strip. Clicking a card goes to /radio/<slug>
   (the existing station page). Hidden for logged-out visitors server-side. */
.lsect .lh .hint {
  font-size: .72rem;
  color: var(--muted, rgba(255,255,255,.55));
  font-weight: 500;
  margin-inline-start: 8px;
}
.mradios { padding-top: 4px; }
.mradios .mrcard {
  display: flex; flex-direction: column;
  width: 230px;
  padding: 10px 10px 15px;
  border-radius: 18px;
  background: #14141c;
  border: 1px solid rgba(255,255,255,.06);
  color: #fff; text-decoration: none;
  position: relative; overflow: hidden;
  box-shadow: 0 14px 28px -14px rgba(0,0,0,.6);
  transition: transform .18s, box-shadow .18s, border-color .18s;
}
.mradios .mrcard:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 32px -14px rgba(0,0,0,.7);
  border-color: rgba(255,255,255,.14);
}
/* big hero cover (seed art) with a bottom scrim + hairline ring */
.mradios .mrhero {
  position: relative; display: block;
  width: 100%; aspect-ratio: 1 / 1;
  border-radius: 12px; overflow: hidden;
  background: #1b1b24 center center no-repeat; background-size: cover;
  box-shadow: 0 10px 22px -12px rgba(0,0,0,.7);
}
.mradios .mrhero::before {
  /* bottom scrim so the overlaid Persian title stays legible on bright covers */
  content: ""; position: absolute; inset: 0; border-radius: 12px; z-index: 1;
  background: linear-gradient(to top, rgba(0,0,0,.82) 0%, rgba(0,0,0,.38) 26%, transparent 54%);
  pointer-events: none;
}
.mradios .mrhero::after {
  content: ""; position: absolute; inset: 0; border-radius: 12px; z-index: 2;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
  pointer-events: none;
}
/* radio name overlaid on the cover — the site's own font family, poster style */
.mradios .mrhtitle {
  position: absolute; inset-inline: 12px; bottom: 10px; z-index: 3;
  font-size: 1.16rem; font-weight: 800; line-height: 1.16;
  color: #fff; text-align: start;
  text-shadow: 0 2px 10px rgba(0,0,0,.85), 0 1px 2px rgba(0,0,0,.9);
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* red "build-a-radio" pill, top-start, with a radio glyph (mockup language) */
.mradios .mrbadge {
  position: absolute; top: 10px; inset-inline-start: 10px; z-index: 3;
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .64rem; font-weight: 900; letter-spacing: .01em;
  background: #d4183a; color: #fff;
  border-radius: 999px; padding: 5px 11px 5px 8px;
  box-shadow: 0 5px 14px -4px rgba(212,24,58,.65);
}
.mradios .mrbadge svg { width: 14px; height: 14px; }
.mradios .mrmeta { padding: 12px 4px 0; text-align: start; }
.mradios .mrt {
  font-size: 1.02rem; font-weight: 800; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mradios .mra {
  font-size: .78rem; opacity: .68; margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* overlapping artist-face avatars + track-count chip */
.mradios .mrfaces { display: flex; align-items: center; margin-top: 12px; direction: ltr; }
.mradios .mrface {
  width: 46px; height: 46px; border-radius: 50%;
  background: #ded9ca center center no-repeat; background-size: cover;
  box-shadow: 0 0 0 1.5px #f1efe6; margin-inline-start: -10px;   /* thin ring = card cream, just separation */
}
.mradios .mrface:first-child { margin-inline-start: 0; }
.mradios .mrcount {
  margin-inline-start: auto;                 /* push to the far edge so the row spans the width */
  font-size: 1.05rem; font-weight: 800; letter-spacing: .01em;
  color: rgba(31,31,31,.5); white-space: nowrap;   /* dark — the meta area is light */
}
@media (max-width: 640px) {
  .mradios .mrcard { width: 190px; padding: 8px 8px 13px; }
  .mradios .mrt { font-size: .94rem; }
  .mradios .mra { font-size: .72rem; }
  .mradios .mrface { width: 42px; height: 42px; }
  .mradios .mrcount { font-size: .98rem; }
}

/* featured playlists card: solid color bg, optional cover image overlay.
   Sized to match .redisc's bigtile/smtile (دوباره گوش کن) exactly, operator
   2026-07-07: 260px was the same "too big" mistake already fixed there once. */
.hcard {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 160px; aspect-ratio: 1 / 1;
  padding: 14px; border-radius: 12px;
  background: var(--bg, #7c3aed);
  color: #fff; text-decoration: none;
  position: relative; overflow: hidden;
  box-shadow: 0 10px 28px -10px rgba(0,0,0,.45);
  transition: transform .18s, box-shadow .18s;
}
.hcard:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 32px -10px rgba(0,0,0,.55);
}
/* cover image with opacity control */
.hcard .hcov {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  opacity: var(--cov-opa, 26%);
  z-index: 0;
}
/* metadata on top of the cover */
.hcard .hmeta {
  position: relative; z-index: 2;
  text-align: center;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  height: 100%; gap: 10px;
}
.hcard .ht {
  font-size: .88rem; font-weight: 800; line-height: 1.2;
  text-shadow: 0 2px 8px rgba(0,0,0,.6);
}
/* pinned cards (liked songs, for you) — larger icon */
.hcard .hicon {
  font-size: 2.75rem; line-height: 1;
  text-shadow: 0 3px 12px rgba(0,0,0,.7);
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.3));
}
@media (max-width: 640px) {
  /* same peek-carousel formula as .redisc's mobile tiles (tile 1 full, tile 2
     ~90% in view) instead of a fixed px, so the two rows genuinely match. */
  .hcard { width: calc((100% - 12px) / 1.9); flex: 0 0 calc((100% - 12px) / 1.9); padding: 12px; }
  .hcard .ht { font-size: .82rem; }
  .hcard .hicon { font-size: 2.4rem; }
}

/* --- «مجله»: Monocle-style dark editorial band (2026-07-10, faithful copy
   pass 2026-07-10b — operator: don't reinterpret a proven design; match
   monocle.com. Warm charcoal not black, NORMAL weights not bold, white
   kickers, thin white hairlines between tiles, meta pinned to card bottom.
   Every color is a literal: paper.css silently overrides --gold to ink. */
.magband { margin: 40px -30px 0; background: #2e2d2b; color: #fff; padding: 30px 30px 8px; }
.magband .magin { border-top: 2px solid #fff; padding-top: 22px; }
.maghead h2 { font-size: 1.72rem; font-weight: 400; line-height: 1.5; letter-spacing: .01em; color: #fff; margin: 0 0 14px; max-width: 26ch; }
.maghead .maghint { display: block; font-size: .74rem; font-weight: 400; letter-spacing: .12em; color: #fff; margin-bottom: 26px; }
.magstrp { display: flex; gap: 0; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; padding: 2px 0 30px; }
.magstrp::-webkit-scrollbar { display: none; }
.magcard { flex: 0 0 272px; scroll-snap-align: start; text-decoration: none; color: inherit; display: flex; flex-direction: column; padding-inline: 20px; border-inline-start: 1px solid rgba(255,255,255,.45); }
.magcard:first-child { border-inline-start: 0; padding-inline-start: 0; }
.magim { position: relative; aspect-ratio: 4 / 3; background: #3a3937; overflow: hidden; }
.magim img { width: 100%; height: 100%; object-fit: cover; display: block; }
.magkick { margin-top: 16px; font-size: .7rem; font-weight: 400; letter-spacing: .14em; color: #fff; }
.magtt { margin-top: 10px; font-size: 1.18rem; font-weight: 400; line-height: 1.6; color: #fff; }
.magcard:hover .magtt { text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 1px; }
.magdt { margin-top: auto; padding-top: 18px; font-size: .72rem; font-weight: 400; letter-spacing: .06em; color: rgba(255,255,255,.65); }

/* article page — same charcoal treatment, narrow reading column */
.magpost { margin: -22px -30px calc(-1 * (var(--player-h) + 32px)); background: #2e2d2b; color: #fff; padding: 34px 30px calc(var(--player-h) + 60px); min-height: 100%; }
.magpost .magin { max-width: 680px; margin: 0 auto; border-top: 2px solid #fff; padding-top: 20px; }
.magpost .magkick { margin-top: 0; }
.magh1 { font-size: 1.65rem; font-weight: 400; line-height: 1.6; color: #fff; margin: 12px 0 8px; }
.magmeta { font-size: .74rem; color: rgba(255,255,255,.65); letter-spacing: .06em; margin-bottom: 24px; }
.magbody { font-size: 1rem; font-weight: 400; line-height: 2.2; color: #f2f0ec; }
.magbody p { margin: 0 0 1.1em; }
.magbody img { max-width: 100%; height: auto; display: block; margin: 18px auto; }
.magbody a { color: #ffcc00; }
.magback { display: inline-block; margin-top: 28px; font-size: .8rem; color: #fff; text-decoration: none; border-bottom: 1px solid rgba(255,255,255,.5); padding-bottom: 2px; }

@media (max-width: 767px) {
  .magband { margin: 30px -14px 0; padding: 22px 14px 4px; }
  .maghead h2 { font-size: 1.5rem; }
  .magcard { flex-basis: 46%; padding-inline: 14px; }
  .magtt { font-size: 1.06rem; }
  .magpost { margin: -16px -14px -30px; padding: 24px 14px 90px; }
}

/* keep مجله tiles dead straight — paper.css tilts .strp children (rotate
   ±.5deg, the pasted-photo language) which bends the white hairlines here;
   operator 2026-07-10: this section lays flat. Specificity must beat
   html[brutal] .strp > *:nth-child(...) AND survive paper.css loading last. */
html[data-ir2-skin="brutal"] .magband .magstrp > .magcard,
html[data-ir2-skin="brutal"] .magband .magstrp > .magcard:hover { transform: none; }

/* --- "listen on" external-apps strip (2026-07-11) ------------------------- */
.listenon .lo-row { display: flex; flex-wrap: wrap; gap: 12px; }
.lo-chip { display: inline-flex; align-items: center; gap: 9px; padding: 9px 15px 9px 13px;
  border: 1.5px solid rgba(31,31,31,.16); border-radius: 999px; background: #fbf6ea;
  text-decoration: none; color: #211d15; font-weight: 500; font-size: .92rem;
  transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease; }
.lo-chip:hover { transform: translateY(-2px); border-color: var(--lo); box-shadow: 3px 4px 0 rgba(31,31,31,.12); }
.lo-chip .lo-wave { width: 24px; height: 14px; color: var(--lo); flex: 0 0 auto; }
.lo-chip .lo-ext { color: rgba(31,31,31,.4); font-size: .74rem; margin-inline-start: 1px; }

/* --- house tiles that borrowed a song cover (2026-08-27) -------------------
   129 of the 149 visible houses have no uploaded artwork, so landing2.php lends
   them the cover of their own most-played track. An uploaded house cover has the
   Persian name painted into the image, which is why .hcard renders no text; a
   borrowed song cover does not, so these tiles print the name themselves and need
   a scrim under it to stay readable over whatever artwork they drew. */
.hcard-auto::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(0,0,0,.10) 0%, rgba(0,0,0,.30) 45%, rgba(0,0,0,.68) 100%);
}
.hcard-auto .hmeta { justify-content: flex-end; padding-bottom: 2px; }
.hcard-auto .ht { font-size: .95rem; text-shadow: 0 2px 10px rgba(0,0,0,.8); }
@media (max-width: 640px) { .hcard-auto .ht { font-size: .86rem; } }

/* --- «ترانه‌ی روز»: the one hand-picked track, straight in the face --------
   (2026-08-27) Operator, after «ناخدا» broke on every other site first: «چرا
   نداریم چنین قسمتی که یک ترانه صاف بخوره تو صورت بازدیدکننده».

   ONE markup, THREE shapes, and the breakpoints are CONTAINER queries, not
   viewport ones. That is the whole trick here: the playing panel takes 320–400px,
   so a 1280px screen leaves only ~810px of content and a 1600px screen ~1130px.
   A viewport media query would put the side-by-side layout on screens that
   cannot hold it. Measured content widths: 1600→1130, 1280→810, 1088 (Tesla
   dash)→698, 820 (iPad portrait)→430.

     < 460  phone            : big square cover, everything stacked under it
     460–900 tablet/Tesla    : cover square on the RIGHT, text beside it, full width
     >= 900 wide desktop     : the card becomes a sticky column on the right and
                               the two rows narrow to its left (the operator's own
                               sketch: «کاور مربعی با توضیحات بیاد سمت راست چند تا
                               از ردیف‌ها و اون ردیف‌ها رو ببره به چپ»)

   These were container queries once, which is more correct in principle. They
   are viewport queries now because the homepage rendered broken on iOS Safari
   while /browse, which carried no container-type, was fine. See the note above
   .heroband. The widths below are the measured viewport equivalents.

   PAPER-FIRST, like shares.css: this site ships one skin and it is cream +
   graphite. Ink on paper, wobbled frames, pencil shadows. NO dark panel and no
   rgba(255,255,255,…) here — the first draft of this block used a translucent
   white card and it was invisible on the cream page. And do NOT reach for
   var(--gold) here: paper.css sets it to #1f1f1f on purpose ("monochrome: the
   old gold accents become pencil"), so a gold-token chip renders ink-on-ink and
   disappears. The kicker is therefore a solid ink chip with cream type, the
   same pairing .shbub-n uses. */
.spotouter{margin-top:22px}
.spotwrap{display:grid;gap:18px;grid-template-columns:minmax(0,1fr);align-items:start}
.spotrows{min-width:0}

.spot{
  display:grid;gap:14px;align-content:start;min-width:0;
  background:#fbfaf5;border:2px solid #1f1f1f;
  border-radius:var(--pp-wob-a,14px 10px 16px 9px / 10px 15px 9px 16px);
  box-shadow:5px 7px 0 rgba(31,31,31,.2);
  padding:14px;color:#1f1f1f;
}
.spot-cov{
  display:block;width:100%;aspect-ratio:1/1;
  background:#efede3 center/cover no-repeat;
  border:1.6px solid #1f1f1f;
  border-radius:var(--pp-wob-b,10px 13px 9px 14px / 13px 9px 14px 10px);
  box-shadow:3px 4px 0 rgba(31,31,31,.14);
  transition:transform .13s ease,box-shadow .13s ease;
}
.spot-cov:hover{transform:translateY(-2px);box-shadow:4px 6px 0 rgba(31,31,31,.22)}
.spot-meta{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:6px}
.spot-kick{
  align-self:flex-start;padding:3px 10px;
  background:#1f1f1f;color:#fbfaf5;
  border:1.4px solid #1f1f1f;
  border-radius:var(--pp-wob-b,10px);
  font-size:.7rem;font-weight:800;letter-spacing:.02em;
}
.spot-ti{font-size:1.5rem;font-weight:800;line-height:1.3;color:#1f1f1f;text-decoration:none}
.spot-ti:hover{text-decoration:underline;text-underline-offset:4px}
.spot-ar{font-size:.9rem;color:rgba(31,31,31,.55)}
.spot-blurb{font-size:.85rem;line-height:2;color:rgba(31,31,31,.72);margin:4px 0 0}
.spot-play{
  margin-top:8px;
  display:inline-flex;align-items:center;gap:8px;
  background:#1f1f1f;color:#fbfaf5;
  border:2px solid #1f1f1f;
  border-radius:var(--pp-wob-b,10px 13px 9px 14px / 13px 9px 14px 10px);
  box-shadow:3px 4px 0 rgba(31,31,31,.2);
  padding:10px 20px;cursor:pointer;
  font:inherit;font-weight:800;font-size:.9rem;
  transition:transform .13s ease,box-shadow .13s ease;
}
.spot-play:hover{transform:translateY(-2px);box-shadow:4px 6px 0 rgba(31,31,31,.28)}
.spot-play svg{width:14px;height:14px;fill:currentColor;transform:scaleX(-1)}

/* shape 2 — cover on the RIGHT (first grid column is the visual right in RTL).
   Viewport, not container — see the note on .heroband above for why. 900px of
   viewport is roughly 460px of content once the playing panel is subtracted. */
@media (min-width:900px){
  .spot{grid-template-columns:200px minmax(0,1fr);align-items:center;padding:16px;gap:18px}
  .spot-ti{font-size:1.7rem}
}

/* shape 3 — the sticky side column. Must come AFTER shape 2: at 1400px both
   queries match, and this one has to win the reset back to a single stacked
   column inside its own narrow rail. */
@media (min-width:1400px){
  /* :not(.nospot) is load-bearing. landing2 adds .nospot when the operator has
     switched the spotlight off, and without this guard the two-column grid stays
     on with no card to fill column 1 — .spotrows slides into the 320px slot and
     the rows render THREE TIMES NARROWER on a wide screen than on a narrow one,
     which is exactly how he found it: «با زیاد شدن عرض صفحه اون قسمت کوچیک
     می‌شه (برعکس باید باشه)». */
  .spotwrap:not(.nospot){grid-template-columns:320px minmax(0,1fr)}
  .spot{grid-template-columns:minmax(0,1fr);align-items:start;position:sticky;top:6px;gap:14px}
  .spot-ti{font-size:1.35rem}
  .spot-blurb{font-size:.82rem}
}

/* ── the changeover (operator 2026-09-10) ────────────────────────────────────
   Two moments worth showing: the station moves to a new track, and the one that
   just finished takes its place at the head of the played-tracks rail.

   Both are short and small. This fires every few minutes on a page somebody may
   be reading, so it has to register out of the corner of an eye and then be
   over - anything longer or larger becomes something to wait through.

   The card enters from the RIGHT because the rail is RTL and its head is the
   right-hand end: the track appears to arrive from where it came. */
@keyframes vh-slide-in {
  from { opacity: 0; transform: translateX(22px) scale(.96); }
  to   { opacity: 1; transform: none; }
}
.vh-hcard.vh-new { animation: vh-slide-in .5s cubic-bezier(.2,.7,.3,1) both; }

@keyframes vh-live-in {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
@keyframes vh-cov-in {
  from { opacity: .25; transform: scale(1.05); }
  to   { opacity: 1;  transform: none; }
}
.vh-hero.vh-live-in .vh-hero-t { animation: vh-live-in .45s ease-out both; }
.vh-hero.vh-live-in .vh-hero-a { animation: vh-live-in .45s ease-out .06s both; }
.vh-hero.vh-live-in .vh-hero-cov { animation: vh-cov-in .55s ease-out both; }

/* a reader who has asked the system for less motion gets the change without it */
@media (prefers-reduced-motion: reduce) {
  .vh-hcard.vh-new, .vh-hero.vh-live-in .vh-hero-t,
  .vh-hero.vh-live-in .vh-hero-a, .vh-hero.vh-live-in .vh-hero-cov { animation: none; }
}

/* A linked row heading keeps looking like a heading. It carries no colour of its
   own and no underline until the pointer is on it - the point is that the whole
   title is now a target, not that it should announce itself as a link. The skin
   is named explicitly because paper.css scopes its own heading colour that way
   and would otherwise win. */
.lsect .lh h2 a,
html[data-ir2-skin="brutal"] .lsect .lh h2 a { color: inherit; text-decoration: none; }
.lsect .lh h2 a:hover { text-decoration: underline; text-underline-offset: 5px;
                        text-decoration-thickness: 1px; }

/* ── the two secondary cards leave the phone home page (operator 2026-09-11) ──
   «رادیو بوفی کلاسیک» and «فالِ ترانه» are rows in the ☰ menu now, and the
   menu is the reason they can go: about 200px of the first screen back, on a
   page where the live banner and the news are what the reader came for.

   ≤767 ONLY, and that number is not a guess - it is exactly where .tb-burger
   appears. Above it there is no menu, so these cards are the only route to
   either destination and they stay. Hidden, not removed: the markup is intact
   and a wider window shows it. */
@media (max-width: 767px) {
  .hero-secondary { display: none; }
}

/* ── تازه‌ها wears the «دوباره گوش کن» cover (operator 2026-09-11) ───────────
   The .ctile cover is 132px at every width. The redisc tiles are 160 on a wide
   screen and, on a phone, one full tile plus most of the next. He asked for
   this rail to match them, and it is the right rail to give the extra size to:
   it is the only one of the three whose whole claim is "look at this, it wasn't
   here yesterday", and a cover is what carries that.

   Only .frsh - ترند and تاپ ۵۰ keep the smaller tile. Three rails of identical
   cards in a row say nothing about which one to look at first.

   The numbers are lifted from .redisc rather than re-guessed, down to the 1.9
   divisor that leaves the next cover half-showing on a phone. */
.frsh .ctile { width: 160px; }
.frsh .ctile .ccov { width: 160px; height: 160px; border-radius: 12px; }
@media (max-width: 640px) {
  .frsh .ctile { width: calc((100% - 12px) / 1.9); }
  .frsh .ctile .ccov { width: 100%; height: auto; aspect-ratio: 1; }
}
