@import url("/assets/fonts/fonts.css");

/* =========================================================================
   Eppu-projekti results site
   =========================================================================

   The E0-E13 element system from docs/design/design_handoff_eput_results_site/,
   rebuilt as named classes. The handoff is a look-and-feel artefact: its copy,
   sample figures and data claims are superseded by the real build. What is
   adopted here is the token set, the type scale, the spacing rhythm and the
   component shapes.

   Two rules from the design that are easy to erode and worth stating:
     - border-radius is 0 everywhere and there are no box-shadows. Depth comes
       from 1px rules and the two grounds. A rounded corner here will look
       borrowed from somewhere else.
     - gold (#D9A521) is for dark grounds ONLY. On paper it measures 2.1:1.
     - the handoff calls #A8801A the paper-safe gold and says it passes AA. It
       does not: measured against this paper it is 3.38:1, which is fine for a
       rule or a bar fill (the 3:1 UI-component threshold) and fails for text.
       So there are two tokens. --gold-ink (#856413, 5.09:1) is the one that
       goes on words; --gold-rule (#A8801A) is for fills, bars and the focus
       ring. Do not use --gold-rule for text.

   The brief's cyan (#90E0EF) is not used: 1.6:1 on paper. If it is ever wanted
   as a state colour on a dark panel, #2C7C8C is the paper-safe version.
   ========================================================================= */

/* ---------- E0: tokens ---------- */
:root {
  --ink: #241A10;
  --ink-2: #3D3021;
  --ink-3: #5C4C36;
  --muted: #7A6A52;
  --muted-2: #8A7A62;

  --paper: #FAF6EF;
  --paper-2: #F1E9DA;
  --paper-3: #F6F0E5;
  --paper-4: #EDE4D3;

  --rule-pale: #E2D7C2;
  --band-pale: #D9C79A;
  --band-pale-2: #C9BBA4;

  --night: #1E150C;
  --night-2: #2A2015;
  --night-3: #231A10;
  --night-rule: #4A3C28;
  --night-rule-2: #3A2E1E;

  --gold: #D9A521;        /* dark grounds only: 2.1:1 on paper */
  --gold-ink: #856413;    /* gold for TEXT on paper: 5.09:1, AA */
  --gold-rule: #A8801A;   /* gold for fills, bars, focus ring: 3.38:1, UI-only */

  --on-dark: #FAF6EF;
  --on-dark-2: #EFE7D9;
  --on-dark-3: #B8A88C;
  --on-dark-4: #B0A088;

  --hair: rgba(36, 26, 16, .14);
  --hair-2: rgba(36, 26, 16, .18);
  --hair-3: rgba(36, 26, 16, .28);

  --display: "Playfair Display", Georgia, "Times New Roman", serif;
  --text: "Source Serif 4", Georgia, "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --wrap: 1180px;
  --gutter: clamp(18px, 4vw, 40px);
  --section-y: clamp(40px, 6vw, 80px);
  --gap: clamp(20px, 3vw, 32px);
  --card-pad: clamp(20px, 2.6vw, 30px);
}

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

html {
  scroll-behavior: smooth;
  /* Anything scrolled to by an anchor clears the top edge rather than sitting
     flush against it. */
  scroll-padding-top: 2rem;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 17px/1.6 var(--text);
  -webkit-text-size-adjust: 100%;
  text-wrap: pretty;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

/* Every number in the project is compared against another number. */
.num, .figures dt, table, .dist__legend b, .shift__nums, .nps__value,
.hours__label, .wd__list b { font-variant-numeric: tabular-nums; }

h1, h2, h3, h4 {
  font-family: var(--display); font-weight: 500; margin: 0 0 .5em;
  /* Two-word headings were breaking as "Rokkipoliisit pamputtaa / taas". */
  text-wrap: balance;
}
h1 { font-size: clamp(40px, 8.5vw, 84px); line-height: 1.02; letter-spacing: -.015em; }
h2 { font-size: clamp(28px, 4vw, 42px); line-height: 1.1; }
h3 { font-size: clamp(20px, 2.4vw, 25px); line-height: 1.16; }
h4 { font-size: 17px; line-height: 1.2; }
p { margin: 0 0 1em; }

.eyebrow {
  font: 600 12px/1.4 var(--text);
  text-transform: uppercase;
  letter-spacing: .2em;
  color: var(--muted);
  margin: 0 0 1.2em;
}
/* Short copy should not wrap at all.
   ------------------------------------------------
   A fixed measure is a rule for PROSE: it stops a paragraph running to 1180px
   and becoming unreadable. Applied to a one-sentence deck it does the opposite
   of its job -- a 65-character sentence sat in a 62ch box and broke into two
   lines with 640px of empty column beside it.

   The fix is simply a measure wide enough to hold the sentence.

   DO NOT reach for width:max-content here. It sizes the box to the sentence,
   which looks identical for left-aligned text and quietly breaks every layout
   it sits in: an auto-sized grid track takes its max-content contribution, so
   a 236-character paragraph made a single-column grid 1889px wide inside a
   390px phone. A max-width alone does the same job and cannot overflow. */
.footnote {
  font-size: 14px; color: var(--muted);
  max-width: 84ch;
}
.sub { color: var(--muted); font-weight: 400; }
.strong { font-weight: 600; }

/* Visually hidden but available to assistive technology. Used heavily: every
   suppressed cell, every chart, every high/low mark carries one. */
.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); }
.section { padding: var(--section-y) 0; border-top: 1px solid var(--hair-2); }
.section__lead {
  color: var(--ink-2);
  /* A deck under a 42px heading carries a longer line than body copy does, so
     the cap is 100ch rather than the 62ch reading measure -- wide enough that
     every one- or two-sentence lead on the page stays unbroken. Anything long
     enough to wrap at this width is a paragraph and should be marked up as one.
     See .footnote for why this is a max-width and not a max-content width. */
  max-width: 100ch;
  /* And when one does wrap, two even lines rather than a full line and a stub. */
  text-wrap: balance;
}
.section--dark { background: var(--night); color: var(--on-dark); border-top: 0; }

/* ---------- focus, skip link, :target ---------- */
:focus-visible {
  outline: 2px solid var(--gold-rule);
  outline-offset: 3px;
}
.section--dark :focus-visible, .gate :focus-visible, .site-footer :focus-visible {
  outline-color: var(--gold);
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--night); color: var(--on-dark);
  padding: 14px 20px; text-decoration: none;
}
.skip:focus { left: 0; }

