/* ============================================================================
   Memoirs of a Sinner — 18 Down   |   custom showcase site  (shared styles)
   Modern dark look. This one file controls the whole site's colours & layout,
   so tweaks live in one place. Used by every page (index.html, about.html, …).
   ============================================================================ */

/* ---- typefaces: self-hosted, no third-party request at runtime ------------
   Served from this stylesheet's own folder, so the same rule works on the home
   page and five levels down in blog/YYYY/MM/DD/slug/ without any HTML change.
   Each face is split latin / latin-ext: the browser fetches latin-ext only if
   a page actually uses an accented character, so the common cost is ~93 KB.
   Fonts are SIL Open Font Licence -- redistributable in a public repo.        */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;          /* one variable file covers the whole range */
  font-display: swap;
  src: url("fonts/inter-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;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/inter-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;
}

:root {
  /* ---- surface ---------------------------------------------------------- */
  /* The band ends are pushed further apart than the old flat palette: at the
     original #2c2c2e/#141416 the diagonal was technically present and
     effectively invisible, which is not what was asked for. */
  /* Chosen by Charl and the user 2026-08-17, sampled in GIMP. Do not "improve"
     the contrast of these two: the reason earlier values kept being invisible
     was a browser extension repainting the page flat #181a1b, not the range. */
  --bg-top:    #282828;   /* the lit middle of the sweep */
  --bg-bot:    #181818;   /* both corners it fades to    */
  /* ---- ink -------------------------------------------------------------- */
  --text:      #d7d7dc;
  --heading:   #f5f5f7;
  /* Lifted from #8b8b93 2026-08-17. Measured against the LIGHTEST diagonal
     band (#3c3c45), the worst case a reader can land on: the old value gave
     3.23:1, below the 4.5:1 minimum for normal text -- and it was already
     under at 4.12:1 on the flat background this replaced. #a6a6af gives
     4.52:1. Body and headings were never in question (7.6:1 and 10:1). */
  --muted:     #a6a6af;

  /* ---- accent: ONE colour, every tint derived from it -------------------
     Previously eight hand-copied rgba(143,208,224,...) values. Changing the
     accent now means editing one line, and none of them can drift apart.     */
  --accent:    #8fd0e0;   /* soft blue-teal */
  --accent-hi: color-mix(in oklab, var(--accent) 55%, white);
  --accent-06: color-mix(in oklab, var(--accent) 6%,  transparent);
  --accent-10: color-mix(in oklab, var(--accent) 10%, transparent);
  --accent-16: color-mix(in oklab, var(--accent) 16%, transparent);
  --accent-24: color-mix(in oklab, var(--accent) 24%, transparent);
  /* The ambient glow is a deeper teal than --accent on purpose: at 10-17%
     over near-black, the accent itself goes grey and reads as haze.        */
  --glow:      #79b8c9;

  /* ---- panels: two levels, so hover has somewhere to go ----------------- */
  --panel:     rgba(255,255,255,0.035);
  --panel-2:   rgba(255,255,255,0.062);
  --panel-brd: rgba(255,255,255,0.085);
  /* A one-pixel lit top edge. This is what stops a dark card reading as a
     flat grey rectangle -- it suggests a light source above the page.        */
  --sheen:     inset 0 1px 0 rgba(255,255,255,0.055);

  /* ---- elevation -------------------------------------------------------- */
  /* Four steps, and every raised block sits on one of them at REST. Before
     2026-08-17 only the player and the photo had a visible shadow: the cards
     were on --sh-1, which at 2px blur is invisible on this background, and
     the link rows had none at all until hovered. So the page read as one flat
     sheet with a single object floating on it. Rest --sh-2, hover --sh-3 is
     the pair for a card; the hero photo is the largest object and runs one
     step higher, --sh-3 to --sh-4. --sh-1 is kept for small inline chips. */
  --sh-1: 0 1px 2px rgba(0,0,0,0.30);
  --sh-2: 0 12px 32px -12px rgba(0,0,0,0.60);
  --sh-3: 0 34px 76px -24px rgba(0,0,0,0.75);
  --sh-4: 0 52px 104px -28px rgba(0,0,0,0.85);

  /* ---- radius: four steps, applied by role rather than by eye ----------- */
  --r-sm:   8px;    /* inputs, chips, small controls */
  --r-md:  14px;    /* cards and panels              */
  --r-lg:  20px;    /* hero media, player, lightbox  */
  --r-pill: 999px;

  /* ---- type ------------------------------------------------------------- */
  /* ONE family for the whole site. A display face was tried and rejected:
     a serif is not wanted here, and a second family means type that changes
     between pages. Display size does its work through WEIGHT and negative
     tracking instead, which is what --font-display now carries. */
  --font-body:    "Inter", -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: var(--font-body);

  /* One easing curve everywhere. The browser default (ease) decelerates too
     late and is the main reason stock hover states feel sluggish.          */
  --ease:      cubic-bezier(0.2, 0.7, 0.2, 1);

  --wrap:      1120px;
}

