/* ═══════════════════════════════════════════════════════════════
   DORUA — landing page
   Direction: TERRA / MACHINA

   Motion contract: js/signals.js writes four normalised values onto
   :root every frame. Everything below reacts to them declaratively.

     --kick    0→1  spikes on a transient, decays ~120ms
     --flash   0→1  downbeat only, ~70ms. Drives the invert.
     --energy  0→1  smoothed overall level
     --bass    0→1  smoothed low band
   ═══════════════════════════════════════════════════════════════ */

@font-face {
  font-family: 'Clash Display';
  src: url('../assets/fonts/ClashDisplay-Variable.woff2') format('woff2-variations');
  font-weight: 200 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Satoshi';
  src: url('../assets/fonts/Satoshi-Variable.woff2') format('woff2-variations');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --void:   #0A0A0B;
  --bone:   #EDE6D6;
  --clay:   #A64B28;
  --indigo: #1B2A4A;
  --volt:   #C6F547;

  --bone-dim: #8C877D;

  --display: 'Clash Display', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --body:    'Satoshi', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Signals — defaults keep the page composed before JS runs. */
  --kick: 0;
  --flash: 0;
  --energy: 0;
  --bass: 0;

  --gut: clamp(1.25rem, 4vw, 3.5rem);

  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--void);
  color: var(--bone);
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  min-height: 100svh;
  overflow-x: hidden;
  overscroll-behavior: none;
}

a {
  color: inherit;
  text-decoration: none;
}

::selection { background: var(--volt); color: var(--void); }

:focus-visible {
  outline: 2px solid var(--volt);
  outline-offset: 4px;
}

/* ── Atmosphere ────────────────────────────────────────────────── */

/* Warm depth behind everything: clay low, indigo high. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(110% 70% at 10% 108%, color-mix(in srgb, var(--clay) 34%, transparent) 0%, transparent 62%),
    radial-gradient(90% 60% at 88% -8%, color-mix(in srgb, var(--indigo) 46%, transparent) 0%, transparent 60%);
  opacity: calc(0.55 + var(--energy) * 0.45);
  transition: opacity 90ms linear;
}

.dust {
  position: fixed;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.grain {
  position: fixed;
  inset: -150%;
  z-index: 6;
  pointer-events: none;
  opacity: 0.07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grainShift 0.7s steps(6) infinite;
  will-change: transform;
}
@keyframes grainShift {
  0%   { transform: translate3d(0, 0, 0); }
  16%  { transform: translate3d(-4%, 3%, 0); }
  33%  { transform: translate3d(3%, -5%, 0); }
  50%  { transform: translate3d(-6%, -2%, 0); }
  66%  { transform: translate3d(5%, 4%, 0); }
  83%  { transform: translate3d(-2%, -6%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* One-frame invert on the downbeat. Difference blend = true invert,
   far cheaper than filter: invert() on a full-page subtree. */
.flash {
  position: fixed;
  inset: 0;
  z-index: 7;
  pointer-events: none;
  background: var(--bone);
  mix-blend-mode: difference;
  opacity: var(--flash);
}

/* ── Stage ─────────────────────────────────────────────────────── */

.stage {
  position: relative;
  z-index: 3;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  gap: clamp(1.5rem, 4vh, 3rem);
  padding: var(--gut);
}

/* ── Meta bar ──────────────────────────────────────────────────── */

