/* Page, controls panel, stage and device frame.
   Everything inside the phone screen is in player.css. */

:root {
  --panel-width: 360px;
  --settings-width: 360px; /* the Settings column, when open */
  --stage-padding: 32px;

  --page-bg: oklch(0.93 0 0);
  --panel-bg: oklch(0.99 0 0);
  --line: oklch(0.9 0 0);
  --hover: oklch(0.96 0 0);
  --selected: oklch(0.91 0 0);
  --text: oklch(0.22 0 0);
  --text-2: oklch(0.52 0 0);
  --focus: oklch(0.6 0.2 257);
  --note: oklch(0.52 0.13 55); /* fallback notes */
  --guide: oklch(0.65 0.26 350);

  color-scheme: light;
  font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Helvetica Neue", system-ui, sans-serif;
  color: var(--text);
}

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

html, body { height: 100%; margin: 0; }
body { overflow: hidden; background: var(--page-bg); }

.app {
  display: grid;
  grid-template-columns: var(--panel-width) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  height: 100%;
}

/* With Settings open, the panel widens by a column between the list and the stage */
.app:has(.settings-panel:not([hidden])) {
  grid-template-columns: var(--panel-width) var(--settings-width) minmax(0, 1fr);
}

/* Controls panel */

.panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--panel-bg);
  border-right: 1px solid var(--line);
}

.panel-header { padding: 20px 20px 16px; border-bottom: 1px solid var(--line); }
.panel-header h1 { margin: 0; font-size: 15px; font-weight: 600; }
.panel-header p { margin: 2px 0 0; color: var(--text-2); }

.panel-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 20px;
  scroll-padding-block: 16px; /* rows revealed by ↑/↓ keep the padding clear */
}

.section-title {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-2);
}

.section-title .count { margin-left: 2px; font-weight: 400; }

/* List title, with the colour rule (or its fallback note) on the right */
.list-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.list-head .section-title { margin: 0; }

.rule-summary {
  overflow: hidden;
  font-size: 12px;
  color: var(--text-2);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.rule-summary.is-fallback,
.colour-note { color: var(--note); }

.placeholder { margin: 0; color: var(--text-2); }

/* Settings: the prototype's settings in their own column next to the list, so the list
   keeps its height. One section per setting, each titled with its keys on the right:
   Colour (applied colour, palette and swatches), then Gradient, Mesh, White and Layout. Closed by default;
   S or the footer toggle opens it. In very short windows it scrolls on its own. */

.settings-panel {
  min-height: 0;
  overflow-y: auto;
  padding: 20px;
  background: var(--panel-bg);
  border-right: 1px solid var(--line);
}

.settings-section + .settings-section {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.settings-title { display: flex; justify-content: space-between; align-items: center; }
.settings-keys { font-weight: 400; letter-spacing: 0; }
.settings-hint { margin: 4px 0 0; color: var(--text-2); }
.layout-options { display: flex; gap: 16px; } /* the two layout radios, side by side */

/* Mesh (8): the sliders, name · slider · value, dimmed while the mesh is off */
.mesh-params {
  display: grid;
  gap: 2px;
  min-width: 0;
  margin: 8px 0 0;
  padding: 0;
  border: 0;
}

.mesh-params:disabled { color: var(--text-2); }

.mesh-param {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr) 32px;
  align-items: center;
  gap: 8px;
}

.mesh-param input { margin: 0; accent-color: var(--guide); }
.mesh-param output { font-variant-numeric: tabular-nums; text-align: right; }

/* The spots, read only, laid out as the chips (below) without their highlight */
.mesh-spots {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px 8px;
}

.mesh-spot {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  gap: 2px 6px;
  align-items: center;
  padding: 5px 0;
  font-size: 11px;
  line-height: 14px;
}

.mesh-spot .colour-value { grid-column: 1 / -1; overflow: hidden; text-overflow: ellipsis; color: var(--text-2); }

.colour-applied {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  min-height: 52px;
}

.colour-applied > div { display: grid; }

.colour-applied-label { font-weight: 600; }
.colour-applied-contrast,
.colour-applied-detail,
.colour-message { color: var(--text-2); }

.colour-value { font-variant-numeric: tabular-nums; white-space: nowrap; }

.sub-title {
  margin: 14px 0 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text);
}

.sub-title-detail { font-weight: 400; color: var(--text-2); }

/* Chips reach 6 px into the column padding, like the list rows, so the highlight
   has room around the content */
.chips {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px 8px;
  margin: 0 -6px;
}

/* Each chip is a colour rule: a radio over the whole chip.
   Swatch and name on the first line, the value full width below it.
   Selected: grey fill and a dark ring, which turns blue for keyboard focus.
   Dashed ring: the palette colour Auto picked, or Dominant #1 painting in place of
   a rule this artwork lacks. */