/* Declares the site as dark. Chrome's auto-dark-mode leaves a page alone once
   it sees this, and form controls, scrollbars and the canvas behind the body
   all pick the dark palette instead of a white flash on load. */
:root { color-scheme: dark; }

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  min-height: 100vh;
  color: var(--text);
  font-family: var(--font-body);
  font-optical-sizing: auto;
  font-size: 1.05rem;
  line-height: 1.72;
  /* Inter ships proper typographic defaults behind feature flags; without
     these it renders straight quotes and lining figures like a 2015 UI. */
  font-feature-settings: "cv05" 1, "cv11" 1, "ss03" 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* ONE diagonal gradient across the page: dark in the bottom-left corner,
     lightest through the middle, dark again in the bottom-right. Not a
     repeating one -- that was tried and is not what was wanted.
     Fixed attachment matters more here than it did for bands: without it the
     gradient stretches over the whole document, and the archive runs to 616
     entries, so the ramp would spread across thousands of pixels and read as
     flat. Anchored to the viewport, it stays one clean sweep at any length. */
  background:
    radial-gradient(90% 42% at 50% 0%, color-mix(in oklab, var(--glow) 6%, transparent) 0%, transparent 60%),
    linear-gradient(45deg, var(--bg-bot) 0%, var(--bg-top) 50%, var(--bg-bot) 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

a { color: var(--accent); text-decoration: none; }
a:hover, a:focus-visible { color: var(--accent-hi); text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 3px; border-radius: 3px; }
/* `height: auto` is not optional next to `max-width`. An <img> carrying width
   and height attributes gets them as real CSS values; max-width then shrinks
   the WIDTH to fit the column while the height stays pinned at the attribute,
   and the picture is stretched vertically. The hero was rendering 457x643
   from a 857x643 file. Anything that sets its own height explicitly, or uses
   object-fit: cover, is more specific and is unaffected. */
img { max-width: 100%; height: auto; display: block; }

.wrap { width: min(var(--wrap), 92%); margin: 0 auto; }

/* ---- header / nav (reused on every page) ---- */
header.site { text-align: center; padding: 3.4rem 1rem 1.4rem; }
.wordmark {
  margin: 0; color: var(--heading);
  font-family: var(--font-display);
  /* Display type wants NEGATIVE tracking. The old +0.015em was set for a UI
     sans at 3rem; at this size it reads as spaced-out and dated. */
  font-weight: 700; letter-spacing: -0.035em; line-height: 1.04;
  font-size: clamp(2rem, 5.2vw, 3.1rem);
  text-wrap: balance;
}
/* On interior pages the wordmark is a link home, so it was picking up the
   accent colour and rendering teal there but white on the home page. The
   wordmark is identity, not navigation -- it keeps its colour either way. */
/* The band's own mark, in place of the words. White ink on transparency, so
   it sits on the gradient without a plate behind it. Sized to roughly the
   presence the text heading had, and capped at 88% so a narrow phone does not
   run it edge to edge. The height comes from the aspect ratio, which is why
   the <img> carries its real width and height: without them the page reflows
   when the logo arrives. */
.logo { width: clamp(210px, 30vw, 340px); max-width: 88%; height: auto; margin-inline: auto; }
.wordmark a { color: inherit; text-decoration: none; display: block; }
.wordmark a:hover, .wordmark a:focus-visible { color: var(--accent-hi); text-decoration: none; }

.tagline { max-width: 33rem; margin: 1rem auto 0; color: var(--muted); font-size: 0.96rem;
           text-wrap: pretty; }
nav.primary {
  margin-top: 1.6rem; display: flex; gap: 1.7rem; justify-content: center; flex-wrap: wrap;
  font-size: 0.82rem; letter-spacing: 0.16em; text-transform: uppercase;
}
nav.primary a { color: var(--text); }
nav.primary a[aria-current="page"] { color: var(--accent); }
nav.primary a:hover { color: var(--accent-hi); text-decoration: none; }
.rule { width: min(var(--wrap), 92%); height: 1px; margin: 1.3rem auto 0; border: 0;
        background: linear-gradient(90deg, transparent, var(--panel-brd), transparent); }

/* ---- hero: photo (left)  +  cycling player (right) ---- */
.hero { display: grid; grid-template-columns: 1fr 1fr; gap: 2.6rem; align-items: center;
        padding: 2.8rem 0 1rem; }
.hero-photo img {
  width: 100%; border-radius: var(--r-sm);
  border: 1px solid var(--panel-brd);
}
.hero-photo figcaption { margin-top: 0.7rem; color: var(--muted); font-size: 0.8rem; font-style: italic; text-align: center; }

/* the cycling song player */
.player { background: var(--panel); border: 1px solid var(--panel-brd); border-radius: var(--r-lg);
          padding: 1.3rem 1.3rem 1.5rem; box-shadow: var(--sheen), var(--sh-2);
          transition: box-shadow .25s var(--ease); }
/* Deliberately no lift on this one. The Spotify player is inside it, so the
   pointer is usually on its way to a control -- moving the target underfoot. */
.player:hover { box-shadow: var(--sheen), var(--sh-3); }
.player-eyebrow { font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); }
.player-title { margin: 0.35rem 0 1rem; color: var(--heading); font-size: 1.5rem; font-weight: 500; }
.player-frame { border-radius: var(--r-lg); overflow: hidden; background: #101014; min-height: 152px; }
.player-frame iframe { width: 100%; height: 152px; border: 0; display: block; }
.player-controls { display: flex; align-items: center; gap: 0.9rem; margin-top: 1rem; }
.player-btn {
  width: 2.4rem; height: 2.4rem; border-radius: 50%; flex: none;
  background: transparent; border: 1px solid var(--panel-brd); color: var(--text);
  font-size: 1.1rem; cursor: pointer; line-height: 1; transition: border-color .15s, color .15s, background .15s;
}
.player-btn:hover { border-color: var(--accent); color: var(--accent-hi); background: var(--accent-06); }
.player-count { color: var(--muted); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.player-dots { display: flex; gap: 0.4rem; margin-left: auto; }
.player-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--panel-brd); border: 0; padding: 0; cursor: pointer; }
.player-dot[aria-current="true"] { background: var(--accent); }

