/* The feed: one full-screen toy at a time, swiped sideways (up/down on a portrait phone). Tokens match labs/shared/lab.css. */
:root {
  --bg: #000000;
  --grid: #1c1c22;
  --ink: #f4f4f6;
  --dim: #8b8b98;
  --line: #5c5c66;   /* 3:1 on black, the minimum for borders */
  --panel: #07070a;
  --grid-size: 16px;
  --display: "Unbounded", "Arial Narrow", system-ui, sans-serif;
  --body: "Familjen Grotesk", "Helvetica Neue", Arial, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --gut: max(16px, env(safe-area-inset-left, 0px));
  --gut-r: max(16px, env(safe-area-inset-right, 0px));
  --ease: cubic-bezier(.22, .8, .24, 1);
  --slide-ms: 420ms;
  color-scheme: dark;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; }
body { background: var(--bg); color: var(--ink); font: 400 15px/1.55 var(--body); -webkit-font-smoothing: antialiased; }
a { color: var(--ink); }
button { font: inherit; color: inherit; }
:focus-visible { outline: 1px solid var(--ink); outline-offset: 2px; }
.btn { display: inline-flex; align-items: center; gap: 8px; font: 400 12px/1 var(--mono); padding: 11px 14px; border: 1px solid var(--line); background: transparent; border-radius: 2px; cursor: pointer; text-decoration: none; white-space: nowrap; }
.btn:hover { border-color: var(--ink); }
.btn.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.icon { display: grid; place-items: center; width: 36px; height: 36px; border: 1px solid var(--line); border-radius: 2px; background: rgb(0 0 0 / .45); cursor: pointer; }
.icon:hover { border-color: var(--ink); }
.num { font: 400 11px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--dim); }

/* ---- feed ---- */
.feed { position: fixed; inset: 0; overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none; background: var(--bg);
  /* the bench grid, centered like the labs', so a slide that isn't painted yet still looks like the bench */
  background-image: linear-gradient(to right, var(--grid) 1px, transparent 1px), linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  background-size: var(--grid-size) var(--grid-size); background-position: 50vw 50dvh; }
.slides { position: absolute; inset: 0; }
.slide { position: absolute; inset: 0; will-change: transform; backface-visibility: hidden; contain: strict; background: var(--bg); }
.slide.anim { transition: transform var(--slide-ms) var(--ease); }
.slide.pinned { z-index: 0; }
.slide.parked { z-index: -1; }   /* a resting neighbour, under the toy on screen (feed.js park()) */
.slide.entering { z-index: 2; animation: enter .22s ease-out both; }
@keyframes enter { from { opacity: 0; } }
.slide .stage { position: absolute; inset: 0; overflow: hidden;
  background-image: linear-gradient(to right, var(--grid) 1px, transparent 1px), linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  background-size: var(--grid-size) var(--grid-size); background-position: 50vw 50dvh; }
.slide .poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  pointer-events: none; -webkit-user-drag: none; }   /* a mouse drag on an image would start the browser's own drag */
.slide iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; pointer-events: none; opacity: 0; transition: opacity .2s; background: transparent; color-scheme: dark; }
.slide.ready iframe { opacity: 1; }
/* Every toy centres itself in its frame, so the frame sits higher than the screen: on a tall screen it runs up
   behind the top bar (same size toy, higher), on a wide one it stops above the caption and fades out there. */
.slide :is(.poster, iframe) { top: var(--toy-top, 0px); height: calc(100% - var(--toy-top, 0px) - var(--toy-cut, 0px)); }
@media (orientation: portrait) { .slide { --toy-top: -120px; } }
@media (orientation: landscape) {
  .slide { --toy-cut: 120px; }
  .slide :is(.poster, iframe) { -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 72px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 72px), transparent); }
}
.slide.live iframe { pointer-events: auto; }

/* Edge zones: a drag that starts in one moves between toys; the middle of the screen is the toy's.
   They sit at both ends of the swipe axis (html[data-axis], set by the vertical-swipe feed), wide enough for a
   thumb, narrow enough that leaving is a deliberate reach to the edge. A faint grip marks each on touch screens;
   a press lights it and washes the edge, so the very first touch says "this leaves". */
