/* One sheet of the player's design system (tickets/T19-room-for-two.md).
   style.css keeps the fonts, the tokens and the reset; every area has its
   own file from there, linked in index.html in this order — the cascade is
   the order of the <link>s, not of a bundler. A ticket works in its sheet.
   No @import: it serialises the requests, which is the cost being avoided. */

/* ---------- What has been on: the wheel (tickets/T16-the-wheel.md) ----------
   The ring the desk turns while it searches, with eleven records on it instead
   of fifty-four: a fan of spines seen from well above, the one on the air at
   the top when it opens. Drawn by vinyl.js on the canvas; the page carries the
   two things type does better — the hours on the spokes, and the card above.

   The list of tiles is still the truth of the view (T11): the browser scrolls
   it, and the loop reads its scrollLeft to wind the wheel. With the scene up
   the tiles are hit areas and nothing else; without it, they are the view. */
.tracks { --sleeve: clamp(96px, 14vw, 132px); display: flex; flex-direction: column; align-items: center; width: 100%; flex: 1; min-height: 0; }
.tracks[hidden] { display: none; }

/* The card of the record at the top of the fan (tickets/T20, T22). A centred
   pile and nothing else: the record turning, the two lines of credit, the
   stickers the desk read off it, the heart. Every distance breathes with the window,
   because the card's whole job is to sit clear of the fan below it. The hour
   is not here — it is out on the record's own spoke. */
.pick {
  --gap: clamp(8px, 1.6vh, 12px);
  display: flex; flex-direction: column; align-items: center;
  margin-top: calc(clamp(8px, 3vh, 40px) + var(--drop, 0px));   /* --drop: a tall window's spare room, crate.js */
  text-align: center;
  animation: surface 1.2s var(--ease-soft) both;
}
.pick[hidden] { display: none; }   /* an author rule beats the UA's [hidden], so it is spelled out */
/* A record that HAS been on is its sleeve — square, still, the artwork as the
   desk holds it. The one that IS on is the record itself, out of the sleeve
   and turning, whether or not this browser is the one playing it (T22). One
   box, two states: the size never changes, so nothing under it moves. */
.pick__art {
  display: grid; place-items: center;
  position: relative;
  /* 152 at the 800 of the desktop frame and under, as drawn; past it the
     record grows with the window — a big or a tall screen left it a coaster
     in an empty column (23/10). On a tall narrow one the width caps it. */
  width: max(clamp(104px, 23vh, 152px), min(19vh, 40vw, 360px)); aspect-ratio: 1;
  margin-bottom: 12px;
  border-radius: var(--radius-sleeve);   /* the home's sleeve, to the pixel (T140) */
  overflow: hidden;
  background: var(--control);
}
.pick__img { width: 100%; height: 100%; object-fit: cover; }
.pick__img[hidden] { display: none; }

/* On the air: the same record vinyl.js paints in 3D, in two dimensions and at
   its proportions — grooves, the cover on a label 0.5/1.48 of the radius, the
   radio's blue under it when the desk has no sleeve, the spindle hole. It
   turns at 33⅓: 1.8 s a revolution, the speed the scene spins its own.
   The sweeps of light are what make the turn READABLE — a ring of grooves is
   rotationally symmetric, and a record with no cover on its label showed a
   blue dot standing perfectly still. */