/* ---- section heading ---- */
.section-label { color: var(--heading); font-family: var(--font-display); font-weight: 650;
                 font-size: clamp(1.4rem, 2.7vw, 1.75rem); letter-spacing: -0.022em;
                 margin: 3.4rem 0 1.3rem; padding-bottom: 0.6rem;
                 border-bottom: 1px solid var(--panel-brd); }

/* ---- streaming link cards ---- */
.links { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.85rem; }
.links a { display: flex; align-items: center; gap: 0.7rem; padding: 0.95rem 1.1rem; color: var(--text);
           background: var(--panel); border: 1px solid var(--panel-brd); border-radius: var(--r-md);
           box-shadow: var(--sheen), var(--sh-2);
           transition: border-color .2s var(--ease), background .2s var(--ease),
                       transform .2s var(--ease), box-shadow .2s var(--ease); }
.links a:hover { text-decoration: none; color: var(--heading); border-color: var(--accent-24);
                 background: var(--panel-2); transform: translateY(-3px);
                 box-shadow: var(--sheen), var(--sh-3); }
.links .dot { width: 0.55rem; height: 0.55rem; border-radius: 50%; background: var(--accent); flex: none; }
.links .go { margin-left: auto; color: var(--muted); font-size: 0.85rem; }

/* ---- the home page's six doors ----
   Three across on a desktop, two on a tablet, one on a phone. Each tile is a
   16:9 picture with its caption underneath; object-fit: cover means a
   replacement picture of a slightly different shape still fills its tile
   rather than letterboxing. The whole tile is the link, so the target is the
   picture and not just the words. */