/* Where the skip link lands, and any other in-page anchor target. Without this
   you jump and have no idea what you jumped to. */
:target { scroll-margin-top: 2rem; }

/* ---------- E12a: pre-launch gate ----------
   Hidden unless the inline script in the <head> has put `is-gated` on <html>,
   which it does only before the launch instant. The page is the default state
   and the holding screen is the exception, so every way this can fail -- script
   blocked, JavaScript off, an instant that has passed -- ends with the results
   visible rather than with a blank page.
   ------------------------------------------------ */
.is-gated #main { display: none; }
.gate {
  position: fixed; inset: 0; z-index: 200;
  background: var(--night); color: var(--on-dark);
  /* none, not grid. The holding screen is the exception; the page is the
     default. Put the `display: grid` here and the gate covers the site
     permanently the moment anything stops setting the class. */
  display: none; place-items: center; text-align: center;
  padding: var(--gutter);
}
.is-gated .gate { display: grid; }
.gate__inner { max-width: 46ch; }
.gate .eyebrow { color: var(--gold); }
.gate__lead {
  font-family: var(--display);
  font-size: clamp(30px, 5.4vw, 54px); line-height: 1.12;
  margin: 0 0 1.2em; position: relative; padding-top: 1.2em;
}
.gate__lead::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 120px; height: 1px; background: var(--gold);
}
.gate__meta { color: var(--on-dark-3); font-size: 15px; margin: 0; }
/* The bypass. It must look and measure exactly like the digit it replaces --
   no colour, no cursor, no underline -- so padding is cancelled by an equal
   negative margin. The padding is there because a 30px glyph is a small target
   for a thumb, which is the whole situation this exists for. */
#gate-key { padding: 12px; margin: -12px; cursor: inherit; }

/* Preview badge. Only ever present when somebody opened a still-gated page with
   the preview word, so it should be impossible to mistake for site furniture. */
.preview-badge {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 150;
  margin: 0; padding: 10px var(--gutter);
  background: var(--gold-rule); color: var(--night);
  font: 600 13px/1.4 var(--text); text-align: center;
  border-top: 2px solid var(--night);
}
@media (min-width: 620px) {
  .preview-badge { left: auto; right: 16px; bottom: 16px; border: 2px solid var(--night); }
}

/* ---------- E1a: hero ----------
   The countdown show's treatment, rebuilt in CSS so it stays responsive: a
   photograph running full bleed, toned sepia and screened with a halftone dot
   grid, with an angled panel laid over one side. In the show that panel is paper
   on a projector; here it is the night ground, because a paper panel on a paper
   page gives the hero nothing to push against.
   ------------------------------------------------ */
.hero {
  position: relative;
  display: flex;
  /* stretch, not center: the angled panel has to run the full height or it
     reads as a floating box dropped on the photo rather than a cut through it. */
  align-items: stretch;
  min-height: clamp(540px, 84vh, 880px);
  background: var(--night);
  overflow: hidden;
  isolation: isolate;
}

.hero__media { position: absolute; inset: 0; z-index: 0; }
.hero__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 44%;
  /* Dimmed hard, because the photograph is now a GROUND rather than a picture
     with a panel beside it. At the old brightness the exposed half competed
     with the type for attention and won. */
  filter: sepia(.6) saturate(.55) contrast(1.06) brightness(.52);
}

/* A full-bleed wash over the whole photograph, densest under the type. This is
   what the phone layout does -- the photo behind everything at low opacity --
   and at 16 inches it reads far better than a photo on one side of a hard edge,
   because no crop has to survive being half-covered. */
.hero__wash {
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(100deg,
    rgba(30, 21, 12, .93) 0%,
    rgba(30, 21, 12, .86) 38%,
    rgba(30, 21, 12, .60) 68%,
    rgba(30, 21, 12, .52) 100%);
  pointer-events: none;
}

/* The halftone screen. A dot grid multiplied over the photo, which is what gives
   the show its printed look -- without it the sepia alone reads as an Instagram
   filter rather than as ink on paper. */
.hero__screen {
  position: absolute; inset: 0;
  background-image: radial-gradient(circle at 50% 50%, rgba(30, 21, 12, .62) 34%, transparent 36%);
  background-size: 4px 4px;
  mix-blend-mode: multiply;
  opacity: .55;
  pointer-events: none;
}
/* Below a phone's pixel density the grid turns to mush; coarsen it. */
@media (max-width: 620px) {
  .hero__screen { background-size: 3px 3px; opacity: .45; }
}
/* One column, so the scrim runs top to bottom rather than across the diagonal
   the wedge cuts on a wide screen. */
@media (max-width: 819px) {
  .hero__wash {
    background: linear-gradient(180deg,
      rgba(30, 21, 12, .84) 0%,
      rgba(30, 21, 12, .74) 50%,
      rgba(30, 21, 12, .88) 100%);
  }
  /* Brighter than the wide layout, because here the scrim is the only thing
     over the photograph -- there is no wedge stacked on it as well. */
  .hero__media img {
    filter: sepia(.6) saturate(.55) contrast(1.06) brightness(.66);
    object-position: 50% 34%;
  }
}

