/**
 * palettes.css
 * -----------------------------------------------------------------------
 * Styles for /palettes only. A fast, Color Hunt-style grid with nothing
 * to configure, so it gets its own small stylesheet rather than carrying
 * the visual weight of a configurable system (score badges, role labels,
 * filter chips). Reuses only the site-wide primitives from styles.css
 * (--action/--paper/--card/--radius/--sans/--grid-line/--text/
 * --text-muted, .wrap, .btn, .toast) — no legacy .badge anywhere here.
 *
 * Visual pass (palette browsing upgrade): the color plate is now the
 * dominant element on every card, the grid is denser above 1160px, and
 * the page header is grouped into one band so the eye runs
 * title -> description -> grid without a gap. Purely
 * visual — palettes.js, the DOM it renders, and every interaction it
 * wires up are untouched. Selectors, data attributes and the inline
 * --sw-hex/--sw-label custom properties are exactly the ones that file
 * already produces.
 *
 * Local design decisions worth knowing before editing:
 *   - --palette-radius is scoped to this page rather than changing the
 *     global --radius (3px) in styles.css, which every other page uses.
 *   - .palette-card's own `box-shadow` property is RESERVED for
 *     .palette-card--highlight (the deep-link ring) and keeps its
 *     original 600ms fade. The hover shadow therefore lives on a
 *     ::after overlay with its own 180ms opacity transition, so the two
 *     never have to share a duration. .palette-card--highlight is
 *     declared after the hover rules on purpose — equal specificity, so
 *     source order is what keeps the deep-link ring winning.
 *   - .palette-card no longer sets overflow:hidden; the clipping moved
 *     to .palette-card__bars (the only thing that needs it), which is
 *     what lets the hover shadow render outside the card box.
 * -----------------------------------------------------------------------
 */

.palettes-page {
  /* Softer corner than the global 3px --radius, scoped to this page so
     the color plates read as tactile swatches rather than table cells.
     styles.css's :root is not touched. */
  --palette-radius: 10px;
  --palette-ease: cubic-bezier(0.2, 0.6, 0.3, 1);
}

/* Tighten the top of the page: .guides-main ships 44px of padding-top
   for the guide grid, which left ~84px of air above this h1 once
   .palettes-hero's own padding stacked on top of it. */
.palettes-page .guides-main {
  padding-top: 28px;
}

/* ---------- header band ---------- */
/* Title and description closed by a single hairline, so the intro reads
   as one unit attached to the grid below. */
.palettes-head {
  border-bottom: 1px solid var(--grid-line);
  margin-bottom: 26px;
}

/* Page-intro scale from styles.css (--intro-*): the title is the first
   thing on the page, then a clear gap, then the description, then room
   before the sort tabs. */
.palettes-hero {
  padding: var(--intro-space-top) 0 clamp(32px, 3vw + 16px, 64px);
}
.palettes-hero h1 {
  font-size: var(--intro-title-hero);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text);
  overflow-wrap: break-word;
  text-wrap: balance;
}
.palettes-hero p {
  max-width: 800px;
  margin-top: var(--intro-title-gap);
  color: var(--text-muted);
  font-size: var(--intro-dek);
  line-height: 1.45;
}

/* ---------- grid ---------- */
/* minmax(0, 1fr) rather than 1fr so a column can never be widened past
   its share by its own content — that is what guarantees no horizontal
   overflow at any width. */
.palettes-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 26px 20px;
  padding-bottom: 24px;
}

@media (max-width: 1159px) {
  .palettes-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 24px 20px;
  }
}

@media (max-width: 959px) {
  .palettes-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 22px 18px;
  }
}

@media (max-width: 459px) {
  .palettes-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }
}

/* Reserve a screen of space while palettes-data.json loads. The grid is
   empty until palettes.js renders it, so without this the footer is drawn
   inside the first screen and then pushed off it — a large layout shift
   (CLS ~0.51 on mobile). If the load fails, the error message shows
   straight away instead of below an empty screen. */
