/* 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. */

/* ---------- More: the page under, a column of destinations up ----------
   The card's phone menu (bestmate-card, .menu), here on every width, opened
   from a third key. The page does not leave: it sinks under a blur, inert,
   and a centred column takes the whole width — the bar on the same lines as
   the page's, the flow's name on the left, the cross on the right. */
:root {
  --ease-soft: cubic-bezier(0.22, 1, 0.36, 1);
  /* A THING ARRIVING LANDS; A THING LEAVING GETS GOING AND GOES. --ease-soft
     is an arrival — it is nearly there before the eye has found it, which is
     exactly what one wants of a sheet coming in. Used on the way OUT it covers
     89 % of the road in the first 150 ms and spends the other 270 on the last
     11 %: the sheet does not travel, it vanishes and leaves a crumb crawling
     home. Measured 27/09, and the reason the closings read as abstract. This
     one is a journey — a quarter of the way at 105 ms, half at 150, and a
     settle — and every departure in the house takes it. */
  --ease-home: cubic-bezier(0.4, 0, 0.2, 1);

  /* ---- THE SHEETS' ONE CLOCK ----------------------------------------------
     Four sheets grow out of a key and go back into it: the Jam drives its own
     glass in CSS (jam.css), the record's card and the account go through
     lib/glass.js. TWO IMPLEMENTATIONS, and every number was written twice —
     they drifted the moment anyone touched one of them, and a part of the
     account took a fifth of a second longer to read than the same part of the
     Jam in the same bar (27/09). The numbers live HERE, once, and both sides
     read them: the CSS through var(), glass.js off the root.
     The curves are the pair above: --jam-spring lands, --ease-home leaves. */
  --sheet-in: 720ms;     /* the glass out of its key */
  --sheet-out: 420ms;    /* and back into it */
  --part-lead: 160ms;    /* before the first part of a sheet comes in */
  --part-step: 50ms;     /* and between one part and the next, to the fourth */
  --part-in: 420ms;      /* a part coming into focus */
  --part-out: 260ms;     /* and going out, over the road and not before it */
}
/* EVERYTHING THE PAGE EASES, IN ONE DECLARATION — the sink under this column
   (the room it made for the Jam went with T146: the Jam lies over it). `transition` is a
   shorthand: a second one on .page anywhere drops the rest of the list without
   a word, and that is what happened — `body.has-jam .page` carried its own
   margin-right and the column stopped sinking, on every width. It snapped from
   sharp to blurred in a frame, seen on a phone, 27/09. Any sheet that wants the
   page to ease something adds it HERE. */
.page { transition: filter 0.8s var(--ease-soft), opacity 0.8s ease; }
body.is-more .page { filter: blur(8px); opacity: 0.4; pointer-events: none; }
body.is-more { overflow: hidden; }
/* While the column is up, the marquee stops: it had the whole blurred page
   composited again, ten to sixty times a second, on top of the record in 3D
   — more than a phone's GPU has (tickets/T7-low-end.md). The grain keeps
   jumping regardless — 16/09 traded that back in for animatedness; watch
   Umami's crash events for a GPU-pressure regression. */
body.is-more .track__scroll { animation-play-state: paused; }
/* THE COLUMN IS NOT HUNG ON THE WINDOW, IT IS HUNG ON WHAT IS SEEN (T61).
   iOS does not shorten the window when the keyboard comes up — it covers it.
   The layout window keeps its full height, and the third of the screen left
   over becomes a porthole a finger drags around inside it. `fixed` is pinned
   to the window, so one drag walked the whole suggestion off the top and left
   the blurred page in its place. more.js measures the porthole on
   `visualViewport` and writes it here; the fallbacks are the old `inset: 0`. */