.hero__panel {
  position: relative;
  z-index: 1;
  width: 100%;
  display: flex;
  align-items: center;
  /* No background of its own on a phone: .hero__wash is already the scrim, and
     stacking a second near-opaque layer on top of it took the photograph from
     "behind everything at low opacity" to black. The wedge below is where the
     panel gets a background again. */
  padding: clamp(40px, 8vw, 96px) 0;
}
.hero__inner {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

@media (min-width: 820px) {
  .hero__panel {
    width: min(66%, 1020px);
    /* Translucent, not solid. The wedge is still there -- it is most of why this
       reads as the same family as the countdown show -- but it now deepens the
       photograph rather than hiding half of it. */
    background: linear-gradient(96deg,
      rgba(30, 21, 12, .90) 0%,
      rgba(30, 21, 12, .82) 55%,
      rgba(30, 21, 12, .40) 100%);
    clip-path: polygon(0 0, 100% 0, calc(100% - 130px) 100%, 0 100%);
    padding: clamp(48px, 6vw, 96px) 0;
  }
  .hero__inner {
    /* Pushed to the panel's own right edge, with room kept clear for the rake of
       the diagonal so the last line of text never runs under it. */
    margin: 0 0 0 auto;
    max-width: 760px;
    padding-right: clamp(90px, 8vw, 180px);
    padding-left: var(--gutter);
  }
}

.hero .eyebrow { color: var(--gold); }

.hero__title {
  color: var(--on-dark);
  /* Sized to hold "Eppu Normaali" on one line inside the panel at every width.
     It is a wordmark; broken across two lines it stops being one. */
  font-size: clamp(38px, 4.4vw, 68px);
  line-height: .94;
  letter-spacing: -.02em;
  margin: 0;
}
/* "Projekti" as a subhead under the band's name, not a second line of it. */
.hero__subtitle {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(26px, 3.1vw, 46px);
  line-height: 1;
  color: var(--gold);
  margin: .12em 0 0;
  letter-spacing: .02em;
}

.hero__lead {
  max-width: 44ch;
  margin-top: clamp(22px, 3vw, 34px);
  color: var(--on-dark-2);
  font-size: clamp(16px, 1.7vw, 19px);
}
.hero__lead p { margin: 0 0 .7em; }
.hero__lead p:last-child { margin-bottom: 0; }

.hero__winner {
  margin: clamp(24px, 3vw, 38px) 0 0;
  padding-top: 20px; border-top: 1px solid var(--night-rule);
  display: flex; flex-direction: column; gap: 6px;
  max-width: 44ch;
}
.hero__winner-label {
  font: 600 12px/1 var(--text); text-transform: uppercase;
  letter-spacing: .2em; color: var(--gold);
}
.hero__winner-title {
  font-family: var(--display); font-size: clamp(23px, 3vw, 34px);
  line-height: 1.1; color: var(--on-dark); text-decoration: none;
  text-decoration-color: var(--gold);
}
.hero__winner-title:hover, .hero__winner-title:focus-visible { text-decoration: underline; }
.hero__winner-meta { color: var(--on-dark-3); font-size: 15px; }
.hero__winner-meta b { color: var(--gold); font-family: var(--display); font-size: 1.15em; }

/* ---------- intro ---------- */
/* The banner. Toned like the hero so the page has two photographic bands rather
   than one hero and one snapshot in a box, but held to the reading column: run
   full bleed on a 2160px screen it becomes a 5:1 slot, and a 5:1 slot re-crops
   a photograph that was already cropped once at build time. */
.intro__figure { position: relative; margin: 0 0 clamp(28px, 4vw, 52px); background: var(--night); }
.intro__figure img {
  display: block; width: 100%;
  aspect-ratio: 1600 / 920; max-height: 520px;
  object-fit: cover; object-position: 50% 26%;
  filter: sepia(.5) saturate(.45) contrast(1.08) brightness(.92);
}
.intro__screen {
  position: absolute; inset: 0;
  background-image: radial-gradient(circle at 50% 50%, rgba(30, 21, 12, .62) 34%, transparent 36%);
  background-size: 4px 4px;
  mix-blend-mode: multiply; opacity: .4; pointer-events: none;
}
@media (max-width: 620px) { .intro__screen { background-size: 3px 3px; opacity: .3; } }

/* minmax(0, 1fr), not the implicit `auto` track.
   ------------------------------------------------
   An `auto` grid track is sized by its content's MAX-content contribution, and
   nothing clamps it to the container. One wide child -- a 183-row table inside
   an overflow:auto box, or a paragraph that does not want to wrap -- therefore
   makes the whole column wider than the phone it is on, and every one of these
   grids is a single column below its breakpoint. minmax(0, 1fr) is the track
   that cannot do that. Every multi-column template below already uses it. */
.intro__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); align-items: start; }
.intro__head h2 { margin: 0; }
.intro__copy > p {
  /* The column is already the measure here -- it is one of two in a grid -- so
     a second, narrower cap only made the short paragraph wrap inside it. */
  margin-top: 0; text-wrap: pretty;
}
.intro__links { list-style: none; margin: 1.4em 0 0; padding: 0; }
.intro__links li { border-top: 1px solid var(--hair); }
.intro__links a {
  display: block; padding: 11px 0; min-height: 44px;
  font-weight: 600; font-size: 15px; color: var(--gold-ink);
}
.intro__links a::after { content: " \2197"; color: var(--muted); }
@media (min-width: 820px) {
  /* Heading in one column, the prose in the other: the editorial split the rest
     of the page uses, instead of a photograph competing with a paragraph. */
  .intro__grid { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }
}

/* ---------- E2a: figure row ---------- */
.figures { display: flex; flex-wrap: wrap; gap: 18px 36px; margin: clamp(24px, 3vw, 36px) 0 0; }
.figures > div { border-top: 1px solid var(--ink); padding-top: 10px; min-width: 92px; }
.figures dt { font-family: var(--display); font-size: 28px; line-height: 1; }
.figures dd {
  margin: 6px 0 0; font-size: 11px; text-transform: uppercase;
  letter-spacing: .12em; color: var(--muted);
}

/* ---------- E3a: the song table ---------- */
.tables__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(32px, 4vw, 56px); align-items: start; }
@media (min-width: 1200px) {
  .tables__grid { grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr); }
}

.tables__head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 10px 20px;
  margin-bottom: .3em;
}
.tables__head h2 { margin-bottom: 0; min-width: 0; }
.tables__actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }
/* nowrap only where the two tables sit side by side. There, a wrap dropped the
   actions below the heading in the narrower album column ALONE, so the two
   columns' buttons sat at different heights on the same screen. On one column
   there is nothing to line up with, and forcing nowrap just overflows a phone. */
@media (min-width: 1200px) {
  .tables__head { flex-wrap: nowrap; }
  .tables__actions { flex-wrap: nowrap; flex: 0 0 auto; }
}

/* The album column is a third the width of the songs column, so its two actions
   are icons. Same controls, same order, no text to push the heading around. */
.button-link--icon {
  padding: 8px; width: 40px; justify-content: center;
}
.button-link--icon .icon { margin: 0; }

.button-link {
  display: inline-flex; align-items: center; min-height: 40px;
  padding: 8px 14px; text-decoration: none;
  border: 1px solid var(--ink);
  font: 600 12px/1 var(--text); text-transform: uppercase; letter-spacing: .1em;
}
.button-link:hover { background: var(--ink); color: var(--paper); }
/* The icon sits on the text baseline and takes the link's colour, so it inverts
   with the button on hover instead of staying a stray dark mark. flex:none
   because .button-link is a flex container and an SVG without it gets squeezed
   to a 3px sliver by the neighbouring text. */
.icon { flex: 0 0 auto; width: 14px; height: 14px; }
.button-link .icon { vertical-align: -2px; margin-right: 6px; }
.button-link--quiet { border-color: var(--hair-3); color: var(--ink-3); }
.button-link--quiet:hover { background: var(--ink-3); color: var(--paper); border-color: var(--ink-3); }