.palettes-grid:empty {
  min-height: 100vh;
}

.palettes-grid:empty:has(~ .empty-state[data-visible="true"]) {
  min-height: 0;
}

/* ---------- card ---------- */
.palette-card {
  position: relative;
  border-radius: var(--palette-radius, 10px);
  background: var(--card);
  /* Subtle shadow, present at rest and unaffected by hover — the card
     itself no longer moves or gains any shadow on hover. Only
     .palette-card--highlight (the deep-link ring) below overrides this
     box-shadow, at equal specificity via source order; the 600ms
     transition is for that ring fading in and back out. */
  transition: box-shadow 0.6s ease;
  animation: palette-card-in 0.2s var(--palette-ease) backwards;
}
/* `backwards`, not `both`: a forwards fill would keep the keyframe's
   transform applied after the animation ends and silently cancel the
   hover lift. */
@keyframes palette-card-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* Restrained stagger: 12ms steps across the first twelve cards, then a
   flat 132ms for the rest, so the grid settles in well under a quarter
   second no matter how many palettes are in the data. */
.palette-card:nth-child(2) {
  animation-delay: 12ms;
}
.palette-card:nth-child(3) {
  animation-delay: 24ms;
}
.palette-card:nth-child(4) {
  animation-delay: 36ms;
}
.palette-card:nth-child(5) {
  animation-delay: 48ms;
}
.palette-card:nth-child(6) {
  animation-delay: 60ms;
}
.palette-card:nth-child(7) {
  animation-delay: 72ms;
}
.palette-card:nth-child(8) {
  animation-delay: 84ms;
}
.palette-card:nth-child(9) {
  animation-delay: 96ms;
}
.palette-card:nth-child(10) {
  animation-delay: 108ms;
}
.palette-card:nth-child(11) {
  animation-delay: 120ms;
}
.palette-card:nth-child(n + 12) {
  animation-delay: 132ms;
}

/* Applied briefly by palettes.js when the page loads at /palettes#<id>
   (e.g. from a Search result) so the linked-to card is findable, not
   just present somewhere in the grid. Removed automatically after a
   couple of seconds; the 0.6s box-shadow transition above fades it out.
   MUST stay declared after .palette-card — same specificity, so
   source order is what keeps the deep-link ring winning over the
   resting shadow. */
.palette-card--highlight {
  box-shadow:
    0 0 0 3px var(--action),
    0 0 24px 2px rgba(37, 99, 235, 0.35);
}

/* ---------- color plate ---------- */
.palette-card__bars {
  position: relative;
  display: flex;
  height: 150px;
  border-radius: var(--palette-radius, 10px);
  overflow: hidden;
  /* Lets .palette-swatch__hex size itself against the plate's width
     (cqi) instead of the viewport, so the label scales with the card at
     every column count. */
  container-type: inline-size;
}
/* Hairline containment ring. It lives on a positioned ::after rather
   than as an inset shadow on .palette-card__bars itself because the
   swatch buttons are positioned children and would paint straight over
   the parent's inset shadow. Keeps pale palettes from bleeding into
   the white sheet without drawing a box around the card. */
.palette-card__bars::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(15, 42, 82, 0.06);
}
@media (max-width: 719px) {
  .palette-card__bars {
    height: 132px;
  }
}

.palette-swatch {
  flex: 1 1 0;
  min-width: 0;
  border: 0;
  padding: 0;
  cursor: pointer;
  background: var(--sw-hex);
  position: relative;
  /* Expansion animates flex-grow rather than transform: the strips share
     a fixed-width flex line, so one growing means the others shrink by
     the same total — the plate, the card and the grid never move. A
     transform would instead have to escape .palette-card__bars'
     overflow:hidden, need a z-index to out-paint its siblings, and leave
     the HEX label centred on the old 53px strip instead of the new one. */
  transition: flex-grow 0.2s var(--palette-ease);
}
/* Guarded on a fine pointer: iOS Safari applies :hover on tap and leaves
   it applied until you tap elsewhere, which would strand one strip
   expanded. Touch keeps tap-to-copy with no stuck state. */
