/*
 * Menealo design tokens — the single source of truth for every colour, space,
 * type step, radius, elevation and motion value in the app.
 *
 * WHY A PLAIN .css FILE AND NOT A LIT `css` EXPORT
 * ------------------------------------------------
 * Custom properties inherit *through* shadow boundaries, so declaring them once
 * on `:root` is enough for every component. Declaring them again inside a
 * component (`:host { --color-bg: … }`) would *win* over the inherited value
 * for that subtree, which would break the theme override — so the values have
 * to live at document level and only at document level.
 *
 * That leaves two ways to get them there: inject them from JS, or link a
 * stylesheet. This is a link, because it is render-blocking: the first paint is
 * already themed, with no flash, no JS, and no build step. `static/js/*` is
 * generated and gitignored; this file is not, so the page is styled even on a
 * fresh clone.
 *
 * `components/styles/tokens.ts` is the other half: the shared Lit `css`
 * fragments that *consume* these properties inside shadow roots. It contains no
 * literal values, which is what keeps the two halves from drifting.
 *
 * THEMING (no JS re-render, by construction)
 * ------------------------------------------
 * Dark is the default. The OS preference picks light; `data-theme` on <html>
 * overrides the OS in either direction:
 *
 *   document.documentElement.dataset.theme = "light" | "dark";
 *   delete document.documentElement.dataset.theme;   // back to the OS
 *
 * Density is the same mechanism:
 *
 *   document.documentElement.dataset.density = "compact" | "comfortable";
 *
 * Both are pure custom-property swaps. Nothing re-renders — which matters here
 * because `@property` decorators are stripped from the browser bundle and no
 * component property is reactive at runtime (CLAUDE.md build rule 3, #52).
 */

/* ==========================================================================
   Colour — dark (default)
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Spotify brand green. Fixed by their design guidelines: it is the brand
     mark, not a theme colour, so it does not change between themes. Use
     --color-accent for UI, and this only where the brand itself is shown. */
  --brand-spotify-green: #1db954;

  /* Surfaces, back to front. */
  --color-bg: #121212;
  --color-surface: #181818;
  --color-surface-raised: #282828;
  --color-surface-sunken: #0d0d0d;
  --color-surface-hover: #232323;
  --color-surface-active: #2f2f2f;

  /* Text. */
  --color-text: #ffffff;
  --color-text-muted: #b3b3b3;
  --color-text-subtle: #7c7c7c;
  --color-text-inverse: #121212;

  /* Lines. */
  --color-border: #2a2a2a;
  --color-border-strong: #3e3e3e;

  /* Accent. --color-accent is a fill; --color-accent-text is the version that
     is legible as text/iconography on --color-bg. They differ in the light
     theme, where the brand green fails contrast as a foreground. */
  --color-accent: #1db954;
  --color-accent-hover: #1ed760;
  --color-accent-text: #1ed760;
  --color-on-accent: #000000;

  /* Status. */
  --color-info: #4aa3f0;
  --color-success: #45c463;
  --color-warning: #f0a33c;
  --color-danger: #ff5c56;
  --color-on-status: #0d0d0d;

  /* Scrims and shadows. */
  --color-overlay: rgba(0, 0, 0, 0.66);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.6);
  --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.5);
  --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.6);
  --shadow-up: 0 -2px 10px rgba(0, 0, 0, 0.5);
}

/* Light. Applied when the OS asks for it and the user has not pinned dark. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;

    --color-bg: #f7f7f8;
    --color-surface: #ffffff;
    --color-surface-raised: #ffffff;
    --color-surface-sunken: #f0f0f1;
    --color-surface-hover: #f0f0f1;
    --color-surface-active: #e4e4e6;

    --color-text: #191414;
    --color-text-muted: #555555;
    --color-text-subtle: #767676;
    --color-text-inverse: #ffffff;

    --color-border: #e4e4e6;
    --color-border-strong: #c9c9cd;

    --color-accent: #1db954;
    --color-accent-hover: #19a349;
    --color-accent-text: #0f7a37;
    --color-on-accent: #000000;

    --color-info: #0b63ce;
    --color-success: #1e7d32;
    --color-warning: #a35a00;
    --color-danger: #c62828;
    --color-on-status: #ffffff;

    --color-overlay: rgba(0, 0, 0, 0.4);
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.08);
    --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.12);
    --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.16);
    --shadow-up: 0 -2px 10px rgba(0, 0, 0, 0.15);
  }
}

/* The explicit override. Duplicated from the media query above rather than
   factored out, because a `:where()` selector list cannot express "OS says
   light AND not pinned dark, OR pinned light" without also lowering
   specificity below `:root`. */