.more {
  position: fixed;
  top: var(--vv-top, 0px);
  left: 0;
  right: 0;
  height: var(--vv-height, 100%);
  --vvh: var(--vv-height, 100dvh);   /* the height to measure a step against: what is visible, not what the window claims */
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--pad-top) var(--margin) var(--pad-bottom);
  overflow: hidden auto;
  overscroll-behavior: contain;   /* the column's own scroll stops at its ends: it does not hand the rest to the page underneath */
  scrollbar-width: none;   /* the column may scroll, but never shows a bar: on a mouse Mac the bar takes 15 px as a step grows, and the whole column — and the record behind it — jumps sideways */
  user-select: none;
  --control-height: 48px;   /* every key in the column is the small one, the phone's 48: the keys sit under the record, not on the deck */
}
.more::-webkit-scrollbar { display: none; }
.more[hidden] { display: none; }
.more > :not(.vinyl) { position: relative; z-index: 1; width: 100%; }
.more__bar { display: flex; align-items: center; justify-content: space-between; height: 24px; }
.more__label { text-box: trim-both cap alphabetic; }
.more__close { position: relative; display: grid; place-items: center; width: 24px; height: 24px; color: var(--ink); }
.more__close::before { content: ""; position: absolute; inset: calc(var(--hit-y) * -1) calc(var(--hit-x) * -1); }
@media (hover: hover) { .more__close:hover { color: var(--primary); } }
.more__close[hidden], .more__back[hidden] { display: none; }
/* The way back to the wheel, in brackets — the house's own link (1461:4947,
   « Lien en style crochets ») at `Body/UI`, where the cross was. */
.more__back {
  display: flex; align-items: center; gap: 4px;
  font-size: 14px; line-height: 16px; color: var(--ink);
  text-box: trim-both cap alphabetic;
}
.more__back::before { content: "["; }
.more__back::after { content: "]"; }
@media (hover: hover) { .more__back:hover { color: var(--primary); } }

/* The record, in 3D, behind the column: the whole window, so the crate can
   bleed off the bottom edge as on the frame. vinyl.js draws; the classes fade.
   .more > * gives every child a z-index; the canvas is the one underneath. */
.vinyl { position: absolute; top: 0; left: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0; transition: opacity 1.2s ease, filter 1.2s ease; }   /* absolute, not fixed: it scrolls with the column on a short window */
.vinyl.is-on { opacity: 1; }
.vinyl.is-gone { opacity: 0; filter: blur(12px); transition-duration: 1.2s; }   /* out the way everything leaves here: a quick blur, no zoom, the next step surfacing under it */

/* Each region surfaces from the water, as on the card: fade + rise + sharpen. */
@keyframes surface {
  from { opacity: 0; transform: translateY(16px); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: none; }
}

/* The menu: the destinations, centred (frame 1360:1067). */
.menu { display: flex; flex-direction: column; align-items: center; gap: 32px; margin-top: 88px; animation: surface 0.9s var(--ease-soft) both; }
.menu[hidden] { display: none; }
.menu__title, .hint, .err { text-box: trim-both cap alphabetic; }
.menu__item { text-align: center; }
.menu__title {
  font: 400 40px/40px var(--font-display);
  font-style: italic;
  letter-spacing: -0.01em;
  transition: color 0.15s;
  /* Safari composites transformed, filtered cap-trimmed text against the trimmed
     line box and shaves Signifier's overshoot while it surfaces. Eight pixels of
     paint bleed fix the layer bounds; the negative margins keep every distance
     (the card's fix, bestmate-card style.css). */
  padding-block: 8px;
  margin-block: -8px;
}
@media (hover: hover) { .menu__item:hover .menu__title { color: var(--primary); } }

/* The signature's link — in About since T146, where the foot's line went.
   A bracket link, the house's (more__back above); the glyph rides inside the
   brackets, part of the same token. */
/* A 16-line link is a 16-tall target: eight pixels either side take it past
   the 24 a finger is owed, and the negative margin gives the distance back. */
.credit__link { display: inline-flex; align-items: center; gap: 4px; color: var(--ink); padding-block: 8px; margin-block: -8px; }
.credit__link::before { content: "["; }
.credit__link::after { content: "]"; }
@media (hover: hover) { .credit__link:hover { color: var(--primary); } }

/* ---------- Suggest a song ----------
   One column, centred: a step at a time. The record lives behind, on the
   canvas; a step that shows it leaves it a box (.step__stage) and vinyl.js
   aims the camera at that box. Slower than the menu: 1.2 s to surface, and
   every move of the record above a second. */
.suggest { display: flex; flex-direction: column; align-items: center; margin-top: 40px; }
.suggest[hidden] { display: none; }
.step { display: flex; flex-direction: column; align-items: center; gap: 24px; width: 100%; text-align: center; }
.step[hidden] { display: none; }
.step:not([hidden]) > * { animation: surface 1.2s var(--ease-soft) both; }
.step.is-leaving > * { animation: none; opacity: 0; filter: blur(6px); transition: opacity 1s ease, filter 1s ease; }
/* The record's box — nothing in it, the camera fills it. It must still be a
   box: empty and in a centred column it measured ZERO WIDE, and vinyl.js
   throws away a rectangle with no width (a hidden step measures one, and
   framing on it flings the record to the corner). The record was then
   framed on the window instead of on the step — centred low, over the
   credits and into the keys on a short window. */