.meta {
  text-align: center;
  font-size: clamp(0.6875rem, 1.05vw, 0.8125rem);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

.meta__role { margin: 0; color: var(--bone); }
.meta__dash { color: var(--clay); padding: 0 0.15em; }

/* ── Wordmark ──────────────────────────────────────────────────── */

.hero {
  /* Centred in the free space rather than bottom-anchored. Sitting on
     the release block made the page read bottom-heavy. */
  align-self: center;
  display: flex;
  flex-direction: column;
  gap: clamp(0.9rem, 2.2vh, 1.6rem);
}

.wordmark {
  margin: 0;
  font-family: var(--display);
  /* js/wordmark.js measures the real ink bounds and sets these two so
     the D's left edge and the A's right edge land exactly on the page
     margins. Everything else on the page sits at those same margins,
     so the whole layout aligns to the letterforms rather than to
     invisible glyph boxes.

     The clamp is the no-JS fallback: close, but not optically flush. */
  font-size: var(--wm-size, clamp(3.25rem, 26vw, 46rem));
  margin-left: var(--wm-nudge, 0px);
  line-height: 0.82;
  letter-spacing: -0.035em;
  white-space: nowrap;
  display: flex;
  align-items: center;

  /* The breathing. Clash Display's weight axis is 200–700.
     Base is set at the resting weight the wordmark should hold when
     silent — bass is 0 until something is playing, so this value is
     what people see most of the time. */
  font-variation-settings: 'wght' calc(545 + var(--bass) * 155);
}

.wm {
  position: relative;
  display: inline-block;
  color: var(--bone);
}

/* Load reveal: letters snap in on a grid. Never fade.
   Gated on .js — the hidden state must not exist for a visitor whose
   JS is off, or the reveal never fires and the page reads as blank. */
.js .wm {
  clip-path: inset(0 0 100% 0);
  transform: translate3d(0, 0.08em, 0);
}
.is-revealed .wm {
  /* NOT inset(0). line-height 0.82 makes the element box shorter than
     the glyphs, so a zero inset clips whatever rides past it — it
     sheared the bottom off the A's right leg. Overshoot the box so the
     finished state never touches the letterforms. */
  clip-path: inset(-40% -10% -40% -10%);
  transform: translate3d(0, 0, 0);
  transition:
    clip-path 420ms steps(5, end) var(--d, 0ms),
    transform 420ms cubic-bezier(0.16, 1, 0.3, 1) var(--d, 0ms);
}

/* Duotone transient split — two offset copies, additive on black.
   Restrained: 3px maximum, and only while the kick is decaying. */
.wm::before,
.wm::after {
  content: attr(data-ch);
  position: absolute;
  inset: 0;
  mix-blend-mode: screen;
  pointer-events: none;
  opacity: calc(var(--kick) * 0.85);
}
.wm::before {
  color: var(--clay);
  transform: translate3d(calc(var(--kick) * -3px), 0, 0);
}
.wm::after {
  color: var(--indigo);
  transform: translate3d(calc(var(--kick) * 3px), 0, 0);
}

/* ── Ø — the mark, and the play button ─────────────────────────── */

.disc {
  --disc: 0.70em;
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  /* Buttons don't inherit type. Without this, every em below resolves
     against the 13px UA default and the Ø renders at 7px. */
  font: inherit;
  line-height: 1;
  width: var(--disc);
  height: var(--disc);
  margin: 0 0.045em;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  border-radius: 50%;
}
.js .disc {
  transform: scale(0.72);
  opacity: 0;
  transition: transform 520ms cubic-bezier(0.16, 1, 0.3, 1) 160ms,
              opacity 300ms linear 160ms;
}
.is-revealed .disc { transform: scale(1); opacity: 1; }

.disc__ring,
.disc__grooves,
.disc__slash {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
}

/* Record edge. Doubles as the Ø's bowl. */
.disc__ring {
  /* Matched to Clash Display's stem weight at the wordmark's optical
     size. A thinner ring makes the Ø read as a different typeface. */
  border: 0.108em solid var(--bone);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--volt) 90%, transparent);
  transition: border-color 220ms linear, box-shadow 220ms linear;
}

/* Grooves. Invisible at rest so the Ø reads as a letter; once it
   turns, the counter fills in and it becomes a record. */
.disc__grooves {
  inset: 0.108em;
  background: repeating-radial-gradient(
    circle at 50% 50%,
    transparent 0 2.5%,
    color-mix(in srgb, var(--bone) 15%, transparent) 2.5% 3.1%
  );
  opacity: 0;
  transition: opacity 320ms linear;
}

