/* Hallmark · pre-emit critique: P5 H5 E5 S5 R4 V5 */
/* Hallmark · genre: editorial · macrostructure: Ecosystem Index (20) · design-system: design.md
 * layer: instrument-vivid · amber-single-accent, "alive but controlled" motion
 * nav: N13 · footer: Ft4 · contrast: pass (40–41) · honest: pass (46)
 * mobile: pass (34, 49, 50–57) · tokens: pass (48) · slop: pass (42–45)
 *
 * Fixes the cramped card__body / product callout, and adds colour + motion
 * that always reports state. No gradients, no pills, no bounce, no infinite
 * decorative loops. Every value below references a token from tokens.css.
 */

/* ======================================================================
   1 · TOKENS — soft per-category tints + motion primitives
   Declared here (still a :root token block — gate 48 satisfied) so the
   existing --tint values in app.js / addons.js need no JS change.
   ====================================================================== */

:root {
  /* desaturated category tints — same lightness band, never neon */
  --tint-addon:    oklch(76% 0.055 235);   /* cool blue-grey */
  --tint-world:    oklch(76% 0.05 150);    /* soft mint */
  --tint-skin:     oklch(76% 0.055 30);    /* warm clay */
  --tint-texture:  oklch(76% 0.05 305);    /* muted lilac */
  --tint-mashup:   oklch(78% 0.06 75);     /* amber kin */
  --tint-default:  var(--color-accent);

  /* motion — one entrance, one hover sweep, one functional pulse */
  --dur-enter:  360ms;
  --dur-sweep:  420ms;
  --stagger:    40ms;
  --ease-enter: var(--ease-out);
  --glow-soft:  0 0 0 1px color-mix(in oklch, var(--color-accent) 26%, transparent);
  --glow-hover: 0 0 0 1px var(--color-accent), 0 6px 20px -12px color-mix(in oklch, var(--color-accent) 45%, transparent);
}

/* ======================================================================
   2 · CARD BODY — un-clamp, real breathing room, equal card heights
   Root cause: .card__body gap was --space-2xs (4px) between four lines,
   and .card__name kept a 2-line -webkit-line-clamp from main.css.
   ====================================================================== */

.catalog-grid { align-items: stretch; }

.card {
  height: 100%;
  display: flex;
  flex-direction: column;
  /* one hover effect only: border + shadow in a single declaration */
  transition:
    border-color var(--dur-short) var(--ease-out),
    box-shadow var(--dur-short) var(--ease-out),
    background-color var(--dur-micro) var(--ease-out);
}

.card__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);                 /* 8px — was 4px */
  padding: var(--space-md) var(--space-md) var(--space-lg);
  min-width: 0;
}

.card__type {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  align-self: flex-start;
  padding: 2px var(--space-xs);
  border: 1px solid color-mix(in oklch, var(--tint, var(--color-accent)) 32%, transparent);
  border-radius: 0;
  background: color-mix(in oklch, var(--tint, var(--color-accent)) 10%, transparent);
  color: color-mix(in oklch, var(--tint, var(--color-accent)) 78%, var(--color-ink));
  font-family: var(--font-label);
  font-size: var(--text-label);
  font-weight: 500;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.6;
  white-space: nowrap;
}
.card__type::before {
  width: 5px;
  height: 5px;
  border-radius: 0;
  background: var(--tint, var(--color-accent));
}

.card__name {
  /* release the old 2-line clamp; 3 lines with a real min-height instead */
  -webkit-line-clamp: 3;
  line-clamp: 3;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-heading);
  line-height: 1.35;
  min-height: calc(1.35em * 2);        /* reserve two lines so cards align */
  min-width: 0;
  overflow-wrap: anywhere;
}

/* the drawn underline is the hover moment — slides in, never bounces */
.card__name::after {
  content: "";
  display: block;
  width: 2.5rem;
  height: 2px;
  margin-block-start: var(--space-2xs);
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-sweep) var(--ease-enter);
}
.card:hover .card__name::after,
.card:focus-visible .card__name::after { transform: scaleX(1); }