/* A grid, not a wrapping flex row. Flex-wrap put "Vuosikymmen" on a second row
   under a half-empty first one, which reads as a broken layout rather than as a
   deliberate one. Three equal columns where there is room, one where there is
   not, and nothing in between that can look accidental. */
.controls {
  display: grid; gap: 14px 18px; align-items: end;
  grid-template-columns: minmax(0, 1fr);
  background: var(--paper-3); border: 1px solid var(--hair-2);
  padding: 16px 18px; margin: 0 0 22px;
}
@media (min-width: 560px) { .controls { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
@media (min-width: 900px) { .controls { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); } }
.controls[hidden] { display: none; }
.field { margin: 0; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field label {
  font: 600 11px/1 var(--text); text-transform: uppercase;
  letter-spacing: .12em; color: var(--muted);
}
.field input, .field select {
  font: 400 16px/1 var(--text); color: var(--ink);
  background: var(--paper); border: 1px solid var(--hair-3);
  padding: 10px 12px; min-height: 44px; border-radius: 0;
  /* width, not min-width: a min-width inside a grid track makes the track
     refuse to shrink and puts the wrap back. */
  width: 100%; min-width: 0;
}
.field--toggle { justify-content: end; }

button {
  font: 600 13px/1 var(--text); color: var(--ink);
  background: var(--paper); border: 1px solid var(--ink);
  padding: 12px 16px; min-height: 44px; cursor: pointer; border-radius: 0;
}
@media (prefers-reduced-motion: no-preference) {
  button, .downloads a, .songtable tbody tr { transition: background-color 120ms ease, color 120ms ease; }
}
button:hover { background: var(--ink); color: var(--paper); }
button[disabled] { opacity: .35; cursor: default; }
button[disabled]:hover { background: var(--paper); color: var(--ink); }

.table-scroll { overflow-x: auto; }
.grid-table { width: 100%; border-collapse: collapse; }
.grid-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--paper); padding: 0;
  border-top: 2px solid var(--ink); border-bottom: 1px solid var(--ink);
  text-align: left; white-space: nowrap;
}
.grid-table thead th button {
  width: 100%; border: 0; background: none; padding: 12px 10px;
  font: 600 12px/1.2 var(--text); text-transform: uppercase; letter-spacing: .14em;
  text-align: inherit; min-height: 44px;
}
.grid-table thead th button:hover { background: var(--paper-2); color: var(--ink); }
/* A heading with an explanation says so. A title attribute with no visible cue
   is a tooltip nobody discovers. The same text is repeated in the legend on the
   full-data pages, because a title is nothing at all on a touch screen. */
.grid-table thead th button[title] { cursor: help; }
.grid-table thead th button[title] > :first-child,
.grid-table thead th button[title]::first-line { text-decoration: none; }
.grid-table thead th button[title] { text-decoration: underline dotted var(--hair-3); text-underline-offset: 4px; }
.grid-table th.num button, .grid-table td.num { text-align: right; }
.grid-table td { padding: 10px; border-bottom: 1px solid var(--hair); vertical-align: baseline; }
.grid-table tbody tr:hover { background: var(--paper-2); }

.caret::after { content: " ↕"; color: var(--band-pale-2); }
th[aria-sort="ascending"] .caret::after { content: " ▲"; color: var(--gold-ink); }
th[aria-sort="descending"] .caret::after { content: " ▼"; color: var(--gold-ink); }

.songtable .rank, .albumtable .rank {
  font-family: var(--display); font-size: 18px; color: var(--gold-ink); white-space: nowrap;
}
.songtable .title { font-family: var(--display); font-size: 19px; }
.songtable .album, .albumtable .album { font-size: 15px; color: var(--ink-3); }
.album__year { color: var(--muted); }
.songtable td.num.strong { font-family: var(--display); font-size: 18px; }

/* The front-page table carries six columns and the full thirteen live on
   /biisit.html. That replaced an in-place column toggle: thirteen columns inside
   a scroll container on a phone is a horizontal maze where you lose the song
   title before reaching the number you came for, and the toggle also pushed the
   rest of the page off the screen. A separate page can be as wide as it likes.
   */

/* The targeted row: arriving from a shared #biisi-112 link. A rule and a tint,
   not a colour alone, and it stays put rather than fading -- somebody who
   followed a link and looked away for ten seconds should still find it. */
.songtable tbody tr.is-target > td {
  background: var(--paper-2);
  border-top: 2px solid var(--gold-rule);
  border-bottom: 2px solid var(--gold-rule);
}
.songtable tbody tr.is-target .rank::before {
  content: "▸ "; color: var(--gold-ink);
}

.empty { padding: 28px 0; font-size: 17px; }
.empty button { margin-left: 10px; }

.pager { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 20px; }
.pager__status { font-size: 14px; color: var(--muted); margin: 0 10px; }
.pager__all { margin-left: auto; }

/* ---------- E4a: albums ---------- */
.tables__side .albumtable { font-size: 15px; }
.tables__side h2 { margin-top: 0; }

/* ---------- pikafaktat ----------
   The second dark band, below the quotes. Same family as Tiesitkö -- night
   ground, gold eyebrow -- but one of its facts carries a short ranked list, so
   it is its own block rather than a third item in that one.
   ------------------------------------------------ */
.quickfacts { padding: var(--section-y) 0; }
.quickfacts .eyebrow { color: var(--gold); }
.quickfacts__list {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(28px, 4vw, 44px);
}
@media (min-width: 760px) { .quickfacts__list { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.quickfact__claim {
  font-family: var(--display); font-size: clamp(21px, 2.6vw, 28px);
  color: var(--on-dark); margin: 0 0 .4em;
}
.quickfact__body { margin: 0; color: var(--on-dark-2); max-width: 52ch; }

.quickfact__songs {
  list-style: none; margin: 18px 0 0; padding: 0;
  border-top: 1px solid var(--night-rule);
}
.quickfact__songs li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--night-rule);
}
/* No underline: the row already has a rule under it, so the two stacked read as
   one doubled line rather than as a link. The list below this one is styled the
   same way -- colour on hover is enough affordance inside a card where every
   title is a link. */
.quickfact__songs a { color: var(--on-dark); text-decoration: none; }
.quickfact__songs a:hover, .quickfact__songs a:focus-visible { color: var(--gold); }
.quickfact__year { color: var(--on-dark-4); font-size: 13px; font-variant-numeric: tabular-nums; }
.quickfact__songs b { color: var(--gold); font-variant-numeric: tabular-nums; }