.doors {
  list-style: none; margin: 2.6rem 0 1rem; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.6rem;
}
.doors a {
  display: block; color: var(--text); text-decoration: none;
  border-radius: var(--r-md); overflow: hidden;
  background: var(--panel); border: 1px solid var(--panel-brd);
  box-shadow: var(--sheen), var(--sh-2);
  transition: transform .2s var(--ease), box-shadow .2s var(--ease),
              border-color .2s var(--ease);
}
.doors img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block;
  /* A hairline around the picture itself, so a dark-cornered photo still has
     an edge instead of bleeding into the tile. An `outline` pulled inward,
     NOT a border: a border would push the picture 1px smaller and, with the
     tile clipping its corners, would vanish on three sides. An inset
     box-shadow is the other obvious answer and it does not work here --
     browsers paint an image over its own inset shadow. */
  outline: 1px solid rgba(255,255,255,0.14);
  outline-offset: -1px;
  transition: transform .45s var(--ease), outline-color .2s var(--ease);
}
.doors span {
  display: block; padding: 0.85rem 1rem 0.95rem; text-align: center;
  font-size: 0.92rem; letter-spacing: 0.01em; color: var(--muted);
  transition: color .2s var(--ease);
}
.doors a:hover, .doors a:focus-visible {
  transform: translateY(-4px); border-color: var(--accent-24);
  box-shadow: var(--sheen), var(--sh-3);
}
/* the picture pushes in slightly under the pointer, inside its own frame */
.doors a:hover img, .doors a:focus-visible img {
  transform: scale(1.04); outline-color: rgba(255,255,255,0.26);
}
.doors a:hover span, .doors a:focus-visible span { color: var(--heading); }