@media (hover: hover) and (pointer: fine) {
  .palette-swatch:hover {
    flex-grow: 1.9;
  }
}
.palette-swatch:focus-visible {
  flex-grow: 1.9;
  outline-offset: -3px;
}
.palette-swatch__hex {
  /* Fills the strip and centres the label on both axes, the same way
     .palette-swatch__copied centres its confirmation. */
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 2px;
  font-family: var(--sans);
  /* Two tiers. At rest a strip is 1/4 of the plate, which is all the
     room the label gets on touch (sticky :hover, no expansion) and under
     reduced motion — this size fits that. The expanded strip below gets
     the larger, Color Library-scale size. 12px is the fallback for
     engines without container units. */
  font-size: 12px;
  font-size: clamp(10px, 3.9cqi, 13px);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: var(--sw-label);
  opacity: 0;
  transform: translateY(3px);
  transition:
    opacity 0.16s var(--palette-ease),
    transform 0.16s var(--palette-ease);
}
.palette-swatch:hover .palette-swatch__hex,
.palette-swatch:focus-visible .palette-swatch__hex {
  opacity: 1;
  transform: translateY(0);
}
/* Larger tier, only where the strip actually expands (same conditions
   as the flex-grow rules above): an expanded strip is ~39% of the
   plate, so 6cqi still leaves side room for a 7-character HEX. */
@media (hover: hover) and (pointer: fine) {
  .palette-swatch:hover .palette-swatch__hex {
    font-size: 16px;
    font-size: clamp(13px, 6cqi, 18px);
  }
}
.palette-swatch:focus-visible .palette-swatch__hex {
  font-size: 16px;
  font-size: clamp(13px, 6cqi, 18px);
}

/* ---------- copied state ---------- */
/* Copy confirmation now lives inside the strip that was clicked,
   replacing the site-wide .toast palettes.js used to fire on a
   successful copy. The toast is still used for the failure case, where
   there is nothing to confirm inside the swatch.
   Everything here is opacity on an absolutely positioned overlay, so
   the strip, the plate, the card and the grid cannot move — the
   flex-grow expansion above stays the only thing that resizes.
   --sw-veil is emitted inline by palettes.js alongside the --sw-label
   it already set, from the same YIQ split, so scrim and text always
   agree on polarity.
   MUST stay declared after the :hover/:focus-visible reveal above —
   equal specificity, so source order is what lets the copied state
   hide the HEX label while the pointer is still on the strip. */
.palette-swatch__copied {
  position: absolute;
  inset: 0;
  display: flex;
  /* Stacked, not inline: a strip is ~53px wide at the 2- and 3-column
     breakpoints, which a check icon plus "Copied" on one line would
     overflow. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: var(--sw-veil, rgba(15, 42, 82, 0.34));
  color: var(--sw-label);
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--palette-ease);
}
.palette-swatch__copied svg {
  width: 13px;
  height: 13px;
  flex: none;
  opacity: 0.85;
}
.palette-swatch[data-copied="true"] .palette-swatch__copied {
  opacity: 1;
  transition: opacity 0.12s var(--palette-ease);
}
/* Fade only: the label keeps whatever transform the reveal above gave
   it, so it drops out under the scrim without sliding first. */
.palette-swatch[data-copied="true"] .palette-swatch__hex {
  opacity: 0;
}

/* ---------- card foot ---------- */
/* Near-zero horizontal padding so the heart lines up with the left
   edge of the color plate — the metadata sits under the palette rather
   than inside a box with it. */
.palette-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 2px 0;
}
.palette-like-btn {
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px 2px 0;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 12.5px;
  font-weight: 600;
  font-family: var(--sans);
  transition: color 0.16s var(--palette-ease);
}
.palette-like-btn svg {
  width: 18px;
  height: 18px;
  transition:
    transform 0.16s var(--palette-ease),
    fill 0.16s var(--palette-ease),
    stroke 0.16s var(--palette-ease);
}
.palette-like-btn:hover {
  color: var(--action);
}
.palette-like-btn:hover svg {
  transform: scale(1.08);
}
.palette-like-btn:active svg {
  transform: scale(0.9);
}
/* Declared after :hover so a liked palette stays pink while hovered. */
.palette-like-btn[aria-pressed="true"],
.palette-like-btn[aria-pressed="true"]:hover {
  color: #e0245e;
}
.palette-like-btn[aria-pressed="true"] svg {
  fill: #e0245e;
  stroke: #e0245e;
}
/* Save to the account (docs/SAVED.md): drawn by palettes.js beside the
   heart only once Saved is launched, so nothing here matches before then.
   A bookmark in the site's action blue rather than a second pink heart,
   so Save and Like never read as the same control. saved.js sets
   aria-pressed, and aria-busy while a request runs. 24px minimum so the
   target stays usable next to the heart. */