/* The same claim as ten rows you can count. Small, because it illustrates the
   sentence above it rather than competing with it. */
.quickfact__toplist {
  list-style: none; margin: 18px 0 0; padding: 0;
  border-top: 1px solid var(--night-rule);
  font-size: 13px;
}
.quickfact__toplist li {
  display: grid; grid-template-columns: 1.6rem minmax(0, 1fr);
  align-items: baseline; gap: 10px;
  padding: 5px 0; border-bottom: 1px solid var(--night-rule);
}
.quickfact__rank { color: var(--on-dark-4); font-variant-numeric: tabular-nums; text-align: right; }
.quickfact__toplist a { color: var(--on-dark-3); text-decoration: none; }
.quickfact__toplist a:hover, .quickfact__toplist a:focus-visible { color: var(--gold); }
/* The bold IS the data here: these are the ones that fade. */
.quickfact__toplist .is-fade a { color: var(--on-dark); font-weight: 700; }
.quickfact__note { margin: 10px 0 0; font-size: 12px; color: var(--on-dark-4); }

/* ---------- E5c: Tiesitkö ---------- */
.tiesitko { padding: var(--section-y) 0; }
.tiesitko .eyebrow { color: var(--gold); }
.tiesitko__list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(28px, 4vw, 44px); }
@media (min-width: 760px) { .tiesitko__list { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.tiesitko__claim {
  font-family: var(--display); font-size: clamp(20px, 2.4vw, 26px);
  line-height: 1.25; color: var(--on-dark); margin: 0 0 .5em;
}
.tiesitko__body { color: var(--on-dark-2); margin: 0 0 .6em; }
.tiesitko__song a { color: var(--gold); font-size: 14px; text-decoration-thickness: 1px; }

/* ---------- E5a: findings ---------- */
.findings__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 1px; background: var(--hair-2);
  border: 1px solid var(--hair-2);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
}
.finding { background: var(--paper); padding: var(--card-pad); }
.finding__tag {
  font: 600 11px/1 var(--text); text-transform: uppercase;
  letter-spacing: .14em; color: var(--gold-ink); margin: 0 0 .9em;
}
.finding__head { font-size: 22px; margin: 0 0 .5em; }
.finding__body { margin: 0 0 1em; color: var(--ink-2); }
.finding__song a { font-size: 14px; color: var(--ink-3); }

/* The lead variant the handoff did not have. Six identical tiles is the
   bento-box pattern the brief was trying to avoid, and a grid with no hierarchy
   tells the reader nothing about which fact is worth their attention. */
.finding--lead { background: var(--paper-2); }
.finding--lead .finding__head { font-size: clamp(24px, 2.8vw, 32px); }
.finding--lead .finding__body { font-size: 18px; color: var(--ink); }
@media (min-width: 760px) {
  .finding--lead { grid-column: span 2; }
}

/* ---------- E6a: quotes ---------- */
.quotes__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.quote { border-left: 2px solid var(--hair-3); padding: 4px 0 4px 20px; }
.quote blockquote { margin: 0 0 .8em; }
.quote blockquote p { font-size: 18px; line-height: 1.45; margin: 0; }
.quote__meta { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; margin: 0; }
.quote__author {
  font: 600 12px/1.3 var(--text); text-transform: uppercase; letter-spacing: .1em;
}
/* An unattributed quote must read as a deliberate choice, not a missing field.
   The generator emits an explicit label and marker so this can be styled rather
   than inferred from an empty element. */
.quote__author--anon { color: var(--muted); font-style: italic; text-transform: none; letter-spacing: 0; }
.quote[data-anonymous] { border-left-style: dashed; }
.quote__song { font-size: 14px; color: var(--ink-3); }

/* ---------- E7a: crosstabs ---------- */
.crosstab-wrap + .crosstab-wrap { margin-top: clamp(36px, 5vw, 60px); }
.crosstab { width: 100%; border-collapse: collapse; }
.crosstab th, .crosstab td { padding: 10px 12px; border-bottom: 1px solid var(--hair); }
.crosstab thead th {
  font: 600 12px/1.2 var(--text); text-transform: uppercase; letter-spacing: .12em;
  text-align: right; border-bottom: 1px solid var(--ink); color: var(--muted);
}
.crosstab tbody th { text-align: left; font-weight: 600; font-size: 15px; }
.crosstab td.num { text-align: right; }
.crosstab .total, .crosstab .total-row th, .crosstab .total-row td { font-weight: 600; }
.crosstab .total-row td, .crosstab .total-row th { border-top: 1px solid var(--ink); }
.crosstab .suppressed { color: var(--muted); }
.mark { margin-left: 4px; font-size: .8em; }
.mark-high { color: var(--gold-ink); box-shadow: inset 0 -2px 0 var(--gold-rule); }
.mark-low { box-shadow: inset 0 -2px 0 var(--ink); }

/* Two structures, one shown. A six-column crosstab cannot reflow to a phone
   width, and a CSS-only transpose cannot repeat the row labels inside each
   block. display:none keeps the hidden one out of the accessibility tree, so
   nothing is announced twice. */
.crosstab-blocks { display: none; }
@media (max-width: 700px) {
  .crosstab-wrap .table-scroll { display: none; }
  .crosstab-blocks { display: grid; gap: 18px; }
  .ct-block { border-top: 1px solid var(--ink); padding-top: 10px; }
  .ct-block h4 { margin: 0 0 8px; font-family: var(--text); font-size: 12px;
                 text-transform: uppercase; letter-spacing: .12em; color: var(--muted); }
  .ct-row { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; }
  .ct-row.total { border-top: 1px solid var(--hair); margin-top: 4px; font-weight: 600; }
  .ct-row .k { color: var(--ink-2); }
  .ct-row .v { font-variant-numeric: tabular-nums; }
}

/* ---------- E8b: distributions ---------- */
.dists__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(26px, 3.4vw, 40px); }
/* An explicit column count per breakpoint, which the handoff left open. Six
   charts each with a legend need room to be read, not just to fit. */