@media (max-width: 900px) { .doors { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .doors { grid-template-columns: 1fr; gap: 1.2rem; } }
@media (prefers-reduced-motion: reduce) {
  .doors a, .doors img, .doors span { transition: none; }
  .doors a:hover { transform: none; }
  .doors a:hover img { transform: none; }
}

/* ---- videos: click-to-play, so YouTube is never contacted until asked ----
   What sits on the page is a picture and a button. Google gets no request and
   sets no cookie for a visitor who does not watch, and the home page does not
   pay ~1 MB of player code it may never need. site.js swaps in the real player
   on click, from youtube-nocookie.com. A plain <iframe> here would have loaded
   for everyone: this block is near the top, so loading="lazy" saves nothing. */
.videos { display: grid; gap: 1.5rem; }
.video { margin: 0; }
.video-shot {
  display: block; width: 100%; padding: 0; cursor: pointer;
  position: relative; overflow: hidden;
  background: var(--panel); border: 1px solid var(--panel-brd);
  border-radius: var(--r-md); box-shadow: var(--sh-3);
  transition: box-shadow .25s var(--ease), border-color .2s var(--ease);
}
.video-shot img { width: 100%; display: block; }
.video-shot:hover, .video-shot:focus-visible { box-shadow: var(--sh-4); border-color: var(--accent-24); }
/* the play badge */
.video-play {
  position: absolute; inset: 0; margin: auto;
  width: 4.6rem; height: 4.6rem; border-radius: var(--r-pill);
  background: var(--accent-16); border: 1px solid var(--accent-24);
  backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  transition: transform .2s var(--ease), background .2s var(--ease);
}
.video-play::after {
  content: ""; width: 0; height: 0;
  border-left: 1.35rem solid var(--heading);
  border-top: 0.86rem solid transparent;
  border-bottom: 0.86rem solid transparent;
  /* Nudged right because a triangle's visual mass sits left of its box:
     geometrically centred, it reads as off-centre. */
  margin-left: 0.34rem;
}
.video-shot:hover .video-play, .video-shot:focus-visible .video-play {
  transform: scale(1.08); background: var(--accent-24);
}
/* the real player, once swapped in */
.video-frame {
  width: 100%; aspect-ratio: 16 / 9; display: block;
  border: 1px solid var(--panel-brd); border-radius: var(--r-md);
  box-shadow: var(--sh-3);
}
.video figcaption { margin-top: 0.7rem; color: var(--muted); font-size: 0.85rem;
                    font-style: italic; text-align: center; }
@media (prefers-reduced-motion: reduce) {
  .video-shot, .video-play { transition: none; }
  .video-shot:hover .video-play { transform: none; }
}

/* ---- lower row: subscribe + archives ---- */
.lower { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-top: 3rem; }
.card { background: var(--panel); border: 1px solid var(--panel-brd); border-radius: var(--r-md);
        padding: 1.5rem; box-shadow: var(--sheen), var(--sh-2);
        transition: transform .2s var(--ease), box-shadow .2s var(--ease),
                    border-color .2s var(--ease); }
.card:hover { transform: translateY(-3px); border-color: var(--accent-24);
              box-shadow: var(--sheen), var(--sh-3); }
.card h3 { margin: 0 0 0.9rem; color: var(--heading); font-size: 0.76rem; letter-spacing: 0.16em;
           text-transform: uppercase; font-weight: 600; }
.card p { margin: 0 0 1rem; color: var(--muted); font-size: 0.9rem; }
.subs-field { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.subs-field input { flex: 1 1 9rem; min-width: 0; padding: 0.6rem 0.75rem; color: var(--text);
                    background: rgba(0,0,0,0.28); border: 1px solid var(--panel-brd); border-radius: var(--r-sm); font: inherit; font-size: 0.9rem; }
.subs-field input::placeholder { color: #6d6d74; }
.subs-field button { padding: 0.6rem 1.1rem; color: #0f1416; background: var(--accent); border: 0;
                     border-radius: var(--r-sm); font: inherit; font-weight: 600; font-size: 0.9rem; cursor: pointer; }
.subs-field button:hover { background: var(--accent-hi); }
.arch-list { columns: 2; column-gap: 1.2rem; list-style: none; margin: 0; padding: 0; font-size: 0.9rem; }
.arch-list li { margin-bottom: 0.35rem; }
.arch-list a { color: var(--text); }
.arch-list a:hover { color: var(--accent-hi); text-decoration: none; }
.arch-list .more { color: var(--muted); font-style: italic; }

/* ---- footer ---- */
footer.site { width: min(var(--wrap), 92%); margin: 3.4rem auto 0; padding: 2.2rem 0 2.8rem;
              border-top: 1px solid var(--panel-brd); text-align: center; color: var(--muted); font-size: 0.87rem; }
footer.site p { margin: 0 0 0.5rem; }
footer.site .credit { font-size: 0.8rem; opacity: 0.85; }

/* ---- generic article/prose (about, posts) ---- */
.prose { width: min(46rem, 92%); margin: 0 auto; padding: 1rem 0 2rem; }
.prose h1 { color: var(--heading); font-family: var(--font-display); font-weight: 700;
            letter-spacing: -0.032em; line-height: 1.1; text-wrap: balance;
            font-size: clamp(1.9rem, 4.2vw, 2.5rem); }
.prose h2 { color: var(--heading); font-family: var(--font-display); font-weight: 650;
            letter-spacing: -0.022em; margin-top: 2.4rem; font-size: 1.45rem;
            text-wrap: balance; }
.prose a { color: var(--accent); }

/* ============================================================================
   Multi-page additions (about / images / music / links) — GitHub-custom build.
   Everything below layers on top of the shared look above; interior pages reuse
   the same header, footer and colour variables.
   ============================================================================ */

/* ---- slim preview banner ---- */
/* UNUSED since 2026-08-17 and kept on purpose. The markup came out of all six
   hand-authored pages when the site moved to Charl's own domain, where "not
   yet the live site" would have been untrue. Paste the <div> back into a page
   to show a preview notice again; the styling is still here waiting. */
.preview-banner {
  background: var(--accent-06);
  border-bottom: 1px solid var(--panel-brd);
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.5;
  text-align: center;
  padding: 0.55rem 1rem;
}
.preview-banner strong { color: var(--accent); font-weight: 600; }

/* ---- interior page intro (about / images / music / links headers) ---- */
.page-intro { text-align: center; padding: 1.4rem 1rem 0.4rem; }
.page-intro .eyebrow {
  font-size: 0.74rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--accent);
  margin: 0 0 0.6rem;
}
.page-intro h1 {
  margin: 0; color: var(--heading); font-family: var(--font-display);
  font-weight: 700; letter-spacing: -0.035em; line-height: 1.08;
  font-size: clamp(2rem, 4.6vw, 2.9rem); text-wrap: balance;
}
.page-intro p.lead { max-width: 40rem; margin: 0.9rem auto 0; color: var(--muted); font-size: 0.98rem; }

/* ---- prose (about) refinements ---- */
.prose p { margin: 0 0 1.25rem; text-wrap: pretty; }
/* The opening drop cap is "18" -- the name of the project, and two characters.
   This was ::first-letter, which can only ever take ONE character, so it set
   the 1 alone and left the 8 in the body text. A pseudo-element cannot be made
   to span two, so the digits are wrapped in a span in about.html instead. */
.prose .dropcap {
  float: left; color: var(--accent); font-weight: 300; font-size: 3.3rem;
  line-height: 0.8; letter-spacing: -0.02em; padding: 0.35rem 0.6rem 0 0;
}
.prose figure.portrait { margin: 0 0 1.8rem; }
/* Centre any figure image narrower than the text column. `img` is display:block
   globally and max-width:100% only CAPS a width -- it never stretches one -- so
   hero.jpg (640px intrinsic) sat flush left in a 736px column with all the slack
   on its right, reading as misaligned against the paragraphs below it. Centring
   rather than stretching: 640px is the only copy of that portrait, and scaling
   it up to fill the measure would soften the one image of Charl on the page. */
.prose figure img { margin-inline: auto; }
.prose figure.portrait img { border-radius: var(--r-md); border: 1px solid var(--panel-brd); box-shadow: var(--sh-2); }
.prose figure.portrait figcaption { margin-top: 0.6rem; color: var(--muted); font-size: 0.8rem; font-style: italic; text-align: center; }
.prose .pull {
  margin: 2.2rem 0; padding: 0.2rem 0 0.2rem 1.3rem; border-left: 2px solid var(--accent);
  color: var(--heading); font-size: 1.2rem; font-weight: 300; font-style: italic; line-height: 1.55;
}

/* ---- music page: grid of all song players ---- */
.song-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.3rem; margin: 1.8rem 0 1rem; padding: 0; list-style: none;
}
.song-card { background: var(--panel); border: 1px solid var(--panel-brd); border-radius: var(--r-lg);
  padding: 1.1rem 1.1rem 1.2rem; box-shadow: var(--sheen), var(--sh-1);
  transition: border-color .2s var(--ease), transform .2s var(--ease), box-shadow .2s var(--ease); }
.song-card:hover { border-color: var(--accent-24); transform: translateY(-3px);
                   box-shadow: var(--sheen), var(--sh-2); }
.song-card .track-no { font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); }
.song-card h3 { margin: 0.2rem 0 0.8rem; color: var(--heading); font-size: 1.12rem; font-weight: 500; }
.song-card .embed { border-radius: var(--r-md); overflow: hidden; background: #101014; }
.song-card .embed iframe { width: 100%; height: 152px; border: 0; display: block; }

/* ---- generic centred section blurb ---- */
.blurb { width: min(46rem, 92%); margin: 2.4rem auto 0; text-align: center; color: var(--muted); }
.blurb .accent { color: var(--accent); }

@media (max-width: 780px) {
  .hero { grid-template-columns: 1fr; }
  .links, .lower { grid-template-columns: 1fr; }
  .arch-list { columns: 1; }
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } * { transition: none !important; } }