.chip {
  --ring: var(--text);

  position: relative;
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  gap: 2px 6px;
  align-items: center;
  padding: 5px 6px;
  border-radius: 6px;
  font-size: 11px;
  line-height: 14px;
  cursor: pointer;
}

.chip input { position: absolute; margin: 0; opacity: 0; pointer-events: none; }

.chip:hover { background: var(--hover); }
.chip:has(input:checked) { background: var(--selected); box-shadow: inset 0 0 0 1.5px var(--ring); }
.chip:has(input:focus-visible) { --ring: var(--focus); box-shadow: inset 0 0 0 1.5px var(--ring); }
.chip.is-substitute { outline: 1.5px dashed var(--text-2); outline-offset: -1.5px; }
.chip:has(input:disabled) { cursor: default; }
.chip:has(input:disabled):not(:has(input:checked)) { background: none; }

.chip .colour-value { grid-column: 1 / -1; }
.chip-name { font-weight: 500; } /* also on the mesh's spots */
.chip-detail { font-weight: 400; color: var(--text-2); font-variant-numeric: tabular-nums; }
.chip .colour-value { overflow: hidden; text-overflow: ellipsis; color: var(--text-2); }
.chip.is-empty .chip-name { color: var(--text-2); }

.colour-applied-swatch,
.chip-swatch {
  width: 44px;
  height: 44px;
  border-radius: 8px;
  background: var(--swatch);
  box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.1); /* edge for near-white colours */
}

.chip-swatch { width: 14px; height: 14px; border-radius: 4px; }

/* No colour: an empty dashed outline */
.colour-applied-swatch:not([style]),
.chip-swatch:not([style]) {
  background: none;
  box-shadow: none;
  outline: 1px dashed oklch(0 0 0 / 0.25);
  outline-offset: -1px;
}

/* Podcast list. Rows reach 8 px into the panel padding, so the highlight has room around the content. */

.podcast-list {
  display: grid;
  gap: 2px;
  margin: 0 -8px;
  padding: 0;
  list-style: none;
}

.podcast-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 8px;
  font: inherit;
  color: inherit;
  text-align: left;
  background: none;
  cursor: pointer;
}

.podcast-row:hover { background: var(--hover); }
.podcast-row[aria-current="true"] { background: var(--selected); }

.podcast-thumb {
  width: 40px;
  height: 40px;
  border-radius: 6px;
  object-fit: cover;
  background: var(--line);
  outline: 1px solid oklch(0 0 0 / 0.08); /* edge for white artwork */
  outline-offset: -1px;
}

/* Long titles wrap to 2 lines at most */
.podcast-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Footer: prototype controls (guides, Settings, finish) */

.panel-footer {
  display: flex;
  align-items: center;
  gap: 12px; /* 16 until Step 6: "Settings" is 14 px wider than "Colour" was */
  padding: 12px 20px;
  border-top: 1px solid var(--line);
  color: var(--text-2);
}

.toggle { display: inline-flex; align-items: center; gap: 6px; color: var(--text); cursor: pointer; }
.toggle input { margin: 0; accent-color: var(--guide); }

/* Finish picker: one colour dot per bezel finish, over visually hidden radios.
   The ring marks the selected finish and turns blue for keyboard focus. */

.finish { display: inline-flex; align-items: center; gap: 6px; color: var(--text); }

/* Read by screen readers only: the footer has no room for the word next to Guides and Settings */
.finish-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.finish-options { display: inline-flex; }

.finish-option { position: relative; padding: 4px; cursor: pointer; }
.finish-option input { position: absolute; margin: 0; opacity: 0; pointer-events: none; }

.finish-dot {
  --edge: inset 0 0 0 1px oklch(0 0 0 / 0.15); /* so the light finishes read on the panel */
  --ring: var(--text);

  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--swatch);
  box-shadow: var(--edge);
}

.finish-option input:checked + .finish-dot {
  box-shadow: var(--edge), 0 0 0 2px var(--panel-bg), 0 0 0 3.5px var(--ring);
}

.finish-option input:focus-visible + .finish-dot { --ring: var(--focus); }

kbd {
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
  font: 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text-2);
  background: var(--page-bg);
}

.scale-readout { font-variant-numeric: tabular-nums; }

/* Stage: centres the device inside a fixed margin. device.js sets --device-scale. */

.stage {
  --device-scale: 1;

  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  padding: var(--stage-padding);
  overflow: hidden;
}

/* Hidden until device.js has measured the stage, so it never flashes at 100% */
.stage:not(.is-measured) .device-fit { visibility: hidden; }

/* Device: the 450 × 920 pt bezel, built at 1 pt = 1 CSS px and scaled as one unit.
   .device-fit takes up the scaled size in layout; .device scales from its top-left corner. */

.device-fit {
  width: calc(450px * var(--device-scale));
  height: calc(920px * var(--device-scale));
}