@media (min-width: 620px) { .dists__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
@media (min-width: 1000px) { .dists__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.dist { margin: 0; }
.dist figcaption {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font: 600 13px/1.3 var(--text); margin-bottom: 10px;
}
.dist__n { color: var(--muted); font-weight: 400; font-size: 12px; white-space: nowrap; }
.dist__bar {
  display: flex; height: 30px; border: 1px solid var(--hair-3); background: var(--rule-pale);
}
.dist__seg { display: block; height: 100%; }
.dist__seg--0 { background: var(--ink); }
.dist__seg--1 { background: var(--gold-rule); }
.dist__seg--2 { background: var(--band-pale); }
.dist__seg--3 { background: var(--ink-3); }
.dist__seg--4 { background: var(--paper-4); }
.dist__seg--5 { background: var(--muted-2); }
/* Asuinkaupunki has seven answers. A bar whose last segments fall off the end of
   the palette renders as bare background, which reads as "no data" rather than
   "an option nobody remembered to colour". */
.dist__seg--6 { background: var(--band-pale-2); }
.dist__legend { list-style: none; margin: 12px 0 0; padding: 0; font-size: 13px; }
.dist__legend li { display: flex; align-items: baseline; gap: 8px; padding: 3px 0; }
.dist__key { width: 10px; height: 10px; flex: 0 0 10px; border: 1px solid var(--hair-3); }
.dist__label { flex: 1 1 auto; color: var(--ink-2); }
.dist__count { color: var(--muted); font-size: 12px; min-width: 3ch; text-align: right; }

/* ---------- E9b: before / after ---------- */
.shifts__list { list-style: none; margin: 0 0 var(--gap); padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 26px; }
.shift__q { font-weight: 600; margin: 0 0 8px; }
.shift__track {
  position: relative; height: 14px; background: var(--rule-pale);
  border: 1px solid var(--hair-3);
}
.shift__track { margin-bottom: 22px; }
/* The 1-5 ticks. Without them a dot at 57 % of an unlabelled bar is a position,
   not an answer, and the reader has no way to tell 3,3 from 3,9. */
.shift__tick { position: absolute; top: -1px; bottom: -1px; width: 1px; background: var(--hair-2); }
.shift__tick i {
  position: absolute; top: calc(100% + 3px); left: 0; transform: translateX(-50%);
  font: 400 10px/1 var(--text); font-style: normal; color: var(--muted);
}
.shift__bar { position: absolute; top: 5px; height: 2px; background: var(--gold-rule); }
.shift__dot {
  position: absolute; top: 50%; width: 13px; height: 13px; margin: -7px 0 0 -6px;
  border: 2px solid var(--ink); border-radius: 50%;
}
.shift__dot--before { background: var(--paper); }

/* Where the "after" dot used to be. A dot at each end says something moved; an
   arrowhead says which way, which is the entire point of a before/after chart.
   Drawn with borders so it needs no image and inherits no colour by accident. */
.shift__arrowhead {
  position: absolute; top: 50%; width: 0; height: 0;
  border-top: 7px solid transparent; border-bottom: 7px solid transparent;
  margin-top: -7px;
}
.shift__arrowhead--up { border-left: 11px solid var(--ink); margin-left: -1px; }
.shift__arrowhead--down { border-right: 11px solid var(--ink); margin-left: -10px; }

.shift__nums { display: flex; align-items: baseline; gap: 10px; margin: 8px 0 0; font-size: 14px; }
.shift__before { color: var(--muted); }
.shift__move { color: var(--gold-ink); }
.shift__after { font-weight: 600; }
.shift__delta { font-size: 13px; font-variant-numeric: tabular-nums; }
.shift--nousi .shift__delta { color: var(--gold-ink); }
.shift--laski .shift__delta { color: var(--muted); }
.shift__n { margin-left: auto; color: var(--muted); font-size: 12px; }

/* The two end-of-project scores, side by side: neither is a before/after, and
   stacking them under the shifts made the second one look like an afterthought. */
.scores { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 4vw, 44px); border-top: 1px solid var(--ink); padding-top: 22px; }
@media (min-width: 720px) { .scores { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.score__value {
  font-family: var(--display); font-size: clamp(42px, 6vw, 68px); line-height: 1;
  color: var(--gold-ink); margin: 0;
}
.score__label { font-weight: 600; margin: .3em 0 .6em; }

/* ---------- timing ---------- */
.timing__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(32px, 4vw, 56px); }
@media (min-width: 820px) { .timing__grid { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); } }
.hours__chart {
  list-style: none; margin: 0 0 14px; padding: 0;
  display: grid; grid-template-columns: repeat(24, minmax(0, 1fr)); gap: 2px;
  align-items: end; height: 150px;
}
.hours__chart li { display: flex; flex-direction: column; justify-content: end; height: 100%; }
.hours__bar { display: block; background: var(--ink); min-height: 2px; }
.hours__label { font-size: 9px; color: var(--muted); text-align: center; padding-top: 4px; }
/* Every third hour on a phone: 24 labels at 9px is a grey smear. */
@media (max-width: 620px) {
  .hours__chart li:not(:nth-child(3n+1)) .hours__label { visibility: hidden; }
}
.wd__list { list-style: none; margin: 0 0 20px; padding: 0; }
.wd__list li { display: flex; align-items: center; gap: 10px; padding: 5px 0; font-size: 14px; }
.wd__list .k { flex: 0 0 7.5rem; color: var(--ink-2); }
/* The bar needs its OWN track to be a percentage of. As a flex item its width
   resolved against the whole row, so the longest bar (100 %) overflowed and was
   shrunk back to fit while the shorter ones were not -- which drew Sunday, the
   busiest day, as the shortest bar. */
.wd__track { flex: 1 1 auto; min-width: 0; display: block; }
.wd__bar { display: block; height: 10px; background: var(--gold-rule); min-width: 2px; }
.wd__list b { flex: 0 0 auto; font-weight: 600; }

/* What a chart's bars are measured in, said above the chart rather than left to
   be inferred from the shape. */
.chart__scale {
  display: flex; justify-content: space-between; gap: 12px;
  margin: 0 0 10px; font-size: 12px; color: var(--muted);
}
.chart__max { font-variant-numeric: tabular-nums; white-space: nowrap; }
.timing__pairs { margin: 0; }
.timing__pairs > div { display: flex; justify-content: space-between; gap: 16px;
                       border-top: 1px solid var(--hair); padding: 8px 0; }
.timing__pairs dt { color: var(--muted); font-size: 14px; }
.timing__pairs dd { margin: 0; font-weight: 600; font-size: 14px; }

/* ---------- E10a: downloads ---------- */
.raakadata__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); align-items: start; }
@media (min-width: 820px) { .raakadata__grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); } }
/* The prose column is already a measure. A second, narrower cap inside it only
   wrapped the short lines twice over. */