/* Measured on `--vvh`, and with no floor under it: `clamp(220px, 42vh, 340px)`
   read the FULL window and kept 220 px whatever happened, so with a keyboard up
   the box alone asked for more than the porthole had and pushed the column out
   by that much (T61). Same number on a full window — 0.42 × 900 lands on the
   340 ceiling either way; it only gives back what was taken from it. */
.step__stage { width: 100%; height: min(340px, calc(var(--vvh) * 0.42)); animation: none !important; }
/* The two steps that carry more than the record — the address and its face, then
   the name and its keys — breathe with the window instead of running past its
   foot: nothing in them may ask for a scroll. */
.step[data-step="email"], .step[data-step="thanks"] { gap: clamp(12px, 2.4vh, 24px); }
.step[data-step="email"] .step__stage, .step[data-step="thanks"] .step__stage { height: min(260px, calc(var(--vvh) * 0.26)); }
/* A short window: the record gives first, then the paddings. */
@media (max-height: 700px) {
  .more { padding-top: 24px; padding-bottom: 32px; }
  .suggest { margin-top: 16px; }
  .step[data-step="email"] .step__stage, .step[data-step="thanks"] .step__stage { height: min(160px, calc(var(--vvh) * 0.18)); }
}
.hint { max-width: 44ch; line-height: 20px; }
.err { color: var(--primary); max-width: 44ch; min-height: 20px; line-height: 20px; }
.err[hidden] { display: block; visibility: hidden; }   /* the line is always there: a refusal must not shove the layout */
.keys { display: flex; justify-content: center; gap: 12px; }   /* no led over these; 12 between, so a focus ring (outline 1.5, offset 3) never touches the neighbour */

/* The pill: the link goes in a capsule the width of the page (1360:1900) —
   a hairline of control at 60 %, a breath of warm grey, the search glyph at
   40 %. A paste is enough; there is no key. */
.pill {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  /* The house field (tickets/T64-les-controles.md): the Jam's box, for every
     field of the page. It was a 72 capsule here, a 56 one in the drawer and a
     40 grey box in the Jam — three drawings for typing a word. */
  height: var(--control-height);
  padding: 0 20px;
  border-radius: 999px;   /* a field one types in is a capsule, a key (T72) */
  background: var(--control);
  box-shadow: var(--field-up);
  transition: box-shadow 120ms ease;
  font: 500 14px/16px var(--font-ui);
  letter-spacing: -0.02em;
}
.pill:focus-within { box-shadow: var(--field-down); }   /* held down while one types, and its contents with it (T72) */
@media (hover: hover) { .pill:not(:focus-within):hover { box-shadow: var(--field-lift); } }
.pill > * { transition: transform 80ms ease; }
.pill:focus-within > * { transform: translateY(2px); }
.pill > .icon { flex: none; opacity: 0.4; }
/* The roll: at the far end of the capsule, the marks of the platforms the desk
   reads, one at a time — each fades in, holds, fades out as the next comes;
   no movement, it must not pull the eye (1360:1900 shows one; the roll gives
   them all a turn). Pure CSS: one animation, six delays. */
.roll { position: relative; flex: none; width: 20px; height: 20px; overflow: hidden; }
.roll > .icon { position: absolute; inset: 0; opacity: 0; animation: roll 12s ease-in-out infinite; }
.roll > .icon:nth-child(2) { animation-delay: 2s; }
.roll > .icon:nth-child(3) { animation-delay: 4s; }
.roll > .icon:nth-child(4) { animation-delay: 6s; }
.roll > .icon:nth-child(5) { animation-delay: 8s; }
.roll > .icon:nth-child(6) { animation-delay: 10s; }
@keyframes roll {
  0%        { opacity: 0; }
  5%, 12%   { opacity: 0.4; }
  17%, 100% { opacity: 0; }
}
.pill--narrow { max-width: 480px; }
.pill__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  letter-spacing: inherit;
  user-select: text;
}
.pill__input:focus { outline: none; }
.pill__input::placeholder { color: var(--ink); opacity: 0.4; }
@media (pointer: coarse) { .pill__input { font-size: 16px; } }   /* under 16, iOS zooms the page in on focus */