.pick__art.is-now {
  border-radius: 50%;
  background:
    conic-gradient(#0000 0deg, #ffffff1c 38deg, #0000 96deg, #0000 190deg, #ffffff12 224deg, #0000 274deg),
    radial-gradient(circle at 50% 50%, var(--primary) 0 17%, #0000 17%),
    repeating-radial-gradient(circle at 50% 50%, #141417 0 1px, #050506 1px 2.5px),
    #0b0b0d;
  box-shadow: 0 0 0 1px #ffffff1a;
  animation: spin 1.8s linear infinite;
}
/* On the night's ground a black record vanishes: a faint white halo sets it
   off (T126 — the sleeve under the hand on the home has the same). */
:root[data-theme="dark"] .pick__art.is-now { box-shadow: 0 0 0 1px #ffffff1a, 0 0 64px 4px rgb(255 255 255 / 0.07); }
.pick__art.is-now .pick__img { width: 34%; height: 34%; border-radius: 50%; }
.pick__art.is-now::after {   /* the spindle hole, over whatever is on the label */
  content: ""; position: absolute; width: 4%; height: 4%;
  border-radius: 50%; background: #0b0b0d;
}
@keyframes spin { to { transform: rotate(1turn); } }
/* Two rows of the same type, in the frame's 24-tall boxes: the artist is not
   the title's caption, it is the other half of the credits. */
.pick__title, .pick__artist {
  max-width: 32ch; height: 24px;
  font-size: 12px; line-height: 24px; letter-spacing: -0.035em;
  text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The stickers, and the room they take whether or not the desk has read this
   one: an unanalysed record would otherwise pull the whole fan up by 26 px as
   the wheel turned through it. */
.pick__moods {
  display: flex; flex-wrap: wrap; justify-content: center; align-content: flex-start;
  gap: 6px; min-height: 26px; max-width: min(88vw, 420px);
  margin-top: var(--gap);
}
.pick__like { display: grid; place-items: center; width: 24px; height: 24px; margin-top: var(--gap); color: var(--ink); opacity: 0.4; transition: color 0.15s, opacity 0.15s; }
.pick__like > .icon { grid-area: 1 / 1; }
@media (hover: hover) { .pick__like:hover { opacity: 1; } }
.pick__like .icon--on, .pick__like[aria-pressed="true"] .icon--off { display: none; }
.pick__like[aria-pressed="true"] .icon--on { display: block; }
.pick__like[aria-pressed="true"] { color: var(--primary); opacity: 1; }

/* A badge is a pill, not a key: the only radius of the house is for things you
   push, and a tag is read. 10px, like every label of the column. */
.badge {
  display: inline-flex; align-items: center; height: 24px;   /* the JAM pill's height: what is read, 24 (T64) */
  padding: 0 10px; border: 1px solid var(--ink-muted); border-radius: 999px;
  font-size: 10px; font-weight: 500; line-height: 16px; letter-spacing: -0.035em;
  text-transform: uppercase; text-box: trim-both cap alphabetic;
}
.badge--mood { background: var(--ink); color: var(--bg); border-color: var(--ink); }
/* A record the room put on (T124): the Jam's own blue, on its outline. */
.badge--jam { color: var(--primary); border-color: var(--primary); }

/* The hours, one per spoke, laid on the fan from the geometry vinyl.js hands
   back — in the viewport's own frame, so no box has to match the canvas's. */
.spokes { position: fixed; inset: 0; z-index: 1; pointer-events: none; }
.spoke {
  position: absolute;
  display: flex; align-items: center; gap: 6px;
  font-size: 10px; font-weight: 500; line-height: 16px; letter-spacing: -0.035em;
  text-transform: uppercase; color: var(--ink);
  white-space: nowrap; opacity: 0.45;
  transition: opacity 0.3s ease, color 0.3s ease;
}
/* The record being read: its hour is the one that is lit. This is what the
   card's own hour line used to do, given back to the fan (T22). */
.spoke.is-on { opacity: 1; }
/* …and so is the one under the hand (tickets/T27): the fan is a set of keys
   since T26, and a key answers the pointer. The hour is the answer — there is
   nothing else on a record that could light without repainting the canvas.
   Desktop only, like every other hover in the player. */
@media (hover: hover) {
  .spoke.is-aimed { opacity: 1; }
  /* The hand goes on the ROW and not only on the column: the canvas takes no
     pointer (`#vinyl` is pointer-events: none), so the element under the
     cursor anywhere over the fan is the band below it — and the band says
     `cursor: default` on purpose, so that eleven records do not promise a grab
     (T22). It keeps saying it; it just stops saying it over a record. */
  .more.is-aimed, .more.is-aimed .crate__row { cursor: pointer; }
}
/* An hour is a time; *On air* is a state, and the house says a state with a
   led (frame 1454:2081, the same dot as STEREO in the bar). One spoke has it
   and the ten others have nothing to light. */
/* What comes next is a ghost on the fan (T104): its sleeve is half there, and
   so is its word — lit, it still reads quieter than an hour that happened. */
.spoke--ahead { opacity: 0.25; }
.spoke--ahead.is-on { opacity: 0.6; }
@media (hover: hover) { .spoke--ahead.is-aimed { opacity: 0.6; } }
.tile--ahead .tile__art { opacity: 0.4; }
.spoke > .led { display: none; }
.spoke--now > .led { display: block; background: var(--primary); }
.crate__row.is-3d ~ .spokes, .spokes:has(> *) { display: block; }
/* No scene, no fan: the tiles carry their own hours instead. */
.crate__row:not(.is-3d) ~ .spokes { display: none; }

/* The wheel's own band: the whole bottom of the window is the surface one
   turns it from — a scroll area over the fan, invisible while the fan is up. */
.crate { position: relative; flex: 1; align-self: stretch; min-height: 0; }
/* The band under the card. It is not dragged: the wheel, the trackpad, the
   keyboard and a finger are what turn the fan, and a hand cursor over eleven
   records promised a grab that was taken away (T22). Each record is still a
   key — the browser's own pointer says so, over the tile and nowhere else. */
.crate__row {
  position: absolute; inset: 0;
  cursor: default;
  scroll-snap-type: x mandatory;
  display: flex; align-items: flex-end;
  /* Every tile can come to the centre: the centred one is the one at the top of
     the fan — and the padding is read off the SAME step as the tiles, or the
     oldest record can no longer reach it and scrollLeft: 0 stops meaning "the
     oldest" (crate.js reads the turn off the scroll, tickets/T36). */
  --slot: var(--sleeve);
  padding-inline: calc(50% - var(--slot) / 2);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  list-style: none;
  touch-action: pan-x;   /* a finger across turns the wheel; a finger up and down belongs to the column, not to the fan */
}
.crate__row::-webkit-scrollbar { display: none; }
.crate__row.is-hand { scroll-snap-type: none; }      /* a hand pushing the fan from a trackpad is not fought for every pixel; the rest snaps */
.crate__row.is-gliding { scroll-snap-type: none; }   /* and the same while a notch carries it to the next record: mandatory snap cancels a scroll that is still on its way */
.crate__row:focus { outline: none; }
.tile { position: relative; flex: none; width: var(--slot); scroll-snap-align: center; }
.tile__key { position: relative; display: block; width: 100%; }
.tile__art {
  display: block; width: 100%; aspect-ratio: 1;
  object-fit: cover;
  background: var(--control);
  transition: opacity 0.6s ease;
}
.tile__when {
  display: block; height: 20px; margin-top: 12px;
  font-size: 10px; font-weight: 500; line-height: 16px; letter-spacing: -0.035em;
  text-transform: uppercase; text-align: center; color: var(--ink); opacity: 0.5;
  text-box: trim-both cap alphabetic;
}
.tile--now .tile__when { opacity: 1; }   /* one colour for an hour, here as on the card and the spokes: what is on the air is the one that is not faded */
/* The fan has it: the tiles are the scroll surface, and nothing else. */
.crate__row.is-3d .tile__art, .crate__row.is-3d .tile__when { opacity: 0; }
/* …and with the fan up a tile is a hit area a long way from the record it
   stands for, so the pointer is not given to it: a click belongs to the record
   under the finger, which crate.js reads off the fan itself (T26). The row
   still scrolls — a tile never did that, it only gives its width — and the
   keyboard still has every key: pointer-events is not focus. */
.crate__row.is-3d .tile { pointer-events: none; }
.tile__key:focus-visible { border-radius: var(--radius); }   /* the ring itself is the house's (style.css) */
/* …except with the fan up, where the tile is an invisible hit area and the
   record it stands for is drawn on the canvas, a long way from it: the ring
   floated in the middle of the window, round nothing. The fan says it
   instead — the focused record comes up out of it (lift(), vinyl.js). */
.is-keyed .crate__row.is-3d .tile__key:focus-visible { outline: none; }

@media (max-width: 640px) {
  .tracks { --sleeve: clamp(80px, 22vw, 110px); }
}

/* A thumb crosses the whole wheel in one go (tickets/T36).
   With the fan up a tile shows nothing and takes no pointer: all it still does
   is give its width, and that width is the gearing between the finger and the
   turn. Geared to a sleeve it was 825 px of travel in a 311 px row — two and a
   half screens, three drags. Ten steps of 8vw is 80vw: one sweep from edge to
   edge, and a flick does it without trying.
   Touch only: the wheel counts detents and the trackpad pushes pixel for pixel
   (T22), and neither has anything to gain from a short step. And with no scene
   the tiles ARE the view, so they keep the sleeve. */
/* T82: a sweep is half a screen, not a whole one — a thumb from the edge
   reaches the middle, rarely the far side, and twelve records at 8vw (88vw)
   stopped one or two short of the end. The WHOLE wheel is now 50vw, however
   many records the history holds: crate.js writes their number of steps.
   24/10: still one or two short — the two to come (T104) are steps too, and
   iOS's mandatory snap shortens a flick's coast. 36vw: from the record on the
   air to the oldest is ~115 px of thumb on a 390 phone, ~12 px a record. */
@media (pointer: coarse) {
  .crate__row.is-3d { --slot: calc(36vw / var(--steps, 10)); }
}

@media (prefers-reduced-motion: reduce) {
  body::after { animation: none; }
  .btn, .btn::before, .btn::after, .btn > *, .hit { transition: none; }
  .track__scroll.is-scrolling { animation: none; }
  .page, .menu, .step > *, .vinyl, .crate, .pick, .pick__art { animation: none; transition: none; }   /* …including the record's own turn */
  .tile__art, .spoke { transition: none; }
  .roll > .icon { animation: none; }
  .roll > .icon:first-child { opacity: 0.4; }
}
/* Evil Morty (T150): its still sleeve IS a record, head on it — on the air the
   card does not shrink it to a label, it turns it whole. */
.pick__art.is-now .pick__img[src$="evil-morty-sleeve.webp"] { width: 100%; height: 100%; }
