/*
 * HeyHi design system: product UI cards. A product appears as one of its own screens: a call, a list of deadlines, a
 * lead, a test run. They're the main way a product shows up on the site, so each is drawn as the real screen would be:
 * white, ruled in hairlines and near-monochrome, with amber only for the AI's voice and for what needs attention; a head
 * with its title, its meta in mono and, on a card whose details are made up, a small mono "Example" tag; tabular figures
 * and mono times. Each card kind is a partial in app/views/products/previews, filled from config/products.yml (or a
 * helper, for the homepage's own call).
 *
 * A card is a light screen wherever it sits: the partial gives it .surface-light (00_tokens.css), so inside a night band
 * its colour roles stay the light ones. It takes its width from where it's put, and its parts rearrange to that width on
 * their own (container queries), so the same card reads as well in a narrow plate as in a wide stage.
 *
 *   .ui-card                 the card: a head and its kind's body, ringed by a hairline
 *   .ui-card--float          over a photograph or on a stage: the window's shadow, so it stands in the scene
 *   .ui-card--wide           a whole screen, such as a call's transcript
 *   .stage                   a tinted panel a card sits on when there's no photograph
 */

/* ---------- The card ---------- */
/* A screen's own measures. Its type runs a step below the page's, as an app's does; the mono runs smaller still. */
.ui-card {
  --ui-ghost: 0.22;              /* what's not yet said or done: there, but faint, until it inks in */
  --ui-pad: 16px;                /* the screen's side margin */
  --ui-text-meta: 0.71875rem;    /* 11.5px: mono metadata, such as a time */
  --ui-text-small: 0.78125rem;   /* 12.5px: who's speaking, a row's note, a state */

  position: relative; overflow: hidden; isolation: isolate;
  background: var(--surface); color: var(--ink);
  border-radius: var(--radius-md); box-shadow: var(--shadow-ring);
  font-family: var(--font-text); font-size: var(--text-ui); line-height: 1.45; letter-spacing: -0.003em;
  font-variant-numeric: tabular-nums;
  text-align: left;
}
.ui-card--float { box-shadow: var(--shadow-window); }

/* The head: what the screen is, then its meta and the Example tag on the right, which go together under the title when
   they don't fit beside it */
.ui-card__head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  min-height: 48px; padding: 9px var(--ui-pad);
  border-bottom: 1px solid var(--line);
}
.ui-card__icon {
  display: grid; place-items: center; width: 26px; height: 26px; flex: none; border-radius: 50%;
  background: var(--amber); color: var(--c-ink);
}
.ui-card__icon .icon { width: 13px; height: 13px; }
.ui-card__title { flex: 1 1 auto; min-width: 0; font-weight: 600; letter-spacing: -0.008em; }
.ui-card__aside { display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; }
.ui-card__meta { font-family: var(--font-mono); font-size: var(--ui-text-meta); letter-spacing: 0.01em; color: var(--ink-mute); }
/* "Example", quietly: a caption, not a badge */
.ui-card__example { font-size: var(--text-caption); line-height: 1.2; color: var(--ink-mute); }

/* ---------- Call: a conversation, line by line ---------- */
/* Who's speaking over what they said; our AI's name is in amber ink */
.call { list-style: none; display: grid; gap: 14px; padding: 16px var(--ui-pad) 18px; container-type: inline-size; }
.call__line { display: grid; gap: 3px; }
.call__who {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--ui-text-small); font-weight: 600; letter-spacing: 0; color: var(--ink-mute);
}
.call__line--ai .call__who { color: var(--accent-ink); }
.call__text { font-size: var(--text-small); line-height: 1.45; color: var(--ink); }

/* With times, as a transcript: when (in mono), who and what, in columns, each line ruled off from the last; in a narrow
   screen the words go under the time and the speaker */
.call--transcript { gap: 0; padding: 4px 0; }
.call--transcript .call__line {
  grid-template-columns: 2.75rem 7rem minmax(0, 1fr); column-gap: 12px; row-gap: 2px; align-items: baseline;
  padding: 11px var(--ui-pad); border-top: 1px solid var(--line);
}
.call--transcript .call__line:first-child { border-top: 0; }
.call__time { font-family: var(--font-mono); font-size: var(--ui-text-meta); letter-spacing: 0.01em; color: var(--ink-mute); }
.call--transcript .call__text { font-size: var(--text-ui); }
@container (max-width: 30rem) {
  .call--transcript .call__line { grid-template-columns: 2.75rem minmax(0, 1fr); }
  .call--transcript .call__text { grid-column: 2; }
}

