/* =============================================================================
   Allfield v2 — radio-log aesthetic
   ============================================================================= */

/* Anek Kannada, self-hosted — these blocks mirror the css2 response Google
   served (same variable woff2 files, weights, stretch and unicode-ranges), so
   rendering is identical but first paint doesn't wait on two extra TLS
   connections. Browsers only fetch the subsets a page actually uses — for
   this site that's the 39KB latin file, which index.html also preloads. */
/* kannada */
@font-face {
  font-family: 'Anek Kannada';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 125%;
  font-display: swap;
  src: url('assets/fonts/anek-kannada-kannada.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0C80-0CF3, U+1CD0, U+1CD2-1CD3, U+1CDA, U+1CF2, U+1CF4, U+200C-200D, U+20B9, U+25CC, U+A830-A835;
}
/* latin-ext */
@font-face {
  font-family: 'Anek Kannada';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 125%;
  font-display: swap;
  src: url('assets/fonts/anek-kannada-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Anek Kannada';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 125%;
  font-display: swap;
  src: url('assets/fonts/anek-kannada-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

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

:root {
  /* Accent palette — single source of truth.
     Site palette: #001524 navy / #15616D teal / #FFECD1 cream /
     #FF7D00 orange / #78290F rust. */
  --orange:     #ff7d00;
  --amber:      #e3a23a;
  --moss:       #9eb05c;
  --ice:        #15616d;

  /* Smoke palette — shader companion colours, shared across themes.
     Teal highlight / deep navy / sea teal, from the site palette. */
  --smoke-cyan:    #1f8fa3;
  --smoke-deep:    #0a2e40;
  --smoke-sea:     #15616d;

  /* Defaults; per-theme blocks override accent + smoke-bg/c2/c3 below */
  --accent:     var(--orange);
  --accent-fg:  #001524;
  --accent-ink: var(--accent);

  --radius-sm: 4px;
  --radius:    10px;
  --radius-lg: 16px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  --grain: 0.18;
}

[data-accent="orange"] { --accent: var(--orange); }
[data-accent="amber"]  { --accent: var(--amber);  }
[data-accent="moss"]   { --accent: var(--moss);   }
[data-accent="ice"]    { --accent: var(--ice);    }

/* ---------- Themes ---------- */
[data-theme="charcoal"] {
  --bg:          #001524;
  --bg-2:        #06202e;
  --bg-3:        #0c2a3a;
  --fg:          #ffecd1;
  --fg-muted:    #b3a793;
  --fg-subtle:   #746f63;
  /* Accent used as text ink: full accent reads fine on the dark bg. */
  --accent-ink:  var(--accent);
  --border:      rgba(255, 236, 209, 0.10);
  --border-2:    rgba(255, 236, 209, 0.20);
  --border-3:    rgba(255, 236, 209, 0.35);
  --smoke-bg:    #000b14;
  --smoke-c1:    var(--smoke-cyan);
  --smoke-c2:    var(--smoke-deep);
  --smoke-c3:    var(--smoke-sea);
}
[data-theme="light"] {
  --bg:          #ffecd1;
  --bg-2:        #f4dfc0;
  --bg-3:        #fff4e2;
  --fg:          #001524;
  --fg-muted:    #47525c;
  --fg-subtle:   #6f7680;
  /* Accent as text ink — pure orange fails contrast on cream, so lean it
     toward the navy fg; with the default orange this lands on the palette's
     rust (#78290F) and tracks the other accents automatically. */
  --accent-ink:  color-mix(in srgb, var(--accent) 58%, var(--fg));
  --border:      rgba(0, 21, 36, 0.12);
  --border-2:    rgba(0, 21, 36, 0.22);
  --border-3:    rgba(0, 21, 36, 0.40);
  --smoke-bg:    #fff8ec;
  --smoke-c1:    #15616d;
  --smoke-c2:    var(--smoke-deep);
  --smoke-c3:    var(--smoke-sea);
}

html, body {
  background: var(--bg);
  color: var(--fg);
  font-family: 'Anek Kannada', system-ui, sans-serif;
  font-stretch: 125%;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  overscroll-behavior: none;
  overflow-anchor: none;
  letter-spacing: -0.005em;
  -webkit-tap-highlight-color: transparent;
}
body {
  min-height: 100dvh;
  position: relative;
  overflow-x: hidden;
  /* All text dodges the cursor (initMagneticText in app.js), so the I-beam
     cursor + text selection would conflict with that interaction. */
  user-select: none;
  -webkit-user-select: none;
  cursor: default;
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button { font: inherit; border: 0; background: transparent; color: inherit; cursor: pointer; }

/* =============================================================================
   Background layers
   ============================================================================= */
.bg-paper, .bg-grain, .bg-vignette, .bg-paint {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  /* Pin to the large viewport so the mobile URL-bar collapse doesn't resize
     these layers (the smoke canvas would rescale and visibly shift). While
     the bar is shown the extra bottom strip is simply cropped. */
  height: 100vh;
  height: 100lvh;
}

/* Shader-rendered paint canvas — see bg-shader init in app.js.
   Height comes from the shared 100lvh rule above. */
.bg-paint {
  z-index: 0;
  width: 100%;
  display: block;
  opacity: 0.9;
  mix-blend-mode: screen;
  filter: saturate(1.15);
}
[data-theme="light"] .bg-paint {
  mix-blend-mode: multiply;
  opacity: 0.7;
  filter: saturate(1.05);
}
.bg-paper {
  background:
    radial-gradient(circle at 18% 15%, rgba(216, 90, 43, 0.10), transparent 40%),
    radial-gradient(circle at 82% 85%, rgba(237, 229, 208, 0.04), transparent 45%);
}
[data-theme="light"] .bg-paper {
  background:
    radial-gradient(circle at 18% 15%, rgba(106, 30, 30, 0.08), transparent 40%),
    radial-gradient(circle at 82% 85%, rgba(74, 93, 58, 0.06), transparent 50%);
}
.bg-vignette {
  background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.35) 100%);
  z-index: 1;
}
[data-theme="light"] .bg-vignette {
  background: radial-gradient(ellipse at center, transparent 55%, rgba(26,22,18,0.15) 100%);
}
.bg-grain {
  z-index: 2;
  opacity: var(--grain);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg viewBox='0 0 240 240' xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.9 0 0 0 0 0.85 0 0 0 0 0.75 0 0 0 1.3 -0.2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 220px 220px;
}
[data-theme="light"] .bg-grain {
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml;utf8,<svg viewBox='0 0 240 240' xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.08 0 0 0 0 0.05 0 0 0 0 0.03 0 0 0 1.4 -0.3'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* =============================================================================
   Page
   ============================================================================= */
.page {
  position: relative;
  z-index: 3;
  max-width: 720px;
  margin: 0 auto;
  /* Notch / home-indicator safe areas with sensible floors at every breakpoint. */
  padding-top: 28px;
  padding-right: max(20px, env(safe-area-inset-right));
  padding-bottom: max(64px, env(safe-area-inset-bottom));
  padding-left: max(20px, env(safe-area-inset-left));
  animation: page-emerge 600ms var(--ease) 0s both;
}
@media (min-width: 640px) {
  .page {
    padding-top: 48px;
    padding-right: max(28px, env(safe-area-inset-right));
    padding-bottom: max(80px, env(safe-area-inset-bottom));
    padding-left: max(28px, env(safe-area-inset-left));
  }
}
@keyframes page-emerge {
  from { opacity: 0; filter: blur(6px); }
  to   { opacity: 1; filter: blur(0); }
}
@keyframes fly-in {
  from { opacity: 0; transform: translateY(16px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes pill-fly {
  from { opacity: 0; transform: translateY(16px) scale(0.97); }
  to   { opacity: 1; transform: rotate(var(--tilt, 0deg)) translateY(var(--nudge, 0px)); }
}
/* Pebble-shape morph — states hover near round so pills read as hand-cut
   stones, never far from the original circle. */
@keyframes pill-blob {
  0%, 100% { border-radius: 42% 58% 55% 45% / 60% 42% 58% 40%; }
  25%      { border-radius: 58% 42% 40% 60% / 44% 57% 43% 56%; }
  50%      { border-radius: 45% 55% 60% 40% / 42% 58% 44% 56%; }
  75%      { border-radius: 57% 43% 42% 58% / 56% 43% 60% 40%; }
}
@keyframes smoke-emerge {
  from { opacity: 0; filter: blur(14px); transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; filter: blur(0); transform: translateY(0) scale(1); }
}
/* Hero name letters scatter to random off-positions and fly into place,
   fading sharp from a smoky blur. Per-letter --dx/--dy/--dr/--ds/--db offsets
   are randomized by initIntro in app.js. */
.id-name { animation: none; }
.id-name .ch {
  display: inline-block;
  will-change: transform, filter, opacity;
  animation: ch-fly 1100ms cubic-bezier(0.2, 0.7, 0.2, 1) calc(0.4s + var(--ci, 0) * 60ms) backwards;
  /* Smooth glide for the magnetic-repel offsets set inline by initMagneticTitle. */
  transition: transform 340ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Magnetic body text — letters split by initMagneticText (app.js). The word
   wrapper is nowrap so wrapping still happens at word boundaries only. */
.mch-w { display: inline-block; white-space: nowrap; }
.mch {
  display: inline-block;
  transition: transform 340ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.next      { animation: fly-in 600ms var(--ease) 1.05s backwards; }
/* pill-fly (not fly-in) so the entrance lands on the tilted resting pose
   instead of snapping from transform:none to the scatter tilt at animation
   end. The negative pill-blob delay phase-offsets each pill's morph cycle,
   so every pill shows a different pebble shape at any moment. */
.pill      { animation: pill-fly 500ms var(--ease) calc(1.2s + var(--pi, 0) * 60ms) backwards,
                        pill-blob 9s ease-in-out calc(var(--pi, 0) * -1.5s) infinite; }
.pills .pill:nth-child(1) { --pi: 0; --tilt: -1.6deg; --nudge: 1px; }
.pills .pill:nth-child(2) { --pi: 1; --tilt:  1.2deg; --nudge: -1px; }
.pills .pill:nth-child(3) { --pi: 2; --tilt: -0.8deg; --nudge: -2px; }
.pills .pill:nth-child(4) { --pi: 3; --tilt:  1.8deg; --nudge: 2px; }
.pills .pill:nth-child(5) { --pi: 4; --tilt: -1.2deg; --nudge: 0px; }
.pills .pill:nth-child(6) { --pi: 5; --tilt:  0.9deg; --nudge: -1px; }
.pills .pill:nth-child(7) { --pi: 6; --tilt: -1.5deg; --nudge: 1px; }
/* Mixes and year-dividers are hidden by default; initBloomOnScroll in app.js
   adds .bloom (run the smoke-emerge animation) or .bloom-instant (already in
   final state — no animation). The initial-cohort cards get .bloom-load too,
   which restores the original hero-cascade timing via --i / --yi. */
.mix:not(.bloom), .yr:not(.bloom) {
  opacity: 0;
  filter: blur(14px);
  transform: translateY(10px) scale(0.985);
}
.mix.bloom { animation: smoke-emerge 820ms var(--ease) backwards; }
.yr.bloom  { animation: smoke-emerge 700ms var(--ease) backwards; }
.mix.bloom-load { animation-delay: calc(1.35s + var(--i, 0) * 55ms); }
.yr.bloom-load  { animation-delay: calc(1.3s + var(--yi, 0) * 55ms); }
.mix.bloom-instant, .yr.bloom-instant {
  animation: none;
  opacity: 1;
  filter: none;
  transform: none;
}

@keyframes ch-fly {
  0% {
    opacity: 0;
    transform: translate(var(--dx, 0), var(--dy, 0)) rotate(var(--dr, 0deg)) scale(var(--ds, 0.7));
    filter: blur(var(--db, 16px));
  }
  60%  { opacity: 1; }
  100% {
    opacity: 1;
    transform: translate(0, 0) rotate(0) scale(1);
    filter: blur(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .page, .id-name .ch, .next, .pill, .mix, .yr { animation: none; }
  /* The bloom-hidden state must not strand the cards invisible. */
  .mix:not(.bloom), .yr:not(.bloom) {
    opacity: 1;
    filter: none;
    transform: none;
  }
}

/* =============================================================================
   Identity block
   ============================================================================= */
.id {
  margin-bottom: 20px;
}

.id-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
  font-family: 'Anek Kannada', sans-serif;
}
.id-call-label, .id-freq-label {
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-subtle);
  margin-bottom: 3px;
}
.id-call-value, .id-freq-value {
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--fg);
  font-weight: 500;
}
.id-call-value span { color: var(--accent); }
.id-freq { text-align: right; }

.id-body {
  display: block;
  /* The whole title→pills gap lives here. */
  margin-bottom: 16px;
}

/* Transient hint beside the title — fades in after the letter cascade lands,
   lingers a few seconds, then fades away for good. */
.id-text { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 16px; }
.id-hint {
  font-family: 'Anek Kannada', sans-serif;
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--accent-ink);
  white-space: nowrap;
  opacity: 0;
  animation: hint-flash 5.5s var(--ease) 2s forwards;
}
@keyframes hint-flash {
  0%   { opacity: 0; transform: translateX(-6px); }
  12%  { opacity: 1; transform: translateX(0); }
  78%  { opacity: 1; }
  100% { opacity: 0; }
}
.id-name {
  font-family: 'Anek Kannada', sans-serif;
  font-weight: 600;
  font-size: clamp(48px, 12vw, 64px);
  line-height: 0.9;
  letter-spacing: -0.05em;
  text-transform: lowercase;
  color: var(--fg);
}
.id-bio {
  font-size: 14px;
  line-height: 1.5;
  color: var(--fg-muted);
  max-width: 42ch;
  text-wrap: pretty;
}
.id-bio em {
  font-style: normal;
  color: var(--fg);
  font-weight: 500;
}

/* Pills */
.pills {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--border);
  /* Static blob fallback = pill-blob's 0% state, for reduced-motion where the
     morph animation is disabled. */
  border-radius: 42% 58% 55% 45% / 60% 42% 58% 40%;
  color: var(--fg);
  touch-action: manipulation;
  /* Hand-placed scatter: per-pill --tilt/--nudge set alongside --pi above. */
  transform: rotate(var(--tilt, 0deg)) translateY(var(--nudge, 0px));
  background: color-mix(in srgb, var(--bg-2) 38%, transparent);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.35),
              inset 0 1px 0 0 color-mix(in srgb, var(--fg) 5%, transparent);
  transition: border-color 220ms var(--ease),
              color 220ms var(--ease),
              background 220ms var(--ease),
              box-shadow 220ms var(--ease),
              /* Overshoot bezier gives the squash a springy pop. */
              transform 350ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (hover: hover) {
  .pill:hover {
    border-color: var(--accent);
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, color-mix(in srgb, var(--bg-2) 38%, transparent));
    box-shadow: 0 8px 22px -6px color-mix(in srgb, var(--accent) 45%, transparent),
                inset 0 1px 0 0 color-mix(in srgb, var(--fg) 8%, transparent);
    /* Keep tilt/nudge so the pill doesn't snap straight; squash for softness. */
    transform: rotate(var(--tilt, 0deg)) translateY(calc(var(--nudge, 0px) - 2px)) scale(1.06, 0.94);
  }
  .pill:hover svg,
  .pill:hover .pill-mask {
    transform: scale(1.1);
  }
}
.pill:active {
  transform: rotate(var(--tilt, 0deg)) translateY(var(--nudge, 0px)) scale(0.92, 1.06);
  box-shadow: 0 1px 4px -2px color-mix(in srgb, var(--accent) 30%, transparent);
}
.pill svg,
.pill .pill-mask {
  flex-shrink: 0;
  transition: transform 220ms var(--ease);
}
.pill .pill-mask {
  display: block;
  width: 20px;
  height: 20px;
  background-color: currentColor;
}
.pill .ra-mask {
  -webkit-mask: url('assets/logo-ra.png') center / contain no-repeat;
          mask: url('assets/logo-ra.png') center / contain no-repeat;
}
.pill .cham-mask {
  /* A touch larger than the 20×20 brand glyphs so the silhouette reads at
     similar visual weight without dominating the row. */
  width: 22px;
  height: 22px;
  -webkit-mask: url('assets/logo-chameleon.png') center / contain no-repeat;
          mask: url('assets/logo-chameleon.png') center / contain no-repeat;
}
.pill .luso-mask {
  -webkit-mask: url('assets/logo-lusophonica.png') center / contain no-repeat;
          mask: url('assets/logo-lusophonica.png') center / contain no-repeat;
}
/* Override the inline width=26 / height=11.3 on the SoundCloud logo so it
   reads at the same visual weight as the other brand glyphs across the row. */
.pill[data-platform="soundcloud"] svg {
  width: 22px;
  height: 9.6px;
}
.pill img {
  flex-shrink: 0;
  display: block;
  width: 22px;
  height: 22px;
  object-fit: contain;
}

/* =============================================================================
   Next event
   ============================================================================= */
.next {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  min-height: 88px;
  margin-bottom: 20px;
  touch-action: manipulation;
  background: color-mix(in srgb, var(--bg-2) 38%, transparent);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  border: 1px solid var(--border);
  /* Organic hand-cut corners, matching the mix cards below. */
  border-radius: 22px 8px 24px 7px / 12px 22px 9px 24px;
  color: var(--fg);
  position: relative;
  overflow: hidden;
  box-shadow: 0 14px 50px -16px color-mix(in srgb, var(--accent) 32%, transparent),
              0 6px 18px -10px rgba(0, 0, 0, 0.4),
              inset 0 1px 0 0 color-mix(in srgb, var(--fg) 8%, transparent);
  transition: border-color 200ms var(--ease),
              transform 200ms var(--ease),
              box-shadow 200ms var(--ease);
}
.next::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 88% 0%,
              color-mix(in srgb, var(--accent) 22%, transparent),
              transparent 55%);
  pointer-events: none;
  border-radius: inherit;
  z-index: 0;
}
@media (hover: hover) {
  .next:hover {
    border-color: var(--accent);
    transform: translateY(-2px) rotate(-0.25deg);
    box-shadow: 0 14px 40px -10px color-mix(in srgb, var(--accent) 50%, transparent),
                inset 0 1px 0 0 color-mix(in srgb, var(--fg) 14%, transparent);
  }
  .next:hover .next-arrow { color: var(--accent); }
}
.next-img {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: clamp(80px, 20%, 140px);
  height: 100%;
  border-radius: 0;
  object-fit: cover;
  display: block;
  pointer-events: none;
  z-index: 0;
  -webkit-mask-image: linear-gradient(to right, black 0%, black 18%, transparent 100%);
          mask-image: linear-gradient(to right, black 0%, black 18%, transparent 100%);
}
.next-body {
  min-width: 0;
  position: relative;
  z-index: 1;
  flex: 1;
  padding-left: clamp(74px, 21%, 140px);
}
.next-tag {
  display: inline-block;
  font-family: 'Anek Kannada', sans-serif;
  font-size: 8px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: 999px;
  padding: 3px 7px;
  margin-bottom: 6px;
}
.next-title {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.next-meta {
  font-family: 'Anek Kannada', sans-serif;
  font-size: 8.5px;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.next-arrow {
  width: 16px;
  height: 16px;
  color: var(--fg-subtle);
  margin-right: 6px;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
  transition: color 160ms var(--ease);
}

/* =============================================================================
   Mixes — cards view
   ============================================================================= */
.mixes {
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
}


/* Year divider */
.yr {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0 4px;
  padding-left: 14px;
  font-family: 'Anek Kannada', sans-serif;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg-subtle);
}
.mixes > .yr:first-child {
  margin-top: 0;
}
.yr-line {
  flex: 1;
  height: 1px;
  background: linear-gradient(to right,
    color-mix(in srgb, var(--border) 90%, transparent) 0%,
    color-mix(in srgb, var(--border) 70%, transparent) 35%,
    transparent 100%);
}
.yr-num {
  color: var(--fg);
  font-weight: 500;
  font-size: 11px;
}
/* Upcoming divider — accent instead of fg so the section reads as "not yet
   another year", without breaking the divider rhythm. */
.yr-up .yr-num { color: var(--accent-ink); }

/* Card */
.mix {
  /* Per-card highlight colour, keyed to the source platform; accent is the
     fallback when a mix has no recognised platform. */
  --plat-color: var(--accent);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  min-height: 88px;
  touch-action: manipulation;
  border: 1px solid var(--border);
  /* Organic hand-cut corners; two nth-child variants below cycle the shape
     down the list so neighbours never share a silhouette. --card-tilt leans
     the hover lift slightly, alternating direction per row. */
  --card-tilt: -0.3deg;
  border-radius: 24px 8px 20px 6px / 14px 24px 8px 22px;
  background: color-mix(in srgb, var(--bg-2) 42%, transparent);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  box-shadow: 0 6px 22px -10px rgba(0, 0, 0, 0.35),
              0 2px 8px -3px color-mix(in srgb, var(--accent) 10%, transparent),
              inset 0 1px 0 0 color-mix(in srgb, var(--fg) 5%, transparent);
  position: relative;
  overflow: hidden;
  z-index: 1;
  transition: border-color 200ms var(--ease),
              transform 200ms var(--ease),
              background 200ms var(--ease),
              box-shadow 200ms var(--ease);
}
.mix:nth-child(even) { --card-tilt: 0.3deg; }
.mix:nth-child(3n)   { border-radius: 7px 22px 9px 24px / 20px 8px 24px 10px; }
.mix:nth-child(3n+1) { border-radius: 20px 10px 26px 8px / 24px 18px 10px 26px; }
.mix[data-platform="soundcloud"] { --plat-color: #ff7700; }
.mix[data-platform="mixcloud"]   { --plat-color: #52aadc; }
.mix[data-platform="youtube"]    { --plat-color: #ff3b30; }
@media (hover: hover) {
  .mix:hover {
    border-color: var(--border-3);
    background: color-mix(in srgb, var(--bg-3) 50%, transparent);
    transform: translateY(-2px) rotate(var(--card-tilt));
    box-shadow: 0 12px 32px -10px rgba(0, 0, 0, 0.4),
                0 4px 14px -4px color-mix(in srgb, var(--plat-color) 32%, transparent),
                inset 0 1px 0 0 color-mix(in srgb, var(--fg) 10%, transparent);
  }
  /* One shared rule drives both glows so they can never diverge. */
  .mix:hover .mix-tag,
  .mix:hover .mix-plat,
  .mix:hover .mix-live {
    border-color: color-mix(in srgb, var(--plat-color) 60%, transparent);
    box-shadow: 0 0 12px -2px color-mix(in srgb, var(--plat-color) 45%, transparent),
                inset 0 0 8px -4px color-mix(in srgb, var(--plat-color) 55%, transparent);
  }
  .mix:hover .mix-tag { color: var(--plat-color); }
}

.mix-rail { display: none; }

.mix-art {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  /* Cap near the card height so the box stays ~square — the 100x100 art
     shows nearly whole instead of a cover-cropped slice. */
  width: clamp(80px, 20%, 116px);
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
  /* Hold solid longer so the photo reads as a photo before dissolving. */
  -webkit-mask-image: linear-gradient(to right, black 0%, black 45%, transparent 100%);
          mask-image: linear-gradient(to right, black 0%, black 45%, transparent 100%);
}
.mix-art img {
  /* +20px slack with -10px offset lets initArtParallax in app.js translate
     the artwork up to ±8px without exposing the card edge. */
  position: relative;
  width: 100%;
  height: calc(100% + 20px);
  top: -10px;
  object-fit: cover;
  display: block;
}
.mix.visible .mix-art img { will-change: transform; }
.mix-art::after { display: none; }
.mix-play { display: none; }

.mix-body {
  position: relative;
  z-index: 1;
  flex: 1;
  min-width: 0;
  /* Tracks the slimmer art column; tucks into the fade tail so the text
     sits right up against the visible part of the photo. */
  padding-left: clamp(74px, 21%, 110px);
}
.mix-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 3px;
  font-family: 'Anek Kannada', sans-serif;
  font-size: 8.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-subtle);
}
.mix-code {
  color: var(--accent);
  font-weight: 500;
  letter-spacing: 0.08em;
  white-space: nowrap;
  flex-shrink: 0;
}
.mix-date { white-space: nowrap; flex-shrink: 0; }
.mix-meta { flex-wrap: nowrap; }
.mix-date { color: var(--fg-muted); }
.mix-dot {
  width: 3px; height: 3px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.6;
}
.mix-title {
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--fg);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-word;
  /* The line-clamp's overflow:hidden clips the magnetic letters (they glide
     up to ~9px); padding gives them room inside the clip box and the negative
     margin cancels it so the layout doesn't shift. */
  padding: 10px 12px;
  margin: -10px -12px;
}
.mix-sub {
  font-family: 'Anek Kannada', sans-serif;
  font-size: 9px;
  color: var(--fg-subtle);
  letter-spacing: 0.06em;
  margin-top: 3px;
}

.mix-tag {
  display: inline-block;
  vertical-align: middle;
  font-family: 'Anek Kannada', sans-serif;
  font-size: 8px;
  letter-spacing: 0.14em;
  /* No text-indent — trimming padding-right instead absorbs the trailing
     letter-spacing on every browser (the text-indent trick is unreliable
     inside inline-flex on iOS Safari). */
  text-transform: uppercase;
  line-height: 1;
  /* Horizontal padding matches the effective vertical padding (5+3)/2 = 4;
     the 5/3 split optically centres the uppercase glyphs at line-height 1. */
  padding: 5px 4px 3px;
  padding-right: calc(4px - 0.14em);
  border-radius: 3px;
  color: var(--fg-muted);
  border: 1px solid var(--border-2);
  /* Transparent shadow at final geometry: transitioning from `none` would
     also animate blur/spread from 0, so the glow only turns visible late in
     the 200ms and reads as delayed. This way only the colour fades in. */
  box-shadow: 0 0 12px -2px transparent, inset 0 0 8px -4px transparent;
  transition: color 200ms var(--ease),
              border-color 200ms var(--ease),
              box-shadow 200ms var(--ease);
}

.mix-right {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

/* Upcoming card — live chip sits where the platform badge normally goes;
   geometry copies .mix-tag so the pair reads as one family. */
.mix-live {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: 'Anek Kannada', sans-serif;
  font-size: 8px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1;
  padding: 5px 4px 3px;
  padding-right: calc(4px - 0.14em);
  border-radius: 3px;
  color: var(--accent-ink);
  border: 1px solid color-mix(in srgb, var(--accent-ink) 45%, transparent);
  /* Transparent shadow at final geometry — see .mix-tag note. */
  box-shadow: 0 0 12px -2px transparent, inset 0 0 8px -4px transparent;
  transition: color 200ms var(--ease),
              border-color 200ms var(--ease),
              box-shadow 200ms var(--ease);
}
.mix-live-dot {
  width: 4px; height: 4px;
  border-radius: 50%;
  background: currentColor;
  /* Uppercase glyphs sit high at line-height 1 with the 5/3 padding split. */
  margin-top: -2px;
  animation: live-pulse 1.6s ease-in-out infinite;
}
@keyframes live-pulse {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .mix-live-dot { animation: none; }
}
/* The slot time, under the title. Time and timezone are separate spans that
   sit side by side and stack as whole units when the column gets narrow. */
.mix-up .mix-sub {
  display: flex;
  flex-wrap: wrap;
  column-gap: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: color-mix(in srgb, var(--accent-ink) 75%, var(--fg));
}
.mix-up .mix-sub span { white-space: nowrap; }
/* Hover glow + tag hover colour on this card follow the same ink. */
.mix-up { --plat-color: var(--accent-ink); }

/* =============================================================================
   Log (table) view
   ============================================================================= */
.mixes.log {
  display: block;
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-2);
}
.mixes.log::before { display: none !important; }
.mixes.log .yr {
  padding: 10px 16px;
  margin: 0;
  background: var(--bg-3);
  border-bottom: 1px solid var(--border);
  border-top: 1px solid var(--border);
}
.mixes.log .yr::before { display: none; }
.mixes.log .yr:first-child { border-top: 0; }

.mixes.log .mix {
  display: grid;
  grid-template-columns: 90px 1fr 110px;
  gap: 14px;
  padding: 9px 16px;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  animation: none;
  min-height: 38px;
  align-items: center;
}
@media (hover: hover) {
  .mixes.log .mix:hover {
    background: var(--bg-3);
    transform: none;
  }
}
.mixes.log .mix-rail { display: none; }
.mixes.log .mix-art { display: none; }
.mixes.log .mix-sub { display: none; }

/* In log view the mix-body becomes the title column; the meta (code + date)
   is split and hoisted into its own columns via CSS grid positioning. */
.mixes.log .mix-body {
  grid-column: 2 / 3;
  min-width: 0;
  padding-left: 0;
}
.mixes.log .mix-meta {
  display: contents; /* let children participate in the grid */
}
.mixes.log .mix-code {
  grid-column: 1 / 2;
  grid-row: 1;
  font-size: 10px;
  align-self: center;
}
.mixes.log .mix-date {
  grid-column: 1 / 2;
  grid-row: 1;
  font-size: 10px;
  color: var(--fg-muted);
  align-self: center;
}
.mixes.log .mix-dot { display: none; }
.mixes.log .mix-title {
  font-family: 'Anek Kannada', sans-serif;
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: 0.01em;
  text-transform: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mixes.log .mix-right {
  grid-column: 3 / 4;
  justify-content: flex-end;
}
.mixes.log .mix-tag { font-size: 8.5px; padding: 3px 2px 1px; }

/* Platform badge (SoundCloud / Mixcloud / YouTube) */
.mix-plat {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  border-radius: 999px;
  color: var(--fg-subtle);
  /* Pill-family look, but the badge sits ON the card's glass — stacking the
     full pill recipe (38% fill + blur + saturate) over it compounded far too
     bright. A thin fill over the card's existing glass composites to roughly
     the same value as a pill on the bare page. border-2 matches the station
     tag beside it. */
  border: 1px solid var(--border-2);
  background: color-mix(in srgb, var(--bg-2) 18%, transparent);
  /* Transparent shadow at final geometry — see .mix-tag note. */
  box-shadow: 0 0 12px -2px transparent, inset 0 0 8px -4px transparent;
  flex-shrink: 0;
  /* Same duration AND curve as .mix-tag — a slower default `ease` here read
     as the badge highlight lagging the tag's. */
  transition: color 200ms var(--ease),
              border-color 200ms var(--ease),
              background 200ms var(--ease),
              box-shadow 200ms var(--ease);
}
.mix-plat[data-plat="soundcloud"] { color: #ff7700; }
.mix-plat[data-plat="mixcloud"]   { color: #52aadc; }
.mix-plat[data-plat="youtube"]    { color: #ff3b30; }
/* Override the inline SVG width/height attributes so each platform glyph
   sits a touch smaller inside the badge — more breathing room around it. */
.mix-plat svg { width: 12px; height: 12px; }
.mix-plat[data-plat="youtube"] svg { width: 10px; height: 7px; }
.mixes.log .mix-plat { width: 18px; height: 18px; }
.mixes.log .mix-plat svg { width: 10px; height: 10px; }

/* =============================================================================
   Footer
   ============================================================================= */
.foot {
  margin-top: 48px;
  font-family: 'Anek Kannada', sans-serif;
}
.foot-line {
  height: 1px;
  background: var(--border);
  margin-bottom: 20px;
  position: relative;
}
.foot-line::before {
  content: '';
  position: absolute;
  left: 0; top: -3px;
  width: 40px; height: 7px;
  background: var(--accent);
}
.foot-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
}
.foot-mark {
  font-family: 'Anek Kannada', sans-serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.035em;
  text-transform: lowercase;
  color: var(--fg);
  line-height: 1;
}
.foot-mark span { color: var(--accent); }
.foot-sub {
  margin-top: 6px;
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-subtle);
}
.foot-meta {
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-subtle);
  text-align: right;
  line-height: 1.6;
}

/* =============================================================================
   Animations / responsive
   ============================================================================= */
@media (prefers-reduced-motion: reduce) {
  .id, .mix, .yr {
    animation: none !important;
  }
}

@media (max-width: 560px) {
  .page {
    padding-top: 20px;
    padding-right: max(14px, env(safe-area-inset-right));
    padding-bottom: max(48px, env(safe-area-inset-bottom));
    padding-left: max(14px, env(safe-area-inset-left));
  }
  .id-top { margin-bottom: 20px; }
  .id-body { grid-template-columns: 72px 1fr; gap: 14px; }
  .id-name { letter-spacing: -0.045em; }
  .id-body { margin-bottom: 12px; }
  .pills { gap: 5px; flex-wrap: wrap; }
  /* Each pill stretches to fill the row instead of leaving leftover space —
     they become equal-width capsules rather than 44px circles. */
  .pill {
    flex: 1 1 0;
    width: auto;
    min-width: 36px;
  }
  /* Bring the standard 20×20 glyphs down a tier on mobile so they don't
     swim in the wider capsule. Chameleon (22) and SoundCloud (22×9.6) are
     already at their normalised sizes from the base rules; the :not()
     keeps them out of this shrink. */
  .pill svg { width: 18px; height: 18px; }
  .pill .pill-mask:not(.cham-mask) { width: 18px; height: 18px; }
  .next { padding: 12px; min-height: 78px; gap: 10px; }
  .next-title { font-size: 12px; }

  .mix {
    padding: 12px 12px 12px 12px;
    min-height: 78px;
  }
  /* Narrow screens: slim the artwork column and the station tag so the show
     name gets the width; a third clamp line catches the longest titles
     instead of an ellipsis. */
  .mix-art { width: 72px; }
  /* Same tuck ratio as desktop (~95% of the art column). */
  .mix-body { padding-left: 68px; }
  .mix-title { font-size: 11.5px; -webkit-line-clamp: 3; }
  .mix-meta { font-size: 9px; gap: 6px; }
  .mix-tag { font-size: 7.5px; padding: 4px 3px 2px; padding-right: calc(3px - 0.1em); letter-spacing: 0.1em; }
  .mix-plat { width: 20px; height: 20px; }
  .mix-right { gap: 6px; }

  .mixes.log .mix { grid-template-columns: 1fr 84px; gap: 10px; padding: 10px 12px; }
  .mixes.log .mix-date { display: none; }
  .mixes.log .mix-body { grid-column: 1 / 2; }
  .mixes.log .mix-right { grid-column: 2 / 3; }

  .yr { margin: 14px 0 6px; }
  .foot-row { flex-direction: column; align-items: flex-start; gap: 16px; }
  .foot-meta { text-align: left; }
}

@media (max-width: 400px) {
  .page {
    padding-top: 16px;
    padding-right: max(12px, env(safe-area-inset-right));
    padding-bottom: max(40px, env(safe-area-inset-bottom));
    padding-left: max(12px, env(safe-area-inset-left));
  }
  .id-top .id-freq { display: none; }
  .mix { padding: 10px 10px; min-height: 70px; }
}