/* The chip: the same capsule, shrunk to its words, while the desk looks
   (1360:1914) — and « Found it ! » when it has. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: auto;
  height: 46px;
  padding: 0 24px;   /* no glyph in it any more (T64): the words alone, the same air both sides */
  border: 1px solid rgba(217, 217, 217, 0.6);
  border-radius: 999px;
  background: linear-gradient(186deg, rgba(238, 231, 231, 0.2) 28%, rgba(238, 231, 231, 0.4) 76%);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;   /* while its width travels (T83), the words never spill */
}
.chip > span { opacity: 0.4; }
:root[data-theme="dark"] .chip { border-color: rgba(255, 255, 255, 0.12); background: linear-gradient(186deg, rgba(255, 255, 255, 0.04) 28%, rgba(255, 255, 255, 0.08) 76%); }
/* The one word in a line you can press. Blue at rest, not only on hover: the
   accent says "live" — what you can act on, or what just changed — and a key
   drawn in the same ink as the sentence around it is a key nobody finds
   (tickets/T44). Hover keeps a move of its own: it goes to full ink. */
.cmd__link { color: var(--primary); cursor: pointer; }
@media (hover: hover) { .cmd__link:hover { color: var(--ink); } }

/* The credits, as the stage prints them — a disc, a mic. Read, not edited:
   what the link says is what goes to the desk. */
.credits { display: grid; gap: 8px; }
.credits__row { display: flex; align-items: center; gap: 8px; height: 24px; }
.credits__text { font-size: 14px; line-height: 20px; text-transform: uppercase; text-box: trim-both cap alphabetic; }

/* The face of an address (tickets/T10-avatars.md): one pixel icon of the
   player's own family on one colour pair, in a square with the keys' radius —
   a sticker, not a photograph. avatar.js writes the box and its size; the page
   only says how round it is, and how it sits. */
/* A face in the capsule (tickets/T21): the slot is NOT there waiting any more.
   T10 kept an empty outline the size of what would land in it, so that nothing
   would move — and what it cost is that a capsule with nobody in it showed a
   dead ring that read as a field to fill. The rule is the other way round now:
   the ring IS the recognition. No face, no ring, no room taken; a face, and it
   arrives — swelling past its size, its edge wobbling, settling. */
.pill--face { padding-left: 8px; }
.pill .face { width: 32px; height: 32px; }
.pill .face-pick { --key: 22px; margin-right: 12px; }   /* the shuffle key rides the face: the text starts after it */
/* The mark at the far end of the name's capsule: on once the desk holds what
   the capsule says, off the moment it differs — no key to press, nothing to
   confirm. The same 0.4 as every icon in a capsule. */
/* "Saved", in the primary, rather than a grey tick nobody read as a save. */
.pill > .pill__mark {
  margin-left: auto; flex: none;
  font-size: 10px; line-height: 16px; text-transform: uppercase; color: var(--primary);
  text-box: trim-both cap alphabetic;
  opacity: 0; transition: opacity 0.3s ease;
}
.pill > .pill__mark.is-on { opacity: 1; }

.face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: 50%;   /* a face is round, wherever it is read */
  box-shadow: 0 0 0 1px var(--ink-muted);   /* its edge: an ink palette on a dark page would have none */
}
/* Nobody there: no edge to draw, and in a capsule no room taken either — the
   width and the capsule's own gap close, so the text starts at the border. */
.face:not(.is-on) { box-shadow: none; }
/* A face the desk holds as a photograph fills the round instead of sitting in
   it; the box keeps its size, its edge and its arrival (tickets/T30). */
.face > img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
/* `span.face` on purpose: each context sets the slot's size with two classes
   (`.drawer .pill .face`), and the empty state has to beat all of them without
   knowing them. The tag name is the cheapest point of specificity there is. */
.pill span.face:not(.is-on) { width: 0; margin-right: -8px; }
/* …but straight in the capsule (the sign-in's), the text has to start where
   every capsule's does, 20 in (T72): 8 of padding, 4 here, 8 of gap. The face
   arriving takes the 4 back with its width — the transition below. */
.pill > span.face:not(.is-on) { margin-right: 4px; }
.pill .face { transition: width 0.5s var(--ease-soft), margin-right 0.5s var(--ease-soft); }
/* Somebody: it arrives the way a drop lands. One pass, on the class — painting
   the same face again does not replay it. */
