/* Shared launch polish. Keep the existing palette, geometry and game motion. */
:root { --ease-interaction: cubic-bezier(.2,.8,.2,1); }
button {
  transition-property: color, background-color, border-color, box-shadow, opacity, transform;
  transition-duration: 160ms;
  transition-timing-function: var(--ease-interaction);
}
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--section, var(--gold)); outline-offset: 4px;
}
.fy-feed:focus-visible { outline-offset: -3px; }
.fy-key-hint { display: none; }
@media (hover: hover) and (pointer: fine) {
  .fy-key-hint { display: block; position: absolute; bottom: 8px; left: 0; right: 0; z-index: 7; pointer-events: none; text-align: center; color: var(--text-dim); font-size: 12px; }
  /* One hover language for every surface you can open with a mouse: a short
     lift and a scale small enough to be felt rather than seen, on the app's
     own curve and one duration. These used to be four durations (140-220ms)
     on three easings, which is what made the desktop read as assembled. */
  .card, .game-card, .list-card, .groove-card, .rw-card, .cf-card {
    transition: transform 280ms var(--ease), box-shadow 280ms var(--ease), border-color 200ms var(--ease);
  }
  .game-card:hover, .list-card:hover, .rw-card:hover { transform: translateY(-3px) scale(1.015); }
  .groove-card:hover { transform: translateY(-2px) scale(1.01); }
  .btn-primary, .btn-ghost, .btn-mini, .splash-cta { transition-timing-function: var(--ease); }
  .btn-primary:hover:not(:disabled), .btn-ghost:hover:not(:disabled), .btn-mini:hover:not(:disabled) { transform: scale(1.02); }
  /* Equal specificity to the hover above and after it, so a press still
     visibly gives under the cursor. */
  .btn-primary:active:not(:disabled), .btn-ghost:active:not(:disabled), .btn-mini:active:not(:disabled) { transform: scale(.97); }
}
/* For You is one screen: the feed is the scroller and already runs under the
   nav. The body's nav padding made the DOCUMENT 102px taller than the window,
   so a swipe outside the feed scrolled the page and slid the whole feed up —
   sleeve cut off under the status bar, bare page below it. No padding, no
   page to scroll. */
body[data-section="foryou"] { padding-bottom: 0; overscroll-behavior-y: none; }
.fy-feed { min-height: 180px; overflow-anchor: none; touch-action: pan-y pinch-zoom; }
.fy-loading { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 20px; }
.fy-loading::before { content: ''; width: min(65%, 240px); aspect-ratio: 1; border-radius: 14px; background: var(--surface-2); animation: fyPlaceholder 1.4s ease-in-out infinite alternate; }
@keyframes fyPlaceholder { to { opacity: .45; } }
/* Every "Loading…" line in the app. Static text reads as stalled; this says
   work is happening without pretending to know how much is left: the words
   breathe, and a hairline track under them carries one travelling highlight
   in the page's own colour. The highlight runs in PIXELS from fully off one
   end to fully off the other, so start and end frames are the same empty
   track and the loop cannot snap (the percentage-gradient trap). */