.raakadata p { max-width: 100%; }
/* A grid, not a wrapping flex row: filename, description and size line up down
   the list instead of each row finding its own arrangement. The filename column
   is sized to the longest name so nothing shifts between rows. */
.downloads { list-style: none; margin: 0; padding: 0; }
.downloads a {
  display: grid; align-items: baseline; gap: 2px 16px;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name size" "desc size";
  padding: 12px 8px; min-height: 44px; text-decoration: none;
  border-bottom: 1px solid var(--hair);
}
@media (min-width: 640px) {
  .downloads a {
    grid-template-columns: 12.5rem minmax(0, 1fr) auto;
    grid-template-areas: "name desc size";
  }
}
.downloads a:hover { background: var(--paper-2); color: var(--gold-ink); }
.downloads__name { grid-area: name; font-family: var(--mono); font-size: 14px; }
.downloads__desc { grid-area: desc; font-size: 13px; color: var(--muted); }
.downloads__size { grid-area: size; font-size: 13px; color: var(--muted);
                   text-align: right; font-variant-numeric: tabular-nums; }

/* Nothing to page through, no page controls. The count beside them stays. */
.pager--single { justify-content: flex-start; }

/* ---------- standalone full-data pages ---------- */
.tabs {
  display: flex; gap: 2px; margin: 0 0 22px;
  border-bottom: 1px solid var(--ink);
}
.tabs__tab {
  padding: 9px 18px; min-height: 44px; display: flex; align-items: center;
  font: 600 14px/1 var(--text); text-decoration: none; color: var(--ink-2);
  border: 1px solid var(--hair-3); border-bottom: 0;
  background: var(--paper-2);
}
.tabs__tab:hover { background: var(--paper-3); color: var(--gold-ink); }
/* The open tab joins the rule underneath it rather than sitting on top of it,
   which is the whole visual grammar of a tab strip. */
.tabs__tab.is-current {
  background: var(--paper); color: var(--ink);
  border-color: var(--ink); margin-bottom: -1px; padding-bottom: 10px;
}

/* The index column is 1180px because that is a comfortable measure for prose
   and a six-column table. biisit.html is neither: thirteen columns squeezed
   into the reading column wrap every album title to four lines and push the
   last two columns off the edge into a horizontal scrollbar nobody notices.
   Only that page gets the extra width -- albumit.html has six columns and at
   1560px they drift apart into unreadable stripes. */
.page-full--wide .wrap { max-width: 1560px; }
.page-full .full { padding: clamp(28px, 5vw, 56px) 0 clamp(40px, 6vw, 80px); }
.page-full .full h1 { font-size: clamp(34px, 5vw, 56px); margin-bottom: .2em; }
.page-full .eyebrow a { color: var(--gold-ink); text-decoration: none; }
.page-full .eyebrow a:hover { text-decoration: underline; }
.page-full .section__lead { margin-bottom: 22px; }

.legend { margin-top: 28px; border-top: 1px solid var(--hair-2); padding-top: 14px; }
.legend summary {
  cursor: pointer; font: 600 12px/1 var(--text);
  text-transform: uppercase; letter-spacing: .12em; color: var(--muted);
  padding: 8px 0; min-height: 44px; display: flex; align-items: center;
}
.legend__list { margin: 8px 0 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (min-width: 760px) { .legend__list { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px 32px; } }
.legend__list dt { font-weight: 600; font-size: 14px; }
.legend__list dd { margin: 3px 0 0; font-size: 14px; color: var(--ink-2); max-width: 52ch; }

/* ---------- tooltips ----------
   The native title attribute waits about a second before appearing, cannot be
   styled, and on a touch screen never appears at all. This is the same idea
   built out of a pseudo-element: no delay, the page's own colours, and it
   answers to :focus as well as :hover so a keyboard reaches it.

   Anything with a data-tip gets one. Screen readers do not announce generated
   content, so anywhere the tooltip carries information that is not also in the
   markup, the host element needs an aria-label of its own.
   ------------------------------------------------ */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute; z-index: 60;
  top: calc(100% + 8px); left: 0;
  width: max-content; max-width: min(280px, 60vw);
  padding: 9px 11px;
  background: var(--night); color: var(--on-dark);
  font: 400 13px/1.45 var(--text);
  text-align: left; text-transform: none; letter-spacing: 0; font-weight: 400;
  white-space: normal;
  /* A 1px rule and the dark ground, not a drop shadow: the design system gets
     its depth from rules and the two paper tones, and a soft shadow here is the
     one place on the site that would look borrowed from somewhere else. */
  border: 1px solid var(--ink);
  border-left: 3px solid var(--gold-rule);
  /* display:none, NOT visibility:hidden. A hidden element still takes part in
     layout, so 30 crosstab cells each carrying a 280px tooltip box below them
     grew the scrollable area of .table-scroll and gave every table a scrollbar
     it had no content for -- which scrolled the header row out of sight. */
  display: none;
  pointer-events: none;
}
[data-tip]:hover::after,
[data-tip]:focus::after,
[data-tip]:focus-within::after { display: block; }

/* Anchored left by default. Near the right-hand edge of a table or a chart that
   would push the tooltip out of the scroll container and add phantom scroll
   width, so those flip to the right edge instead. */
.grid-table th:nth-last-child(-n+4)[data-tip]::after,
.grid-table td:nth-last-child(-n+3)[data-tip]::after,
.crosstab td:nth-last-child(-n+2)[data-tip]::after,
.hours__chart li:nth-last-child(-n+8) [data-tip]::after,
.wd__list b[data-tip]::after { left: auto; right: 0; }

/* A tooltip that is the only explanation of a number needs something to hover.
   This is that something: a real button, so it is focusable and announced. */
/* No box. A bordered square beside a heading reads as a control you are meant
   to press; this is a mark you are meant to hover, so it is set as one. */
.tip {
  /* Negative margins cancel the padding, so the hit area is roughly 20x24
     while the glyph still sits tight against its label and the row does not
     grow. A 6px target is unhittable on a touch screen, where there is no
     hover at all and a tap is the only way in. */
  margin: -6px 0 -6px 2px; padding: 6px; min-height: 0; width: auto;
  border: 0; background: none; color: var(--gold-ink);
  font: 600 13px/1 var(--text); cursor: help; vertical-align: 2px;
}
.tip:hover, .tip:focus-visible { background: none; color: var(--ink); }