.palette-save-btn {
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  padding: 2px 3px;
  margin-left: 4px;
  cursor: pointer;
  color: var(--text-muted);
  transition:
    color 0.16s var(--palette-ease),
    opacity 0.16s var(--palette-ease);
}
.palette-save-btn svg {
  width: 18px;
  height: 18px;
  transition:
    transform 0.16s var(--palette-ease),
    fill 0.16s var(--palette-ease);
}
.palette-save-btn:hover,
.palette-save-btn:focus-visible {
  color: var(--action);
}
.palette-save-btn:hover svg {
  transform: scale(1.08);
}
.palette-save-btn:active svg {
  transform: scale(0.9);
}
/* Saved: filled in the action blue, hovered or not. */
.palette-save-btn[aria-pressed="true"] {
  color: var(--action);
}
.palette-save-btn[aria-pressed="true"] svg {
  fill: currentColor;
}
.palette-save-btn[aria-busy="true"] {
  cursor: progress;
  opacity: 0.55;
}
/* This palette's 4 color names (palettes-data.json's names[], joined
   with " · "), moved here from the swatches as plain, non-interactive
   text. Shrinks below its own content width (min-width:0) and clips
   with an ellipsis rather than wrapping, so a long name list can never
   grow the card's height or push the like button out of place. */
.palette-card__names {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-family: var(--sans);
  font-size: 11px;
  color: var(--text-muted);
  text-align: right;
  padding-left: 10px;
}

.palette-card__note {
  font-size: 11px;
  color: var(--text-muted);
  font-family: var(--sans);
}

/* ---------- reduced motion ---------- */
/* styles.css already forces every animation-duration and
   transition-duration to 0.001ms site-wide, but it does not clear
   animation-delay — which would leave staggered cards invisible for up
   to 132ms before snapping in. Cancelling the entrance outright, and
   the transform-based feedback with it, is the honest reading of the
   preference: the hover shadow and the hex reveal still fire, they just
   don't move anything. */
@media (prefers-reduced-motion: reduce) {
  .palette-card {
    animation: none !important;
  }
  .palette-card:hover {
    transform: none;
  }
  .palette-swatch__hex {
    transform: none;
  }
  .palette-like-btn:hover svg,
  .palette-like-btn:active svg,
  .palette-save-btn:hover svg,
  .palette-save-btn:active svg {
    transform: none;
  }
  /* Zeroing the duration is not enough here: the strip would still snap
     from 53px to 82px, which is a bigger jolt than the animation it
     replaced. The expansion is switched off outright instead — the HEX
     reveal still responds, it just doesn't resize. */
  .palette-swatch:hover,
  .palette-swatch:focus-visible {
    flex-grow: 1;
  }
  /* No expansion, so the label stays at the at-rest tier. */
  .palette-swatch:hover .palette-swatch__hex,
  .palette-swatch:focus-visible .palette-swatch__hex {
    font-size: 12px;
    font-size: clamp(10px, 3.9cqi, 13px);
  }
}

/* Empty/error state reuses the site-wide .empty-state component from
   styles.css (display:none by default, [data-visible="true"] shows
   it) — nothing to add here. */