/* ============================================================================
   CUSTOM-ONLY FLOURISHES — motion the $4 WordPress plan cannot run.
   This is what makes the fully-custom build feel different, not just look it.
   ============================================================================ */

/* bolder, more cinematic hero */
.wordmark { font-size: clamp(2.3rem, 5.8vw, 3.8rem); letter-spacing: -0.04em; }
.hero { position: relative; padding-top: 1.4rem; }
/* The drifting ambient glow that used to sit here is GONE. It was a flourish
   from the flat-background mock-ups, and it is the reason the diagonal
   gradient showed on every page except this one: a big blurred light wash,
   painted above the body background, sitting exactly where the gradient's
   light band runs. The home page had three background layers where every
   other page has one. Consistency across pages beats the flourish. */
/* The shadow belongs on the FIGURE, not on the img inside it. This element
   clips its children (overflow: hidden, so the ken-burns zoom stays inside the
   rounded corners), and a child's box-shadow is drawn outside the child and
   therefore clipped away with everything else. The photo carried --sh-3 for
   months and cast nothing: measured 2026-08-17, the background 2px to its
   right was the same value 44px out, where the player card beside it falls off
   properly. An element's OWN shadow is not clipped by its own overflow. */
.hero-photo { position: relative; overflow: hidden; border-radius: var(--r-md);
              box-shadow: var(--sh-3); transition: box-shadow .25s var(--ease); }