.ld-line { animation: ldBreathe 1.6s ease-in-out infinite; }
.ld-line::after {
  content: ''; display: block; width: 140px; max-width: 70%; height: 2px; margin-top: 10px; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--section, var(--gold)), transparent) no-repeat,
              color-mix(in srgb, var(--text) 8%, transparent);
  background-size: 56px 100%, 100% 100%;
  animation: ldTrack 1.15s cubic-bezier(.45,0,.25,1) infinite;
}
.fy-loading.ld-line::after, .g-loading.ld-line::after { margin-inline: auto; }
@keyframes ldBreathe { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@keyframes ldTrack { from { background-position: -56px 0, 0 0; } to { background-position: 140px 0, 0 0; } }
.fy-card { scroll-snap-align: start; }
.fy-like, .fy-refresh-btn, .modal-close { min-width: 44px; min-height: 44px; }
.fy-push-btn, #fy-seg button, .fy-save { min-height: 44px; }
.fy-sub { overflow-wrap: anywhere; }
.crate-tabs { max-width: 100%; }
.crate-tabs button { min-height: 44px; min-width: 0; }
@media (max-width: 399px) {
  .crate-tabs { display: flex; }
  .crate-tabs button { flex: 1; padding-inline: 8px; font-size: 12px; }
}
.cc-chip { min-height: 44px; }
.fy-alb { border: 0; background: none; color: inherit; font: inherit; }
.modal-panel { overscroll-behavior: contain; }
html.layer-open { overflow-y: hidden; }
body.layer-open { overflow-y: hidden; }
.modal:not(.open) { pointer-events: none; }
@supports (transition-behavior: allow-discrete) {
  .modal {
    opacity: 0;
    transition: opacity 180ms var(--ease-interaction), display 180ms allow-discrete;
  }
  .modal.open { opacity: 1; }
  @starting-style { .modal.open { opacity: 0; } }
}
@media (hover: none) {
  .game-card:hover { transform: none; }
  .game-card:active { transform: scale(.98); }
}
@media (max-width: 359px) {
  .fy-tabs { gap: 6px; padding-inline: 8px; }
  .fy-seg button { padding-inline: 9px; }
  .fy-push-btn { padding-inline: 10px; }
  .fy-rate { padding-inline: 10px; gap: 8px; }
  .fy-save { padding-inline: 12px; }
  .modal-panel { padding-inline: 16px; }
}
@media (max-width: 699px) { .fy-key-hint { display: none; } }
/* Snapping is a navigation rule, not a decorative animation. Keep it in
   reduced mode; only the glide and ambient/large-area motion are removed. */
@media (prefers-reduced-motion: reduce) {
  html:not([data-motion="full"]) { scroll-behavior: auto; }
  html:not([data-motion="full"]) *, html:not([data-motion="full"]) *::before, html:not([data-motion="full"]) *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}
html[data-motion="none"] { scroll-behavior: auto; }
html[data-motion="none"] *, html[data-motion="none"] *::before, html[data-motion="none"] *::after {
  animation-duration: .01ms !important; animation-iteration-count: 1 !important;
  transition-duration: .01ms !important; scroll-behavior: auto !important;
}

/* Login is a screen, not a sheet over the app (2026-10-20). On iPhone the
   keyboard rule below pinned the overlay to the VISIBLE viewport, so once iOS
   scrolled the layout viewport the overlay ended part-way down the screen and
   the live Home page showed (and scrolled) underneath it. Two halves:
   - every sheet's overlay keeps covering the whole layout viewport while a
     keyboard is up; the sheet is lifted by padding instead. layout height - kb
     is exactly the visible bottom, so the sheet lands where it did before.
   - while login is open the app behind it is not painted at all, so there is
     nothing to reveal even if iOS rubber-bands past the overlay. */
@media (max-width: 699px) {
  body.kb-up .modal { top: 0; bottom: 0; height: auto; padding-bottom: var(--kb, 0px); }
}
#auth-modal { background: var(--bg); backdrop-filter: none; }
html:has(#auth-modal.open), body:has(#auth-modal.open) { overscroll-behavior: none; background: var(--bg); }
body:has(#auth-modal.open) > :is(.wrap, #artist-hero, #player-bar, .activity, .activity-backdrop, .today-info-pop) { visibility: hidden; }

/* Crate grid on a phone: a fast flick showed whole cards blank (2026-10-20).
   Two causes stacked. content-visibility lets iOS skip painting a card until it
   is near, and on a fling it is late; and every card past the first screen ran
   its cardIn fade from opacity 0 the moment it was painted, so a card that did
   arrive arrived invisible. The grids that are scrolled fast on a touch screen
   paint everything (a crate of a few hundred lays out in well under a frame
   budget per scroll), the entrance is kept for the first screenful only, and
   the sleeve slot is tinted so a cover still decoding is a square, not a hole. */
@media (hover: none) {
  #albums-grid > *, #prof-rankings > * { content-visibility: visible; }
  /* Still blanking on a fling (2026-10-22): every card's rank badge had a
     backdrop blur, so a crate of a few hundred was a few hundred composited
     backdrop layers and WebKit ran out of tile memory mid-scroll — badges
     drawn as blurred smudges, whole rows black. The badge is opaque enough
     not to need it, and the score's 30px shadow was the other heavy paint. */
  .rank-badge { backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(10,9,8,.86); }
  .card .art-score { text-shadow: 0 1px 8px rgba(0,0,0,.8); }
}
.grid .card:nth-child(n+13) { animation: none; }
.card .art-wrap { background: var(--surface-2); }

/* The status bar is a band of the app's own ground, so content scrolling up
   passes behind the clock rather than through it. Zero tall wherever there is
   no top inset; under the nav (150) and every sheet. */
body::before {
  content: ''; position: fixed; top: 0; left: 0; right: 0; z-index: 140;
  height: env(safe-area-inset-top, 0px); pointer-events: none;
  background: linear-gradient(var(--bg) 70%, color-mix(in srgb, var(--bg) 0%, transparent));
}
body[data-section="foryou"]::before { display: none; }