:root { --edge-x: clamp(28px, 9vw, 72px); --edge-y: clamp(64px, 13dvh, 120px); }
.slide .edge { position: absolute; z-index: 1; display: grid; place-items: center; touch-action: none; }
.slide .edge { top: 0; bottom: 0; width: var(--edge-x); }
.edge.lo { left: 0; }
.edge.hi { right: 0; }
.edge::before { content: ""; width: 3px; height: 40px; border-radius: 2px; background: var(--ink); opacity: 0; transition: opacity .18s, box-shadow .18s; }
.edge::after { content: ""; position: absolute; inset: 0; opacity: 0; transition: opacity .18s; pointer-events: none; }
.edge.lo::after { background: linear-gradient(to right, rgb(244 244 246 / .22), transparent); }
.edge.hi::after { background: linear-gradient(to left, rgb(244 244 246 / .22), transparent); }
@media (pointer: coarse) { .slide.live .edge::before { opacity: .22; } }
.slide .edge.pressed::before { opacity: .9; box-shadow: 0 0 12px rgb(244 244 246 / .55); }
.slide .edge.pressed::after { opacity: 1; }
html[data-axis="y"] .slide .edge { left: 0; right: 0; top: auto; bottom: auto; width: auto; height: var(--edge-y); }
html[data-axis="y"] .edge.lo { top: 0; }
html[data-axis="y"] .edge.hi { bottom: 0; }
html[data-axis="y"] .edge::before { display: none; }   /* the swipe cues mark them instead */
html[data-axis="y"] .edge.lo::after { background: linear-gradient(to bottom, rgb(244 244 246 / .22), transparent); }
html[data-axis="y"] .edge.hi::after { background: linear-gradient(to top, rgb(244 244 246 / .22), transparent); }
/* the top grip sits at the foot of its zone, clear of the top bar; the bottom one is under the caption, which swipes too */
html[data-axis="y"] .edge.lo { align-items: end; padding-bottom: 10px; }
html[data-axis="y"] .toyhint { top: calc(var(--edge-y) + 14px); }

/* Swipe cues (feed.js): drawn like the labs' corner brackets (1px, dim, .55) until a swipe starts, then lit.
   During a drag the one it pulls from follows the finger and a ring marks where the drag counts (see guide());
   letting go eases them home. Over the ripple (z 3). Tall screens only. */
.cues { display: none; }
html[data-axis="y"] .cues { display: block; position: absolute; inset: 0; z-index: 4; pointer-events: none; }
.cue { position: absolute; left: 50%; width: 10px; height: 10px; margin-left: -5px; border: solid var(--dim); border-width: 1px 0 0 1px; opacity: .55;
  transition: opacity .5s, border-color .5s, filter .5s, top .35s ease-out, left .35s ease-out; }
.cue.lo { top: calc(var(--edge-y) - 18px); rotate: 225deg; }
.cue.hi { top: calc(var(--cap-top, calc(100% - var(--edge-y))) + 30px); rotate: 45deg; }
.swiping .cue { opacity: 1; border-color: var(--ink); filter: drop-shadow(0 0 4px rgb(244 244 246 / .75)); transition-duration: .12s; }
.cues .ring { position: absolute; left: 50%; width: 22px; height: 22px; margin-left: -11px; border: 1px solid var(--ink); border-radius: 50%;
  opacity: 0; transition: opacity .3s, box-shadow .15s; }
.cues .slot { position: absolute; left: 50%; top: 0; width: 30px; height: 30px; margin-left: -15px; border-radius: 15px; opacity: 0; transition: opacity .3s;
  border: 1px solid rgb(244 244 246 / .07); box-shadow: inset 0 1px 0 rgb(244 244 246 / .06), inset 0 -8px 14px rgb(0 0 0 / .12);
  background: linear-gradient(to right, rgb(244 244 246 / .04), rgb(244 244 246 / .01) 45%, rgb(244 244 246 / .03));
  -webkit-backdrop-filter: blur(2.5px) saturate(1.1); backdrop-filter: blur(2.5px) saturate(1.1); }
.guiding .slot { opacity: 1; }
.guiding .cue, .guiding .ring, .guiding .slot { transition: opacity .12s, border-color .12s, filter .12s, box-shadow .15s; }   /* following: no easing */
.guiding .ring { opacity: .5; }
/* reached: the ring flares with the ripple's front (transition.js glow, GLOW_MS) and fades, leaving the chevron */
.guiding.armed .ring { opacity: 0; animation: flare .6s linear both; }
.guiding.armed .slot { opacity: 0; transition: opacity .6s; }
/* pulled back short of it again: the ring and the slot fade back in */
.guiding:not(.armed) :is(.ring, .slot) { transition: opacity .35s ease-out; }
@keyframes flare {
  0% { opacity: .5; box-shadow: 0 0 0 rgb(244 244 246 / 0); }
  20% { opacity: 1; box-shadow: 0 0 16px rgb(244 244 246 / .8), inset 0 0 6px rgb(244 244 246 / .5); }
  100% { opacity: 0; box-shadow: 0 0 24px rgb(244 244 246 / 0); }
}
/* while the toy a chevron leads to is loading: a nudge the way it points, two or three chevrons long, and a
   gentle settle, every couple of seconds; translate only, so it carries on through a stall */