.face.is-on { animation: face-liquid 0.7s var(--ease-soft) both; }
@keyframes face-liquid {
  0%   { opacity: 0; transform: scale(0.35); border-radius: 42% 58% 56% 44%; filter: blur(4px); }
  40%  { opacity: 1; transform: scale(1.14); border-radius: 58% 42% 44% 56%; filter: blur(0); }
  70%  { transform: scale(0.96); border-radius: 46% 54% 58% 42%; }
  100% { transform: scale(1); border-radius: 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .face.is-on { animation: none; }
  .pill .face { transition: none; }
}
.who { display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%; max-width: 480px; }
.who .pill { flex: 1; }
/* Same as the schedule's: an author display beats the UA's [hidden], and these
   three carry one — the line about who suggested, the name and its row. */
.track__row[hidden], .who[hidden], .who .pill[hidden] { display: none; }

/* The face one picks: the shuffle key rides on its corner, half over, half off
   — the same relief as the keys below, on the only radius. Its size is a rung
   of the face it rides, not a number: a 28 hung off a 32 swallowed the face
   AND poked out through the capsule's round end (the account's own capsule is
   a 56, not the suggestion's 72). One property, set where the face is set. */
.face-pick { --key: 32px; position: relative; display: inline-flex; flex: none; }
.face-pick__key {
  position: absolute;
  right: calc(var(--key) / -2);
  bottom: calc(var(--key) / -2);
  display: grid;
  place-items: center;
  width: var(--key);
  height: var(--key);
  border-radius: 50%;   /* it rides a round face (T72) */
  background: var(--control);
  color: var(--ink);
  box-shadow: 0 4px 4px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.35);
  transition: box-shadow 120ms ease, transform 80ms ease;
}
.face-pick__key::before { content: ""; position: absolute; inset: calc(var(--hit-y) * -1) calc(var(--hit-x) * -1); }   /* the glyph shrinks with the key; the hand's reach does not */
.face-pick__key > .icon { width: calc(var(--key) * 4 / 7); height: calc(var(--key) * 4 / 7); }
@media (hover: hover) { .face-pick__key:hover { box-shadow: 0 8px 10px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.5); color: var(--primary); } }
.face-pick__key:active { transform: translateY(2px); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08), inset 0 2px 4px rgba(0, 0, 0, 0.18); }
/* A <button> made to sit in a sentence like the <a> version of the same key.
   It used to restate `color: inherit` here — which the global button reset
   already does — and at (0,1,1) it beat .cmd__link's own accent, so five of
   the six keys came out the colour of the sentence around them (T44). */
button.cmd__link { padding: 0; border: 0; background: none; font: inherit; letter-spacing: inherit; }

@media (max-width: 640px) {
  /* Three keys on the last line now: the primary gives what the two 85s take. */
  .actions .action:first-child { flex: 1; }
  .actions .btn--primary { width: 100%; }
  .keys { width: 100%; }
  .keys .btn--primary { flex: 1; width: auto; }
  .menu { margin-top: 64px; }
  .suggest { margin-top: 24px; }
  /* The sheets' margin, not the page's: the column is a sheet over the record,
     as the card and the drawer are, and its edges are theirs (T64). */
  .more { padding-inline: 24px; }
  .step__stage { height: min(240px, calc(var(--vvh) * 0.3)); }   /* the porthole's share, not the window's (T61) */
  .step[data-step="email"] .step__stage, .step[data-step="thanks"] .step__stage { height: min(180px, calc(var(--vvh) * 0.2)); }
  .who { flex-wrap: wrap; }
}

/* A phone: the same moves without the blur — a filter on a full-screen canvas
   or on every surfacing region is a render pass the GPU may not have room for. */
@media (pointer: coarse) {
  .vinyl.is-gone { filter: none; }
  .step.is-leaving > * { filter: none; }
  .menu, .step:not([hidden]) > * { animation-name: surface-flat; }
}
@keyframes surface-flat {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
/* …and a field that has the focus does not move at all (T140). The phone
   draws a focused field's text itself, over the page, and catches up with a
   moving parent late: the placeholder of "Suggest a song" slid behind its
   capsule while it rose, and jumped into it when the rise ended. The capsule
   only fades in; and the 2 px it is pressed while one types (T72) are given
   up here — the keyboard says it is held. */
@media (pointer: coarse) {
  .step:not([hidden]) > :has(input) { animation-name: surface-still; }
  .pill:focus-within > * { transform: none; }
}
@keyframes surface-still {
  from { opacity: 0; }
  to   { opacity: 1; }
}