/* Centre label. Sells the platter more than any amount of motion. */
.disc__grooves::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 27%;
  height: 27%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--clay);
  opacity: 0;
  transition: opacity 320ms linear;
}

/* Cue light. The only volt on the mark, and it's the size of a pea. */
.disc__grooves::after {
  content: '';
  position: absolute;
  top: 7%;
  left: 50%;
  width: 0.034em;
  height: 0.034em;
  margin-left: -0.017em;
  border-radius: 50%;
  background: var(--volt);
  opacity: 0;
  transition: opacity 320ms linear;
}

/* The slash. At rest it's the Ø. Playing, it's the tonearm. */
.disc__slash::before {
  content: '';
  position: absolute;
  top: 50%;
  left: -3%;
  width: 106%;
  height: 0.072em;
  margin-top: -0.036em;
  background: var(--clay);
  transition: background-color 220ms linear;
}
.disc__slash {
  transform: rotate(-38deg);
  transform-origin: 50% 50%;
}

/* Fetching and decoding the clip. Breathes the ring rather than
   spinning a spinner — the mark shouldn't turn until the record does. */
.disc.is-loading { cursor: progress; }
.disc.is-loading .disc__ring {
  animation: discWait 1.1s ease-in-out infinite;
}
@keyframes discWait {
  0%, 100% { border-color: var(--bone); }
  50%      { border-color: var(--clay); }
}

/* Hover is a hint, not an event. On a mark this large, flipping the
   slash to accent floods a third of the viewport with lime. */
.disc:hover .disc__grooves { opacity: 0.28; }
.disc:hover .disc__ring { border-color: #FFFFFF; }

/* Playing: 1.8s per revolution — a literal 33⅓ rpm.
   The mark stays bone and clay. Volt does two small jobs: the cue
   light, and a thin ring that pulses on the kick. Flooding it with
   accent turns the Ø into a road sign. */
.disc.is-playing .disc__slash {
  animation: spin 1.8s linear infinite;
}
.disc.is-playing .disc__grooves {
  opacity: 1;
  animation: spin 1.8s linear infinite;
}
.disc.is-playing .disc__grooves::before,
.disc.is-playing .disc__grooves::after { opacity: 1; }
.disc.is-playing .disc__ring {
  box-shadow: 0 0 0 calc(var(--kick) * 0.014em) color-mix(in srgb, var(--volt) 65%, transparent);
}

@keyframes spin {
  from { transform: rotate(-38deg); }
  to   { transform: rotate(322deg); }
}

/* Without JS the Ø does nothing, so don't advertise it. */
.hero__cue { display: none; }
.js .hero__cue { display: block; }

.hero__cue {
  margin: 0;
  text-align: center;
  font-size: clamp(0.6875rem, 1.05vw, 0.8125rem);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--bone-dim);
  transition: opacity 300ms linear;
}
.hero__o { color: var(--clay); }
.is-playing-page .hero__cue { opacity: 0; }

/* ── Release ───────────────────────────────────────────────────── */

/* ── Release ───────────────────────────────────────────────────── */

.release {
  border-top: 1px solid color-mix(in srgb, var(--bone) 16%, transparent);
  padding-top: clamp(1rem, 2.2vh, 1.5rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.45rem;
}

.release__label {
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  font-size: clamp(0.75rem, 1vw, 0.875rem);
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--volt);
}

/* Live indicator. Does more for "out now" than the words do. */
.release__dot {
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: var(--volt);
  flex: 0 0 auto;
  animation: pulseDot 2.4s ease-in-out infinite;
}
@keyframes pulseDot {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}

/* DORUA is the remixer, so the original credit sits above and quiet —
   present for anyone looking, never competing with the title. */
.release__artists {
  margin: 0;
  font-size: clamp(0.8125rem, 1vw, 0.9375rem);
  font-weight: 500;
  text-transform: uppercase;
  color: var(--bone-dim);
}