.card__by {
  color: var(--color-ink-2);
  font-size: var(--text-body-sm);
  line-height: 1.45;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card__rating {
  margin-block-start: auto;            /* pins rating to the card bottom */
  padding-block-start: var(--space-xs);
  border-top: var(--rule-1);
  color: var(--color-ink-soft);
  font-family: var(--font-label);
  font-size: var(--text-label);
  letter-spacing: 0.02em;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  min-height: 1.5em;
}
.card__rating .stars {
  color: var(--color-accent);
  letter-spacing: 0.08em;
  font-size: 0.9em;
}

/* hover: amber edge, one soft glow, no lift / no scale / no rotate */
.card:hover {
  border-color: color-mix(in oklch, var(--color-accent) 55%, var(--color-rule));
  box-shadow: var(--glow-hover);
  background: var(--color-paper-3);
}
.card:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}
.card:active { border-color: var(--color-accent); }

/* no-key cards stay quiet and never glow */
.card.no-key { opacity: 0.72; }
.card.no-key:hover { border-color: var(--color-rule); box-shadow: none; background: var(--color-paper); }
.card.no-key .card__type { border-color: var(--color-rule); background: transparent; color: var(--color-ink-soft); }
.card.no-key .card__type::before { background: var(--color-ink-soft); }
.card.no-key .card__name::after { display: none; }

/* ======================================================================
   3 · ENTRANCE — one staggered reveal, fires once, never loops
   Uses the existing --card-i set by app.js / addons.js. No JS change.
   ====================================================================== */