.cue.nudge { animation: nudge 2.4s .5s infinite both; }
.cue.lo { --nudge: 24px; }
.cue.hi { --nudge: -24px; }
@keyframes nudge {
  0% { translate: 0 0; animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  16% { translate: 0 var(--nudge); animation-timing-function: cubic-bezier(.45, 0, .25, 1); }
  58%, 100% { translate: 0 0; }
}
.swiping .cue.nudge { animation: none; }
/* Loading pies on the far side of the chevrons, above the top one and below the bottom one (feed.js paintPies):
   a ring while the toy that way waits its turn, filling while it loads, gone once it has drawn. Half a second late,
   so a toy that loads quickly never blips one. Two half-discs turning in two half-windows: transforms only, so the compositor
   keeps them going while a toy starting up holds the main thread. --eta: how long the fill takes; --t0: time
   already spent loading. Out of the way during a swipe. */
.cues .pie { position: absolute; left: 50%; width: 11px; height: 11px; margin-left: -5.5px; border: 1px solid var(--dim);
  border-radius: 50%; opacity: 0; transition: opacity .4s; }
.cues .pie.lo { top: calc(var(--edge-y) - 18px - 18px); }
.cues .pie.hi { top: calc(var(--cap-top, calc(100% - var(--edge-y))) + 30px + 17px); }
.cues .pie.queued { opacity: .35; transition-delay: .5s; }
.cues .pie.loading { opacity: .6; transition-delay: .5s; }
.swiping .pie { opacity: 0 !important; transition-duration: .12s; }
.pie b { position: absolute; top: 1px; bottom: 1px; width: calc(50% - 1px); overflow: hidden; }
.pie b:first-child { left: 50%; }
.pie b:last-child { right: 50%; }
.pie b::before { content: ""; position: absolute; inset: 0; background: var(--ink); rotate: -180deg; opacity: 0; }   /* hidden at rest: no hairline at the seam */
.pie b:first-child::before { border-radius: 0 99px 99px 0; transform-origin: 0 50%; }
.pie b:last-child::before { border-radius: 99px 0 0 99px; transform-origin: 100% 50%; }
.pie.loading b:first-child::before { animation: fill calc(var(--eta) / 2) linear calc(var(--t0) * -1) both; }
.pie.loading b:last-child::before { animation: fill calc(var(--eta) / 2) linear calc(var(--eta) / 2 - var(--t0)) both; }
.pie.done b::before { rotate: 0deg; opacity: 1; }   /* full while it fades */
@keyframes fill { from { rotate: -180deg; opacity: 0; } 1% { opacity: 1; } to { rotate: 0deg; opacity: 1; } }

/* what a finger does to the toy, over it as it arrives */
.toyhint { position: absolute; z-index: 1; left: 50%; top: calc(max(12px, env(safe-area-inset-top, 0px)) + 58px); translate: -50% 0; pointer-events: none;
  font: 400 11px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; color: var(--ink);
  padding: 8px 11px; border: 1px solid var(--line); border-radius: 999px; background: rgb(0 0 0 / .55);
  opacity: 0; transition: opacity .3s; }
.slide.hinting .toyhint { opacity: 1; transition-delay: .35s; }
.slide.ready .loader, .slide.has-poster .loader { opacity: 0; }
/* the toy on screen hasn't drawn yet, or hasn't moved since it was told to play (feed.js waiting()): a spinner,
   and while it hasn't drawn, a pie inside it filling over the guess at its load. Over its still, or under the
   breathing mark when there's no still yet. A beat late, so a quick start never shows them. Transforms only, so
   they keep turning through a stall (a toy compiling its shaders holds the main thread). */
.wait { position: absolute; z-index: 1; left: 50%; top: 50%; width: 32px; height: 32px; margin: -16px 0 0 -16px; border-radius: 50%;
  border: 1.5px solid rgb(244 244 246 / .16); border-top-color: var(--ink); opacity: 0; transition: opacity .2s; pointer-events: none;
  background: rgb(0 0 0 / .35); animation: spin .8s linear infinite; }
.slide.waiting .wait { opacity: .85; transition-delay: .3s; }
.stage .pie { position: absolute; z-index: 1; left: 50%; top: 50%; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 50%;
  border: 1px solid rgb(244 244 246 / .3); opacity: 0; transition: opacity .2s; pointer-events: none; }
.stage .pie b { top: 2px; bottom: 2px; width: calc(50% - 2px); }
.slide.waiting .stage .pie.loading, .slide.waiting .stage .pie.queued { opacity: .85; transition-delay: .3s; }
.stage .pie.finishing { opacity: .85; transition-delay: 0s; }   /* running out to full once the toy beat its guess */
.slide:not(.has-poster) :is(.wait, .stage .pie) { translate: 0 64px; }
@keyframes spin { to { rotate: 360deg; } }
/* the GL transition overlay (site/transition.js): over the slides, under the chrome; only shown mid-transition */
.fx { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 3; pointer-events: none; visibility: hidden;
  opacity: 0; transition: opacity .18s, visibility 0s .18s; }
/* up at once; on the way out it fades, so a toy that isn't ready eases into its loader instead of cutting to it */
.fx.on { visibility: visible; opacity: 1; transition: none; }

/* Placeholder loader: the mark, breathing. Scott will design the real one. */
.loader { position: absolute; inset: 0; display: grid; place-items: center; color: var(--dim); transition: opacity .2s; pointer-events: none; }
.loader svg { animation: breathe 1.6s ease-in-out infinite; }
@keyframes breathe { 50% { opacity: .35; } }

/* caption rides with its slide */
.caption { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; display: flex; align-items: flex-end; gap: 16px;
  padding: 72px var(--gut-r) max(20px, env(safe-area-inset-bottom, 0px)) var(--gut);
  background: linear-gradient(to top, rgb(0 0 0 / .78), rgb(0 0 0 / .45) 55%, transparent); }
.cap { flex: 0 1 620px; min-width: 0; display: grid; gap: 6px; text-align: left; background: none; border: 0; padding: 0; cursor: pointer; max-width: 620px; }
.cap .title { font: 200 26px/1.1 var(--display); letter-spacing: -.01em; }
.cap .blurb { color: var(--dim); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.cap { color: inherit; text-decoration: none; }
.cap .more { font: 400 11px/1 var(--mono); color: var(--ink); letter-spacing: .08em; text-transform: uppercase; }
.cap:hover .more { text-decoration: underline; }
.play { flex: none; margin-left: auto; }
.motion { flex: none; justify-content: center; }

/* ---- chrome over the feed ---- */
.topbar { position: fixed; z-index: 5; top: 0; left: 0; right: 0; display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: max(12px, env(safe-area-inset-top, 0px)) var(--gut-r) 24px var(--gut); pointer-events: none;
  background: linear-gradient(to bottom, rgb(0 0 0 / .55), transparent); }
.topbar > * { pointer-events: auto; }
.artist { display: flex; align-items: center; gap: 10px; padding: 4px 12px 4px 4px; border: 1px solid var(--line); border-radius: 999px; background: rgb(0 0 0 / .45); cursor: pointer;
  font: 400 12px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.artist:hover { border-color: var(--ink); }
.avatar { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; overflow: hidden; background: var(--panel); border: 1px solid var(--line); flex: none; }
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.topright { display: flex; align-items: center; gap: 12px; }
.count { font: 400 12px/1 var(--mono); color: var(--dim); font-variant-numeric: tabular-nums; letter-spacing: .08em; }

.nav { position: fixed; z-index: 4; top: 50%; translate: 0 -50%; display: none; place-items: center; width: 44px; height: 44px; border: 1px solid var(--line); border-radius: 2px; background: rgb(0 0 0 / .45); cursor: pointer; }
.nav:hover { border-color: var(--ink); }
.nav.prev { left: var(--gut); }
.nav.next { right: var(--gut-r); }
@media (hover: hover) and (pointer: fine) { .nav { display: grid; } }

/* ---- the artist card sheet ---- */
dialog { color: var(--ink); }
dialog:focus { outline: none; }
dialog::backdrop { background: rgb(0 0 0 / .55); opacity: 0; transition: opacity .25s; }
dialog.open::backdrop { opacity: 1; }
.sheet { position: fixed; inset: auto 0 0 0; margin: 0 auto; width: 100%; max-width: 640px; max-height: 82dvh; padding: 0; border: 1px solid var(--line); border-bottom: 0;
  background: var(--panel); border-radius: 6px 6px 0 0; overflow: auto; overscroll-behavior: contain;
  transform: translateY(100%); transition: transform .32s var(--ease); }
.sheet.open { transform: none; }
.sheet-body { display: grid; gap: 18px; padding: 10px var(--gut-r) max(28px, env(safe-area-inset-bottom, 0px)) var(--gut); }
/* the handle: a full-width strip that stays put while the sheet scrolls, so it can always be grabbed and dragged down */
.grab { position: sticky; top: 0; z-index: 1; justify-self: stretch; display: grid; place-items: center; height: 30px;
  margin: -10px calc(-1 * var(--gut-r)) -8px calc(-1 * var(--gut)); padding: 0; border: 0; background: var(--panel); cursor: grab; touch-action: none; }
.grab::before { content: ""; width: 40px; height: 4px; border-radius: 2px; background: var(--line); }
.grab:hover::before { background: var(--dim); }
.sheet h2 { margin: 0; font: 200 28px/1.1 var(--display); letter-spacing: -.01em; text-wrap: balance; }
.sheet p { margin: 0; }
.sheet .blurb { color: var(--dim); }
.card .who { display: flex; align-items: center; gap: 16px; }
.card .avatar { width: 72px; height: 72px; }
.links { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.links li { display: flex; justify-content: space-between; gap: 12px; padding: 12px 0; border-top: 1px solid var(--line); font: 400 13px/1.2 var(--mono); }
.links li span { color: var(--dim); }
.links a { text-decoration: none; }
.links a:hover { text-decoration: underline; }

/* ---- overview grid ---- */
.overview { position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; padding: 0; border: 0; background: var(--bg);
  overflow: auto; overscroll-behavior: contain; opacity: 0; transition: opacity .2s; }
.overview.open { opacity: 1; }
.ov-head { position: sticky; top: 0; z-index: 1; display: flex; justify-content: space-between; align-items: center;
  padding: max(12px, env(safe-area-inset-top, 0px)) var(--gut-r) 12px var(--gut); background: var(--bg); border-bottom: 1px solid var(--line); }
.ov-head h2 { margin: 0; font: 400 12px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; display: flex; gap: 10px; align-items: baseline; }
.tiles { list-style: none; margin: 0; padding: 12px var(--gut-r) 32px var(--gut); display: grid; gap: 6px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (min-width: 720px) { .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; } }
@media (min-width: 1100px) { .tiles { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
.tile { position: relative; display: block; width: 100%; aspect-ratio: 4 / 5; padding: 0; border: 1px solid var(--line); border-radius: 2px; overflow: hidden; cursor: pointer; text-align: left;
  background-color: var(--bg);
  background-image: linear-gradient(to right, var(--grid) 1px, transparent 1px), linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  background-size: 8px 8px; background-position: center; }
.tile:hover, .tile[aria-current="true"] { border-color: var(--ink); }
.tile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.tile .mark { position: absolute; inset: 0; display: grid; place-items: center; color: var(--dim); }
.tile .lbl { position: absolute; left: 0; right: 0; bottom: 0; display: grid; gap: 4px; padding: 24px 8px 8px; background: linear-gradient(to top, rgb(0 0 0 / .85), transparent); }
.tile .lbl b { font: 300 13px/1.15 var(--display); }

.stats { position: fixed; z-index: 9; left: var(--gut); top: 64px; font: 400 11px/1.5 var(--mono); color: var(--dim); background: rgb(0 0 0 / .6); padding: 6px 8px; pointer-events: none; font-variant-numeric: tabular-nums; }
.stats b { color: var(--ink); font-weight: 400; }

@media (max-width: 560px) {
  .caption { flex-direction: column; align-items: stretch; gap: 14px; }
  .cap { flex: none; }
  .play { justify-content: center; margin-left: 0; }
  .cap .title { font-size: 23px; }
}
@media (prefers-reduced-motion: reduce) {
  .slide.anim, .sheet, .overview, dialog::backdrop, .toyhint, .edge::before, .edge::after, .cue, .cues .ring, .cues .slot { transition: none; }
  .loader svg { animation: none; }
  .cue.nudge { animation: none; }
  .wait { animation: breathe 1.6s ease-in-out infinite; }
  .slide.entering { animation-duration: .12s; }
}