/* Tracked out by js/wordmark.js until this line measures exactly as
   wide as the title beneath it. Two lines set to the same measure read
   as one designed block; two lines at arbitrary widths read as an
   accident.

   The negative right margin cancels the trailing letter-space CSS adds
   after the final character — without it the ink sits half a space left
   of centre and the measured width overshoots. */
.fitline {
  display: inline-block;
  white-space: nowrap;
  letter-spacing: var(--fit-ls, 0.2em);
  margin-right: calc(-1 * var(--fit-ls, 0.2em));
}

.release__title {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(1.05rem, 1.9vw, 1.55rem);
  font-weight: 600;
  line-height: 1.1;
  /* Uppercased in CSS, not in the markup, so the title stays properly
     cased for search results and screen readers. Caps also need air
     rather than the negative tracking mixed-case wanted. */
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.release__title em {
  font-style: normal;
  font-weight: 400;
  color: var(--bone-dim);
}

/* Outlined buttons, not inline links. As bare text on the far right
   these read as a footnote — the one thing on the page that should
   convert needs to look pressable. */
.release__stores {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  margin-top: 0.5rem;
}

.store {
  padding: 0.85em 1.6em;
  border: 1px solid color-mix(in srgb, var(--bone) 32%, transparent);
  font-size: clamp(0.6875rem, 1vw, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--bone);
  transition: background-color 200ms cubic-bezier(0.2, 0, 0, 1),
              color 200ms cubic-bezier(0.2, 0, 0, 1),
              border-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.store:hover {
  background: var(--volt);
  border-color: var(--volt);
  color: var(--void);
}

/* ── Footer ────────────────────────────────────────────────────── */

.foot__nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  font-size: clamp(0.6875rem, 1.05vw, 0.8125rem);
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.foot__nav a {
  position: relative;
  padding-bottom: 0.25em;
  transition: color 180ms cubic-bezier(0.2, 0, 0, 1);
}
.foot__nav a::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--volt);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 260ms cubic-bezier(0.2, 0, 0, 1);
}
.foot__nav a:hover { color: var(--volt); }
.foot__nav a:hover::after { transform: scaleX(1); }
.foot__sep { color: var(--clay); }

/* ── Narrow viewports ──────────────────────────────────────────── */

@media (max-width: 620px) {
  .stage { gap: 1.5rem; }
  /* No-JS fallback only — the optical fit overrides this via --wm-size.
     The gutter is a far bigger share of a phone's width, so the same vw
     size that fits a desktop overruns the right edge here. */
  .wordmark { font-size: var(--wm-size, clamp(3.25rem, 24.5vw, 12rem)); }
  .release { flex-direction: column; align-items: stretch; }
  .release__stores { width: 100%; }
  .store { flex: 1 1 0; text-align: center; padding-inline: 0.75em; }
}

/* Short landscape phones: stop the wordmark eating the viewport. */
@media (max-height: 520px) and (orientation: landscape) {
  .wordmark { font-size: clamp(2.5rem, 11vh, 6rem); }
}

/* ── Reduced motion ────────────────────────────────────────────────
   A strobing page without this is a genuine photosensitivity hazard.
   Everything that flickers, drifts or scrolls stops. What remains is
   a still composition that still reads as finished — not a
   stripped-down apology.                                            */

@media (prefers-reduced-motion: reduce) {
  .grain { animation: none; opacity: 0.05; }
  .flash { display: none; }
  .dust  { display: none; }
  .release__dot { animation: none; opacity: 1; }
  .disc.is-loading .disc__ring { animation: none; border-color: var(--clay); }

  .wordmark { font-variation-settings: 'wght' 560; }
  .wm::before, .wm::after { display: none; }
  /* Must match .js specificity (0,2,0) or the hidden state wins. */
  .js .wm { clip-path: none; transform: none; transition: none; }
  .js .disc { opacity: 1; transform: none; transition: none; }

  .disc.is-playing .disc__slash,
  .disc.is-playing .disc__grooves { animation: none; }
  .disc.is-playing .disc__grooves { opacity: 1; }

  body::before { opacity: 1; transition: none; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