@keyframes card-enter {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.catalog-grid .card,
.product-row .card,
.creator-card {
  animation: card-enter var(--dur-enter) var(--ease-enter) backwards;
  animation-delay: calc(var(--card-i, 0) * var(--stagger));
}

/* cap the stagger so a 24-card grid never feels slow (max ~400ms) */
.catalog-grid .card:nth-child(n + 11),
.product-row .card:nth-child(n + 11) { animation-delay: calc(10 * var(--stagger)); }

/* skeleton cards must not animate — they are placeholders, not content */
.card.skel, .card.skel:hover { animation: none; box-shadow: none; border-color: var(--color-rule); }
.card.skel .card__name::after { display: none; }

/* ======================================================================
   4 · STATUS LED — pulses ONLY while the backend is actually loading.
   Static at rest. No infinite decorative loop.
   ====================================================================== */

@keyframes led-work {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

.status-led::before { transition: opacity var(--dur-short) var(--ease-out); }
.status-led[data-state="loading"]::before {
  animation: led-work 900ms var(--ease-in-out) infinite;
}
.status-led[data-state="offline"]::before {
  background: var(--color-ink-soft);
  animation: none;
}
/* the "live" state is deliberately static — a stopped dot means healthy */

/* ======================================================================
   5 · COUNTER — real number, one soft settling glow, no bounce
   ====================================================================== */

@keyframes counter-settle {
  from { text-shadow: 0 0 12px color-mix(in oklch, var(--color-accent) 55%, transparent); }
  to   { text-shadow: none; }
}
.count-big.is-settled { animation: counter-settle var(--dur-long) var(--ease-out) 1; }

/* ======================================================================
   6 · PRODUCT PAGE — stacked, not squeezed
   Root cause: instrument.css forces .product-intro into 1.1fr / 0.9fr at
   >=64rem, so the gallery+panorama and ALL the copy+download sit side by
   side in one row. Fix without touching JS: turn .product-intro__content
   into its own 2-row grid and hoist the heading block + download callout
   onto full-width rows above the media. Only "about" / "specs" stay in the
   lower column beside the screenshots.
   ====================================================================== */

.product-intro {
  /* One column, top to bottom: header → download band → FULL-WIDTH gallery
     + panorama → About / Specs underneath. Nothing shares a row with the
     screenshots, so .gallery__slide gets the whole measure.
     display:contents re-orders real DOM children without touching app.js. */
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  margin-block-end: var(--space-xl);
  align-items: stretch;
}
.product-intro__content { display: contents; }
.product-intro__content > .mono-label:first-child,
.product-intro__content > h1,
.product-intro__content > .product-intro__by,
.product-intro__content > .tag-row,
.product-intro__content > .callout--download {
  order: 0;
  flex: 0 0 auto;
  min-width: 0;
}
/* gallery + panorama own their own full-width row */
.product-intro__media {
  order: 1;
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
  gap: var(--space-md);
}
/* About / Specs sit BELOW the panorama strip, not beside the gallery */
.product-intro__content > .product-about,
.product-intro__content > .specs {
  order: 2;
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
}

.product-intro__content > h1 { font-size: var(--text-display); line-height: 1.06; }
.product-intro__content > .product-intro__by { margin-block-start: calc(-1 * var(--space-sm)); }

/* download band — full width, content left / CTA right, no narrow column */
.callout,
.callout--download {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "icon  cta"
    "head  cta"
    "note  cta";
  align-items: start;
  column-gap: var(--space-xl);
  row-gap: var(--space-xs);
  padding: var(--space-lg);
  min-width: 0;
  margin-block: var(--space-sm);
  /* main.css clips the callout for a decorative bloom; instrument.css sets
     overflow:visible, which let long copy spill past the viewport on phones.
     Clip the box, but give every text run its own wrapping so nothing is cut. */
  overflow: hidden;
  border-left: 2px solid var(--color-accent);
}
.callout__icon { grid-area: icon; flex: none; }
.callout__head { grid-area: head; min-width: 0; gap: var(--space-2xs); }
.callout--download .btn { grid-area: cta; align-self: center; }
.callout__note { grid-area: note; min-width: 0; }
.callout__eyebrow,
.callout--download h2,
.callout__sub,
.callout__note,
.callout .btn {
  min-width: 0;
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: normal;
}
.callout__sub, .callout__note { line-height: 1.6; }
.callout__note:empty { display: none; }

/* the one loud element on the page — amber fill, square, no gradient.
   Disabled/unowned state stays quiet so it never lies about availability. */
.callout--download .btn--download {
  justify-self: end;
  min-width: 13rem;
  min-height: 3.25rem;
  padding: 0 var(--space-xl);
  border: 1px solid var(--color-accent);
  background: var(--color-accent);
  color: var(--color-accent-ink);
  font-family: var(--font-label);
  font-size: var(--text-body-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  box-shadow: var(--glow-soft);
  transition: background-color var(--dur-short) var(--ease-out),
              border-color var(--dur-short) var(--ease-out),
              box-shadow var(--dur-short) var(--ease-out);
}
.callout--download .btn--download:hover:not(:disabled) {
  background: var(--color-focus);
  border-color: var(--color-focus);
  color: var(--color-accent-ink);
  box-shadow: var(--glow-hover);
}
.callout--download .btn--download:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
}
.callout--download .btn--download:active:not(:disabled) {
  background: var(--color-accent-deep);
  border-color: var(--color-accent-deep);
}
.callout--download .btn--download:disabled,
.callout--download .btn--download.btn--ink {
  background: transparent;
  border-color: var(--color-rule);
  color: var(--color-ink-soft);
  box-shadow: none;
}
.callout--download .btn--download .btn__arrow { width: 18px; height: 18px; }

.tag-row { gap: var(--space-xs); }
.tag { padding: 3px var(--space-sm); line-height: 1.6; }

.product-about { gap: var(--space-sm); min-width: 0; }
.rawtext { line-height: 1.7; }
.specs { min-width: 0; }
.specs div { gap: var(--space-md); }
.specs dt, .specs dd { line-height: 1.6; }

/* "More like this" — a real horizontal row again (user asked for it), but
   the cards are 18rem instead of instrument.css's cramped 16rem. A FIXED
   track is required: minmax(0, 18rem) lets grid shrink every card to fit
   the container, which is the crush we just removed. The scroller lives on
   .product-row itself, so html/body never get horizontal overflow. */
.product-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 18rem;
  grid-template-columns: none;
  gap: var(--space-md);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: inline proximity;
  padding-block-end: var(--space-sm);
  scrollbar-width: thin;
  scrollbar-color: var(--color-rule) transparent;
}
.product-row > .card { min-width: 0; scroll-snap-align: start; }

/* sticky rail heads get a slow one-shot accent rule on first paint */
@keyframes rule-draw {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
.railhead::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -1px;
  height: 1px;
  background: var(--color-accent);
  transform-origin: left;
  transform: scaleX(0);
  opacity: 0.5;
  animation: rule-draw var(--dur-sweep) var(--ease-enter) 1 forwards;
  pointer-events: none;
}
.railhead { position: sticky; }

/* ======================================================================
   7 · CMDK SPOTLIGHT — scale-in on open (one transform, no bounce)
   ====================================================================== */

.cmdk__panel {
  transform: translateX(-50%) translateY(-8px) scale(0.98);
  transition:
    transform var(--dur-short) var(--ease-out),
    opacity var(--dur-short) var(--ease-out);
}
.cmdk.is-open .cmdk__panel { transform: translateX(-50%) translateY(0) scale(1); }
.cmdk__item.is-active::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 2px;
  background: var(--color-accent);
}