.device {
  position: relative;
  width: 450px;
  height: 920px;
  transform: scale(var(--device-scale));
  transform-origin: 0 0;
  user-select: none;
}

/* Screen: the bezel's cutout is 402 × 874 pt at (24, 23).
   .screen paints 2 pt further under the bezel on every side, so no hairline seam
   shows at fractional scales. .player is the exact 402 × 874 frame, like the Figma frame.
   The 46 pt radius keeps the square corners from poking out past the phone's rounded
   outer edge, while still covering the cutout's corners (≥ 5 pt either way, measured
   from the PNG's alpha). */

/* Registered as colours, so changes to them can animate */
@property --player-bg { syntax: "<color>"; inherits: true; initial-value: oklch(0.3 0 0); }
@property --player-fg { syntax: "<color>"; inherits: true; initial-value: oklch(1 0 0); }

.screen {
  /* Player colours, all OKLCH. Everything in player.css paints with these.
     colour.js sets --player-bg and --player-fg from the artwork, and makes fg white
     with the gradient. The mesh (8) is painted from the artwork by mesh.js, over bg */
  --player-bg: oklch(0.3 0 0);
  --player-fg: oklch(1 0 0);
  /* fg's hue goes in missing, so the mix keeps bg's hue (tokens.css, colour mixes) */
  --player-fg-2: color-mix(in oklch, oklch(from var(--player-fg) l c none) 70%, var(--player-bg)); /* Figma: white 70% (5d checkpoint) */
  /* Over the background, under everything else; on with the Gradient setting (B, Step 6).
     Given as rgba(0, 0, 0, 0.58 / 0.34 / 0.47 / 0.80). Every stop is black, so only the
     alpha changes: it looks the same interpolated in Oklab as in sRGB. It spans all of
     .screen, 2 pt past the frame at each end, so each stop is within 2 pt of its place
     on the 874 frame. If the stops change, recalculate --player-cutout (tokens.css) */
  --player-gradient: linear-gradient(180deg,
    oklch(0 0 0 / 0.58) 0%,
    oklch(0 0 0 / 0.34) 12.42%,
    oklch(0 0 0 / 0.47) 54.81%,
    oklch(0 0 0 / 0.8) 100%);

  position: absolute;
  left: 22px;
  top: 21px;
  width: 406px;
  height: 878px;
  overflow: hidden;
  border-radius: 46px;
  background: var(--player-bg);
  isolation: isolate; /* so the mesh's z-index -1 stays above this background */
  transition: --player-bg 500ms ease, --player-fg 500ms ease;
}

/* The mesh gradient (8): the shader's canvas (mesh.js) over the background colour and
   under the gradient and the player, in place of the plain colour. On with the Mesh
   setting (M); it fades in and out with the colours' 500 ms, as the gradient does */
.mesh {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  transition: opacity 500ms ease;
}

.screen.has-mesh .mesh { opacity: 1; }

/* The gradient fades in and out with the colours' 500 ms, so it and the text turning
   white read as one change */
.screen::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--player-gradient);
  opacity: 0;
  transition: opacity 500ms ease;
}

/* With the gradient the UI is white (colour.js), and the podcast name goes translucent,
   so the gradient shows through it; the other tints are in tokens.css. With the mesh it
   goes translucent too, in fg, so the mesh shows through it */
.screen.has-gradient,
.screen.has-mesh {
  --player-fg-2: oklch(from var(--player-fg) l c h / 0.7); /* Figma: white 70% */
}

.screen.has-gradient::before { opacity: 1; }

.player {
  position: absolute;
  inset: 2px;
}

.bezel {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Guides: screen outline, centre lines and size label, drawn above the bezel.
   Sizes are divided by the scale, so lines stay 1 CSS px and the label 11 px at any size. */

.guides {
  --hairline: calc(1px / var(--device-scale));

  display: none;
  position: absolute;
  left: 24px;
  top: 23px;
  width: 402px;
  height: 874px;
  box-shadow: inset 0 0 0 var(--hairline) var(--guide);
  pointer-events: none;
}

.device.show-guides .guides { display: block; }

.guides::before,
.guides::after {
  content: "";
  position: absolute;
  background: var(--guide);
}

/* Vertical centre line */
.guides::before { left: 50%; top: 0; bottom: 0; width: var(--hairline); translate: -50% 0; }

/* Horizontal centre line */
.guides::after { top: 50%; left: 0; right: 0; height: var(--hairline); translate: 0 -50%; }

.guides-label {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% 0;
  margin-top: calc(8px / var(--device-scale));
  padding: 0.15em 0.5em;
  border-radius: 0.35em;
  font: 600 calc(11px / var(--device-scale)) / 1.3 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  color: oklch(1 0 0);
  background: var(--guide);
  white-space: nowrap;
}