.hero-photo:hover { box-shadow: var(--sh-4); }
.hero-photo img { animation: kenburns 26s ease-in-out infinite alternate; will-change: transform; }

/* animated equaliser beside "NOW CYCLING" — a little music signature */
.eq { display: inline-flex; gap: 3px; align-items: flex-end; height: 12px; margin-left: 9px; vertical-align: middle; }
.eq i { width: 3px; height: 100%; background: var(--accent); border-radius: 2px; transform-origin: bottom;
        animation: eqBar 900ms ease-in-out infinite; }
.eq i:nth-child(2){ animation-delay: 150ms } .eq i:nth-child(3){ animation-delay: 300ms }
.eq i:nth-child(4){ animation-delay: 450ms } .eq i:nth-child(5){ animation-delay: 220ms }

/* page-load entrance (pure CSS, runs on load) */
.reveal-load { opacity: 0; animation: riseIn .7s cubic-bezier(.2,.7,.2,1) forwards; }
.reveal-load.d1{ animation-delay:.05s } .reveal-load.d2{ animation-delay:.18s }
.reveal-load.d3{ animation-delay:.32s } .reveal-load.d4{ animation-delay:.48s }

/* scroll reveal (JS adds .has-reveal to <html>, then .is-in per section) */
.reveal { transition: opacity .7s ease, transform .7s ease; }
.has-reveal .reveal { opacity: 0; transform: translateY(24px); }
.has-reveal .reveal.is-in { opacity: 1; transform: none; }

@keyframes riseIn   { from { opacity:0; transform: translateY(20px); } to { opacity:1; transform:none; } }
@keyframes kenburns { from { transform: scale(1); } to { transform: scale(1.07); } }
@keyframes eqBar    { 0%,100% { transform: scaleY(.3); } 50% { transform: scaleY(1); } }

@media (prefers-reduced-motion: reduce) {
  .hero-photo img, .eq i { animation: none !important; }
  .reveal-load, .has-reveal .reveal { animation: none !important; opacity: 1 !important; transform: none !important; }
}

/* The footer is a Contact button and the copyright line, nothing else
   (Charl, 2026-08-17). Spaced away from the page above it so the button does
   not read as part of the last thing on the page. */
.foot-cta { margin: 0 0 1.1rem; }

/* Subscribe call-to-action. Replaced the old email field, which had no name
   attribute and therefore threw away whatever the visitor typed. Until the
   newsletter exists this is a plain mailto — crude, but it actually reaches him. */
.subs-cta { margin: 0.9rem 0 0; }
.btn {
  display: inline-block;
  padding: 0.6rem 1.1rem;
  color: #0f1416;
  background: var(--accent);
  border-radius: var(--r-sm);
  font-weight: 600;
}
/* Restate the dark label on hover. Without it the general `a:hover` rule up at
   line 138 wins -- it is a class-plus-pseudo-class selector where `.btn` is a
   bare class -- and repaints the text --accent-hi, which is exactly what the
   background is becoming here. Pale on pale: the button went blank on hover. */
.btn:hover, .btn:focus-visible { color: #0f1416; background: var(--accent-hi); text-decoration: none; }
.arch-list .arch-when { color: var(--muted); font-size: 0.82rem; white-space: nowrap; }