/* Speaking: the first line shows from the start (or, on a page's opening card, waits for the card to arrive), and the
   speech controller leaves each later word a faint ghost until it's said, then inks it in, while a small amber waveform
   moves beside the speaker's name; so the card is never empty, and reads like the words being written as they're
   said. Without the controller (or with reduced motion), every word simply shows. Until the controller is ready, a
   speaking card's waiting lines are ghosts where scripts run and motion is welcome, so the words never show at full
   and fade again; should the script not come, they ink in after a moment anyway. */
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .ui-card[data-controller~="speech"]:not(.is-ready) :is(.call__line:not(:first-child), .call__listening),
  .ui-card[data-speech-from-value="0"]:not(.is-ready) .call__line:first-child {
    opacity: var(--ui-ghost); animation: speech-fallback 0.6s 1.5s forwards;
  }
}
@keyframes speech-fallback { to { opacity: 1; } }
/* While the controller sets a card up, nothing transitions, so a card is put in place at once */
.ui-card.is-settling, .ui-card.is-settling * { transition: none !important; }
.call__text .word { transition: opacity 0.32s var(--ease-out); }
.call__text .word.is-unsaid { opacity: var(--ui-ghost); }
.call__who { transition: opacity 0.3s var(--ease-out); }
.call__line.is-waiting .call__who { opacity: var(--ui-ghost); }

/* The waveform: five bars at their own pace, shown while a line is being said and, slower, while the line is open */
.speaking { display: none; align-items: center; gap: 2px; height: 12px; color: var(--amber); }
.speaking i {
  width: 2px; height: 100%; border-radius: 1px; background: currentColor;
  transform: scaleY(0.3); animation: speaking 0.9s var(--ease-in-out) infinite;
}
.speaking i:nth-child(2) { animation-duration: 0.72s; animation-delay: -0.42s; }
.speaking i:nth-child(3) { animation-duration: 1.04s; animation-delay: -0.16s; }
.speaking i:nth-child(4) { animation-duration: 0.8s; animation-delay: -0.6s; }
.speaking i:nth-child(5) { animation-duration: 0.94s; animation-delay: -0.3s; }
.call__line.is-speaking .speaking { display: inline-flex; }
@keyframes speaking { 0%, 100% { transform: scaleY(0.3); } 50% { transform: scaleY(1); } }

/* After the AI has spoken, the line stays open: "Listening", in mono, with a slow waveform and a light that passes
   across the word, as a live screen shows it */
.call__listening {
  display: flex; align-items: center; gap: 9px; margin: 0; padding: 0 var(--ui-pad) 16px;
  font-family: var(--font-mono); font-size: var(--ui-text-meta); font-weight: 500; line-height: 1.2;
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-mute);
  transition: opacity 0.4s var(--ease-out);
}
.call__listening.is-waiting { opacity: var(--ui-ghost); }
.call__listening .speaking { display: inline-flex; height: 10px; }
.call__listening .speaking i { animation-duration: 1.7s; }
.call__listening .speaking i:nth-child(even) { animation-duration: 1.3s; }
@media (prefers-reduced-motion: no-preference) {
  .call__listening-text {
    background: linear-gradient(90deg, var(--ink-mute) 38%, var(--ink) 50%, var(--ink-mute) 62%) 100% 0 / 260% 100% no-repeat;
    -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
    animation: listening 2.8s linear infinite;
  }
}
@keyframes listening { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* ---------- Checklist: items with where they stand, e.g. deadlines ---------- */
/* Each item's mark says where it stands: done is a filled disc with a tick, due an amber ring around an amber dot, still
   to do an empty ring. What's due is tagged in amber; a step still to take is a small button; the rest is quiet. In a
   narrow screen the state goes under the item's note. */
.checklist { list-style: none; padding-block: 2px; container-type: inline-size; }
.checklist__item {
  display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; column-gap: 12px; row-gap: 2px; align-items: start;
  padding: 12px var(--ui-pad); border-top: 1px solid var(--line);
}
.checklist__item:first-child { border-top: 0; }
.checklist__mark { display: grid; place-items: center; width: 16px; height: 16px; margin-top: 2px; border-radius: 50%; }
.checklist__mark .icon { width: 10px; height: 10px; }
.checklist__item--done .checklist__mark { background: var(--ink); color: var(--surface); }
.checklist__item--due .checklist__mark { box-shadow: inset 0 0 0 1.5px var(--amber); }
.checklist__item--due .checklist__mark::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); }
.checklist__item--todo .checklist__mark { box-shadow: inset 0 0 0 1.5px var(--line-strong); }
.checklist__label { font-weight: 600; color: var(--ink); }
.checklist__state {
  justify-self: end; padding: 0 1px; line-height: 20px; white-space: nowrap;
  font-size: var(--ui-text-small); font-weight: 500; color: var(--ink-mute);
}
.checklist__item--due .checklist__state {
  padding: 0 7px; border-radius: var(--radius-xs); background: var(--amber-soft); color: var(--accent-ink); font-weight: 600;
}
.checklist__item--todo .checklist__state {
  padding: 0 8px; border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px var(--line-strong); color: var(--ink-soft); font-weight: 600;
}
.checklist__note { grid-column: 2 / -1; font-size: var(--ui-text-small); line-height: 1.45; color: var(--ink-mute); }
@container (max-width: 17rem) {
  .checklist__item { grid-template-columns: 16px minmax(0, 1fr); }
  .checklist__state { grid-column: 2; grid-row: 3; justify-self: start; margin-top: 6px; }
}