:root[data-theme="light"] {
  color-scheme: light;

  --color-bg: #f7f7f8;
  --color-surface: #ffffff;
  --color-surface-raised: #ffffff;
  --color-surface-sunken: #f0f0f1;
  --color-surface-hover: #f0f0f1;
  --color-surface-active: #e4e4e6;

  --color-text: #191414;
  --color-text-muted: #555555;
  --color-text-subtle: #767676;
  --color-text-inverse: #ffffff;

  --color-border: #e4e4e6;
  --color-border-strong: #c9c9cd;

  --color-accent: #1db954;
  --color-accent-hover: #19a349;
  --color-accent-text: #0f7a37;
  --color-on-accent: #000000;

  --color-info: #0b63ce;
  --color-success: #1e7d32;
  --color-warning: #a35a00;
  --color-danger: #c62828;
  --color-on-status: #ffffff;

  --color-overlay: rgba(0, 0, 0, 0.4);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.08);
  --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.12);
  --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.16);
  --shadow-up: 0 -2px 10px rgba(0, 0, 0, 0.15);
}

/* Derived colours. Declared once, after both themes, so they follow whichever
   theme is in effect instead of being restated per theme. */
:root {
  --color-accent-soft: color-mix(in srgb, var(--color-accent) 16%, transparent);
  --color-info-soft: color-mix(in srgb, var(--color-info) 16%, transparent);
  --color-success-soft: color-mix(
    in srgb,
    var(--color-success) 16%,
    transparent
  );
  --color-warning-soft: color-mix(
    in srgb,
    var(--color-warning) 16%,
    transparent
  );
  --color-danger-soft: color-mix(in srgb, var(--color-danger) 16%, transparent);
  --color-focus-ring: var(--color-accent-text);

  /* Content on a scrim or on a saturated fill (the hero close buttons, the
     album-art play overlay, the swipe actions). The scrim is dark in both
     themes, so this does not flip with the theme. */
  --color-on-scrim: #ffffff;
  --color-scrollbar-thumb: var(--color-border-strong);
  --color-scrollbar-track: transparent;
}

/* ==========================================================================
   Space — density-aware
   ========================================================================== */

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* Component-level rhythm the density switch actually moves. */
  --row-padding-y: var(--space-3);
  --section-gap: var(--space-5);
}

:root[data-density="compact"] {
  --space-1: 2px;
  --space-2: 6px;
  --space-3: 8px;
  --space-4: 12px;
  --space-5: 16px;
  --space-6: 24px;
  --space-7: 32px;
  --space-8: 48px;

  --row-padding-y: var(--space-2);
  --section-gap: var(--space-4);
}

/* ==========================================================================
   Type
   ========================================================================== */

:root {
  --font-sans:
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    Oxygen,
    Ubuntu,
    Cantarell,
    "Open Sans",
    "Helvetica Neue",
    sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-md: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.5rem;
  --font-size-2xl: 2rem;

  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;

  --line-height-tight: 1.25;
  --line-height-base: 1.6;
}

/* ==========================================================================
   Radii
   ========================================================================== */

:root {
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-pill: 999px;
  --radius-circle: 50%;

  /* Spotify's design guidelines prescribe the corner treatment for artwork:
     album/playlist art square, artist art circular. */
  --radius-art: 4px;
  --radius-artist: 50%;
}

/* ==========================================================================
   Motion
   ========================================================================== */

:root {
  --duration-fast: 120ms;
  --duration-base: 200ms;
  --duration-slow: 320ms;
  --easing-standard: cubic-bezier(0.2, 0, 0, 1);
  --easing-decelerate: cubic-bezier(0, 0, 0, 1);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 1ms;
    --duration-base: 1ms;
    --duration-slow: 1ms;
  }
}

/* ==========================================================================
   Layout
   ========================================================================== */

:root {
  /*
   * Below the desktop breakpoint the page fills the viewport and `main`'s own
   * padding does the insetting. A percentage here would only waste screen on a
   * phone, so this stays 100% and the desktop rule below is the override —
   * never the other way round, since a token whose only definition lives in a
   * media query is the trap the theme rules at the top of this file exist to
   * prevent.
   */
  --layout-max-width: 100%;
  --breakpoint-md: 768px;

  --z-header: 12;
  --z-sheet: 900;
  --z-player: 1000;
  --z-toast: 1100;
  /*
   * The full-screen hero overlays (artist, album, tag, track).
   *
   * Above everything else on purpose: a hero covers the whole viewport and is
   * modal in effect, so the player bar and a toast underneath it would be
   * unreachable decoration. The four heroes each had the literal 2000 instead
   * of a token, which is the one thing this file exists to prevent — and a
   * literal cannot be compared against the scale, which is how nobody noticed
   * that the number never applied. See the .main-content-container rule in
   * app-main.ts: a z-index only ranks a box against its siblings inside the
   * nearest stacking context, and the heroes sit two levels down inside one
   * capped at 10.
   */
  --z-hero: 2000;

  /* How tall the player bar is when it is on screen. One declaration, read by
     both the player itself and the page padding below — replacing the constants
     app-header used to hand-sync against audio-player's min-heights. */
  --player-bar-height: 120px;

  /* How much space the page reserves for it. Zero until the player says
     otherwise (see audio-player's data-player-visible toggle), so a logged-out
     visitor gets no dead band. */
  --player-height: 0px;

  /* The selection tray, same contract as the player: one declared height, read
     by the tray itself and by the page padding below. It is a single row on
     purpose — an earlier version wrapped its five buttons onto three lines,
     came out 151px tall, and reserved none of it, so it sat on top of the
     panel it was meant to sit under. */
  --tray-bar-height: 64px;
  --tray-height: 0px;
}