/* Every song title is a link that opens the card. A full underline on all 183
   of them turns the table into a wall of blue; no affordance at all and nobody
   discovers the card exists. A hairline that firms up on hover is the middle. */
.songlink {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid var(--hair-2);
}
.songlink:hover, .songlink:focus-visible {
  border-bottom-color: var(--gold-rule); color: var(--gold-ink);
}

/* ---------- the song card ----------
   A <dialog>, so the browser handles the top layer, the focus trap, inert
   background and Escape. Everything here is presentation; the behaviour is in
   tables.js and the element is empty in the markup.
   ------------------------------------------------ */
.songcard {
  width: min(520px, calc(100vw - 2 * var(--gutter)));
  max-height: min(86vh, 720px);
  padding: 0;
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  /* The dialog's own margin:auto is what centres it in the top layer. */
  margin: auto;
  overflow: auto;
}
.songcard::backdrop { background: rgba(30, 21, 12, .62); }

.songcard__close {
  position: sticky; top: 0; float: right;
  margin: 0; padding: 10px 14px;
  min-width: 44px; min-height: 44px;
  border: 0; background: none; color: var(--muted);
  font: 400 26px/1 var(--text); cursor: pointer;
}
.songcard__close:hover { color: var(--ink); }

#songcard-content { padding: clamp(22px, 4vw, 34px); }

.songcard__head { border-bottom: 1px solid var(--hair-2); padding-bottom: 16px; margin-bottom: 18px; }
.songcard__rank {
  margin: 0 0 .5em; font: 600 11px/1 var(--text);
  text-transform: uppercase; letter-spacing: .14em; color: var(--gold-ink);
}
.songcard__title { font-size: clamp(24px, 4vw, 32px); margin: 0 0 .25em; }
.songcard__meta { margin: 0; color: var(--muted); font-size: 15px; }

/* Two columns of label/value pairs. A definition list because that is what it
   is, and grid because dl's own layout cannot put dt and dd on one line. */
.songcard__figures, .songcard__optional {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 0; margin: 0;
}
.songcard__figures dt, .songcard__optional dt {
  padding: 9px 0; border-top: 1px solid var(--hair-2); color: var(--ink-2); font-size: 15px;
}
.songcard__figures dd, .songcard__optional dd {
  padding: 9px 0; border-top: 1px solid var(--hair-2); margin: 0;
  text-align: right; font-variant-numeric: tabular-nums; font-weight: 600;
}
.songcard__figures dt:first-of-type, .songcard__figures dd:first-of-type { border-top: 0; }

.songcard__optional-head {
  margin: 26px 0 0; font: 600 11px/1.4 var(--text);
  text-transform: uppercase; letter-spacing: .14em; color: var(--muted);
}
/* The n behind an optional average, never hidden and never smaller than this:
   the whole point of printing it is that the average alone would mislead. */
.songcard__n { font-weight: 400; color: var(--muted); font-size: 13px; }

.songcard__credits {
  margin: 14px 0 22px; font-size: 15px; color: var(--ink-2);
}
.songcard__sep { color: var(--muted); }

/* A grid, not a flex row: two equal halves whatever is written in them. As
   flex items the buttons sized to their own labels, so "Seuraava" got a wider
   box than "Edellinen" and the pair looked misaligned. */
/* The album's own songs, listed inside its card. Condensed on purpose: this is
   an index into the song table, not a second copy of it. */
.albumcard__tracks {
  list-style: none; margin: 8px 0 0; padding: 0;
  display: grid; gap: 0;
}
.albumcard__tracks li {
  display: grid; grid-template-columns: 2.4rem minmax(0, 1fr) auto;
  align-items: baseline; gap: 10px;
  padding: 8px 0; border-top: 1px solid var(--hair-2);
  font-size: 15px;
}
.albumcard__rank {
  text-align: right; color: var(--gold-ink);
  font-size: 13px; font-variant-numeric: tabular-nums;
}
.albumcard__title { color: inherit; text-decoration: none; border-bottom: 1px solid var(--hair-2); }
.albumcard__title:hover, .albumcard__title:focus-visible {
  border-bottom-color: var(--gold-rule); color: var(--gold-ink);
}
.albumcard__avg { font-weight: 600; font-variant-numeric: tabular-nums; }

/* The whole row opens the card, so the whole row has to look like it does. */
.grid-table tbody tr[id] { cursor: pointer; }

/* The recording's own facts, below the ratings. Same rows as the figures above
   so the card reads as one table rather than three. */
.songcard__detail dt, .songcard__detail dd { font-size: 14px; }
.songcard__detail dd { font-weight: 400; }

.songcard__listen { display: flex; flex-wrap: wrap; gap: 8px; margin: 22px 0 0; }
.songcard__listen .button-link { font-size: 11px; padding: 7px 12px; min-height: 36px; }

/* One of the twelve curated quotes, where the song has one. */
.songcard__quote {
  margin: 26px 0 0; padding: 16px 0 0;
  border-top: 1px solid var(--hair-2);
}
.songcard__quote blockquote { margin: 0; }
.songcard__quote p {
  margin: 0; font-size: 15px; line-height: 1.55; color: var(--ink-2);
  text-wrap: pretty;
}
.songcard__quote figcaption {
  margin-top: 10px;
  font: 600 11px/1 var(--text); text-transform: uppercase; letter-spacing: .12em;
  color: var(--muted);
}
.songcard__quote-anon { font-style: italic; text-transform: none; letter-spacing: 0; font-size: 13px; }

.songcard__nav {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; margin: 26px 0 0;
  border-top: 1px solid var(--hair-2); padding-top: 18px;
}
.songcard__step { min-width: 0; text-align: center; }

/* ---------- footer ---------- */
.site-footer {
  background: var(--night); color: var(--on-dark-3);
  padding: clamp(32px, 4vw, 52px) 0; font-size: 14px;
}
.site-footer p { margin: 0 0 .5em; }
.site-footer__title {
  font-family: var(--display); font-size: clamp(20px, 2.4vw, 26px);
  color: var(--on-dark); margin-bottom: 1.2em;
}
.site-footer__dates {
  font: 600 12px/1 var(--text); text-transform: uppercase; letter-spacing: .18em;
  color: var(--gold); white-space: nowrap;
}
.site-footer .footnote { color: var(--on-dark-4); max-width: 62ch; }
.site-footer__note {
  border-top: 1px solid var(--night-rule); padding-top: 16px; margin: 0;
  font-size: 13px; line-height: 1.65;
}
.site-footer__note b { color: var(--on-dark-3); font-weight: 600; }