/* ---------- Pipeline: stages and how many are at each ---------- */
/* A gauge per stage: its count in mono, and a bar as a share of the largest, marked off in quarters like an
   instrument's scale; the last stage's bar is amber */
.pipeline { list-style: none; padding: 6px 0 10px; }
.pipeline__row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px 12px; align-items: baseline; padding: 10px var(--ui-pad); }
.pipeline__label { color: var(--ink-soft); }
.pipeline__value { font-family: var(--font-mono); font-size: var(--text-caption); font-weight: 500; color: var(--ink); }
.pipeline__bar { grid-column: 1 / -1; display: block; width: 100%; height: 6px; border-radius: 3px; overflow: hidden; }
.pipeline__track { fill: var(--c-panel); }
.pipeline__fill { fill: var(--ink); }
.pipeline__row:last-child .pipeline__fill { fill: var(--amber); }
.pipeline__ticks { stroke: var(--surface); stroke-width: 1.5; fill: none; vector-effect: non-scaling-stroke; }

/* ---------- Chart: a line through a few points, the latest in amber ---------- */
/* Drawn as an instrument: a dotted grid in even steps, a firm baseline, the line in ink over a faint amber fill, and
   the latest point in amber with a halo; where it started and where it is now underneath, in mono */
.chart { padding: 14px var(--ui-pad) 14px; }
.chart__svg { display: block; width: 100%; height: 112px; overflow: visible; }
.chart__grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 4; fill: none; vector-effect: non-scaling-stroke; }
.chart__base { stroke: var(--line-strong); stroke-width: 1; fill: none; vector-effect: non-scaling-stroke; }
.chart__area { fill: color-mix(in srgb, var(--amber) 9%, transparent); stroke: none; }
.chart__line { stroke: var(--ink); stroke-width: 1.5; fill: none; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.chart__dot { stroke: var(--ink); stroke-width: 5; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.chart__halo { stroke: color-mix(in srgb, var(--amber) 26%, transparent); stroke-width: 18; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.chart__point--now .chart__dot { stroke: var(--amber); stroke-width: 9; }
.chart__legend {
  display: flex; justify-content: space-between; align-items: baseline; margin-top: 10px;
  font-family: var(--font-mono); font-size: var(--ui-text-meta); letter-spacing: 0.01em; color: var(--ink-mute);
}
.chart__now { color: var(--ink); font-weight: 600; }

/* ---------- A card's story, played over and over on the homepage's plates (demo_controller.js) ---------- */
/* The controller marks the card .is-demo and moves its parts through these states; nothing here applies anywhere
   else, so a card that isn't playing (on a product's page, without JavaScript, with reduced motion) shows its last
   frame, whole. A call uses the speech states above, its ghosts inking in as the words are said. Between loops the
   card fades back to its ghosts, never to blank. */
/* Amber is our AI's voice, so the caller's waveform is a quiet one */
.ui-card.is-demo .call__line--caller .speaking { color: var(--line-strong); }

/* A checklist's rows ink in one by one from their ghosts; the one arriving draws its mark (a tick written in, a ring
   coming round, the amber dot appearing) and its state pulses once */
.ui-card.is-demo .checklist__item { transition: opacity 0.6s var(--ease-out); }
.ui-card.is-demo .checklist__item.is-pending { opacity: var(--ui-ghost); }
.checklist__item.is-current .checklist__mark { animation: demo-mark 0.7s var(--ease-out) both; }
.checklist__item--done.is-current .checklist__mark .icon { stroke-dasharray: 20; animation: demo-tick 0.55s 0.25s var(--ease-out) both; }
.checklist__item--due.is-current .checklist__mark::before { animation: demo-dot 0.5s 0.3s var(--ease-out) both; }
.checklist__item.is-current .checklist__state { transform-origin: right center; animation: demo-pulse 0.8s 0.45s var(--ease-in-out) both; }
@keyframes demo-mark { from { opacity: 0; scale: 0.5; } }
@keyframes demo-tick { from { stroke-dashoffset: 20; } to { stroke-dashoffset: 0; } }
@keyframes demo-dot { from { scale: 0; } }
@keyframes demo-pulse { 40% { scale: 1.07; } }
@container (max-width: 17rem) { .checklist__item.is-current .checklist__state { transform-origin: left center; } }

/* ---------- Stage: a tinted panel that a card sits on ---------- */
.stage {
  position: relative; display: grid; place-items: center;
  padding: clamp(28px, 4vw, 56px) clamp(16px, 3.5vw, 48px);
  border-radius: var(--radius-lg); background: var(--c-panel-deep);
}
.stage > .ui-card { width: min(100%, var(--card-w, 440px)); }
:is(.section--tint, .section--panel) .stage { background: var(--c-paper); }