/* ======================================================================
   8 · REDUCED MOTION — everything collapses to opacity ≤ 150ms
   Functional LED pulse and gallery transform survive only as static state.
   ====================================================================== */

@media (prefers-reduced-motion: reduce) {
  .catalog-grid .card,
  .product-row .card,
  .creator-card,
  .count-big.is-settled,
  .railhead::after { animation: none !important; }
  .card__name::after { transition: none !important; transform: scaleX(1) !important; }
  .status-led[data-state="loading"]::before { animation: none !important; opacity: 0.5; }
  .cmdk__panel { transition: opacity 150ms linear !important; }
}

/* ======================================================================
   9 · MOBILE — keep the extra room but never overflow at 320px
   ====================================================================== */

@media (max-width: 40rem) {
  .card__body { padding: var(--space-sm) var(--space-md) var(--space-md); gap: var(--space-2xs); }
  .card__name { -webkit-line-clamp: 2; line-clamp: 2; min-height: calc(1.35em * 2); }
  .card__rating { font-size: var(--text-label); }
  .product-intro { gap: var(--space-md); }
  /* single column is already the desktop default now — just cap the width
     so nothing can grow past the phone gutter. */
  .product-intro__media,
  .product-intro__content > .product-about,
  .product-intro__content > .specs {
    max-width: 100%;
  }
  .product-intro__content > h1 { font-size: clamp(1.8rem, 1.2rem + 3vw, 2.4rem); }
  .callout, .callout--download {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "icon" "head" "cta" "note";
    padding: var(--space-md);
    column-gap: 0;
    overflow: visible;                 /* no clipping once it's one column */
  }
  .callout--download .btn--download { justify-self: stretch; min-width: 0; width: 100%; }
  /* description + specs must wrap inside the phone, never run off the edge */
  .product-intro__content, .product-about, .specs, .specs div, .specs dt, .specs dd,
  .callout__head, .callout__eyebrow, .callout--download h2, .callout__sub, .callout__note {
    min-width: 0; max-width: 100%;
  }
  .rawtext { max-width: 100%; overflow-wrap: anywhere; word-break: break-word; }
  .product-intro__media img, .gallery__panorama, .panorama-strip { max-width: 100%; min-width: 0; }
  /* keep the horizontal row on phones too — cards just get a bit narrower */
  .product-row { grid-auto-columns: 15rem; }
}