/* Desktop: track the viewport instead of capping at a constant.

   The old 1200px cap left most of a wide monitor empty — the whole point of a
   desktop browser at 1600px and up. 80vw is what was asked for, and the
   tradeoff it carries is worth stating: between roughly 1024px and 1500px wide
   this is *narrower* than the old cap, because 80% of 1280px is 1024px. If
   that reads as too tight on laptops, this one declaration is the dial —
   `max(1200px, 80vw)` keeps the old floor and only ever widens.

   `vw` rather than `%` on purpose: the token is also read by the four hero
   panels and by app-header, whose parents are not all full-width, and a
   percentage there would shrink them against their own container instead of
   against the page. As a `max-width` the used width is min(container, 80vw),
   so a value wider than the container simply does not bind — no overflow. */
@media (min-width: 1024px) {
  :root {
    --layout-max-width: 80vw;
  }
}

/* Taller on small screens because the bar becomes two rows there — the
   now-playing block, then full-width transport controls at thumb size. These
   numbers are measured against the rendered bar, not guessed: when they were
   guessed low the controls simply hung below it. */
@media (max-width: 768px) {
  :root {
    --player-bar-height: 165px;
  }
}

@media (max-width: 480px) {
  :root {
    --player-bar-height: 170px;
  }
}

/* Everything the page reserves along the bottom edge: the player, the tray,
   and the iOS home indicator. Anything that floats above the content — the
   back-to-top button, a sheet's bottom padding — offsets by this rather than
   restating the sum, which is the same "one declaration, read by both" rule
   the two bar-height tokens above exist for. Declared here and not inside a
   component: a long calc() restated in a `css` template is what `deno fmt`
   wraps, and a wrapped declaration shifts every rule after it two columns
   deeper, cumulatively (see the design-system notes in CLAUDE.md). */
:root {
  --docked-bottom: calc(var(--player-height) + var(--tray-height) + env(safe-area-inset-bottom, 0px));
}

:root[data-player-visible] {
  --player-height: var(--player-bar-height);
}

:root[data-tray-visible] {
  --tray-height: var(--tray-bar-height);
}

/* ==========================================================================
   Light-DOM base
   ========================================================================== */

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

* {
  margin: 0;
  padding: 0;
}

/*
 * Pull-to-refresh is off.
 *
 * The browser's overscroll gesture reloads the page, and a reload here is not
 * cheap: it tears down the Web Playback SDK device (the music stops mid-track),
 * restarts the playlist membership pass from zero (351 playlist reads), and
 * restarts the background tag sync. On a phone, scrolling a long track list
 * puts your thumb at the top of the document constantly, so the gesture fires
 * by accident — five times in a few minutes, in the report that prompted this.
 *
 * Nothing is lost by disabling it: reloading the library is a labeled action in
 * Sync & Health and in the command palette, which is the rule #70 set — no
 * feature reachable only by a gesture. This is the same rule read backwards: a
 * gesture that costs this much should not be the easiest way to trigger it.
 *
 * `overscroll-behavior` on the root element is what propagates to the viewport;
 * setting it on `body` alone does not (that is `overflow`'s rule, not this
 * one). `contain` rather than `none` so the scroll still stops at the boundary
 * normally — only the chaining and the refresh gesture go.
 */
html {
  overscroll-behavior-y: contain;
}

body {
  font-family: var(--font-sans);
  line-height: var(--line-height-base);
  color: var(--color-text);
  background-color: var(--color-bg);
  /*
   * Only ever non-zero while the player is on screen. The safe-area inset is
   * added unconditionally: on iOS the home indicator overlaps the bottom of
   * the viewport whether or not the player is docked.
   */
  padding-bottom: calc(
    var(--player-height) + var(--tray-height) + env(safe-area-inset-bottom, 0px)
  );
  transition: padding-bottom var(--duration-slow) var(--easing-standard);
  /*
   * Keeps scrollIntoView() — used by "jump to the playing track" — from
   * parking its target underneath the docked player, where it cannot be
   * scrolled clear.
   */
  scroll-padding-bottom: calc(var(--player-height) + var(--tray-height));
}

main {
  max-width: var(--layout-max-width);
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
}

footer {
  text-align: center;
  color: var(--color-text-muted);
  padding: var(--space-6) var(--space-4);
  font-size: var(--font-size-sm);
}

footer a {
  color: var(--color-accent-text);
  text-decoration: none;
}

footer a:hover {
  text-decoration: underline;
}

:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
