/* PulsarCards — marketing section patterns.
 *
 * Loaded after pulsar-site.css on marketing routes only. Tokens come from
 * pulsar-tokens.css; controls from pulsar-primitives.css / -components.css.
 *
 * Why this file exists
 * --------------------
 * The homepage previously rendered five consecutive bands with the same
 * icon + heading + paragraph grid, and /product rendered forty-six of the
 * same card. Repetition at that scale destroys hierarchy: nothing is
 * emphasised because everything is. Each pattern here answers a different
 * question, so a reader can tell from the shape of a band what kind of
 * information is in it before reading a word.
 *
 *   .pm-showcase  one claim beside one piece of evidence
 *   .pm-bento     a set whose members are deliberately unequal
 *   .pm-tour      one sticky artefact, several things to notice about it
 *   .pm-rail      a series you scan sideways rather than scroll past
 *   .pm-flow      an ordered process where sequence is the point
 *   .pm-matrix    two things compared on the same axes
 *   .pm-editorial long-form argument with media that keeps pace
 *   .pm-stage     a WebGL / canvas figure with an honest fallback
 *
 * Rules held throughout: transform and opacity only, no layout animation
 * on scroll, native CSS before JS, `prefers-reduced-motion` removes travel
 * without removing information, and no second palette.
 */

/* ══════════════════════════════════════════════════════════════════
   Band rhythm
   ══════════════════════════════════════════════════════════════════ */

/* pulsar-site.css gives every .band the same --section-y-lg. That is the
   source of the dead 100-140px gaps above and below short content. These
   opt-in rhythms let a page alternate density with air. */
.band[data-rhythm="tight"]  { padding-block: var(--band-y-tight); }
.band[data-rhythm="base"]   { padding-block: var(--band-y); }
.band[data-rhythm="loose"]  { padding-block: var(--band-y-loose); }
.band[data-rhythm="cinema"] { padding-block: var(--band-y-cinema); }

/* Consecutive bands of the same surface read as one region, so the seam
   between them does not need the full rhythm twice. */
.band[data-rhythm] + .band[data-rhythm="tight"] { padding-top: var(--band-y-tight); }

/* A hairline that only appears where two same-coloured bands meet. */
.band-paper + .band-paper,
.band-raised + .band-raised,
.band-ink + .band-ink {
  border-top: var(--border-hair) solid var(--surface-line);
}
.band-ink + .band-ink { border-top-color: rgba(255, 252, 247, 0.08); }

/* ── Section heads ────────────────────────────────────────────────
   The default .section-head caps at 44rem, which strands 40% of a wide
   screen. These variants are the intentional widths. */
.section-head[data-width="display"] h2 { max-width: var(--measure-display); }
.section-head[data-width="display"] p  { max-width: var(--measure-lede); }

/* A head that sits beside its content rather than above it: on wide
   screens the heading holds the left rail and the grid starts alongside,
   which removes a full band of vertical dead space. */
.section-split {
  display: grid;
  gap: var(--space-6) var(--space-8);
  align-items: start;
}
@media (min-width: 1000px) {
  .section-split {
    grid-template-columns: minmax(18rem, 24rem) minmax(0, 1fr);
  }
  .section-split > .section-head {
    margin-bottom: 0;
    position: sticky;
    top: calc(72px + var(--space-6));
  }
}
@media (min-width: 1000px) and (prefers-reduced-motion: reduce) {
  .section-split > .section-head { position: static; }
}

/* ══════════════════════════════════════════════════════════════════
   Scroll-driven enhancement layer
   ══════════════════════════════════════════════════════════════════ */

/* Native scroll timelines where the browser has them. This is additive:
   pulsar-scroll.js still adds .is-visible, and the JS path remains the
   only mechanism in browsers without `animation-timeline`. No library. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Media drifts slower than the page it sits in. Depth from
       differential speed, kept small on purpose — a dramatic offset is a
       vestibular problem, not a design flourish. */
    .pm-parallax > * {
      animation: pm-drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }

    /* A rule that draws itself as its band enters. */
    .pm-scribe {
      animation: pm-scribe linear both;
      animation-timeline: view();
      animation-range: entry 20% cover 45%;
    }
  }
}

@keyframes pm-drift {
  from { transform: translate3d(0, calc(var(--pm-drift, 3%) * -1), 0); }
  to   { transform: translate3d(0, var(--pm-drift, 3%), 0); }
}

@keyframes pm-scribe {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ══════════════════════════════════════════════════════════════════
   .pm-showcase — one claim beside one piece of evidence
   ══════════════════════════════════════════════════════════════════ */

.pm-showcase {
  display: grid;
  gap: var(--space-6);
  align-items: center;
}
@media (min-width: 900px) {
  .pm-showcase {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    gap: var(--space-8);
  }
  /* Media leads on alternating bands so a long page has left/right
     rhythm instead of a single ragged edge. */
  .pm-showcase[data-media="lead"] > .pm-showcase-media { order: -1; }
}

.pm-showcase-body {
  display: grid;
  gap: var(--space-4);
  align-content: center;
  max-width: var(--measure-editorial);
}
.pm-showcase-body h3 {
  font-family: var(--display);
  font-size: var(--text-3xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-snug);
}
.pm-showcase-body > p {
  color: var(--text-secondary);
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}
.band-ink .pm-showcase-body > p { color: rgba(246, 244, 240, 0.72); }

/* Supporting points: a tight definition list, not four more cards. */
.pm-points {
  display: grid;
  gap: var(--space-4);
  margin: var(--space-2) 0 0;
  padding: 0;
}
.pm-point {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  align-items: start;
}
.pm-point-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  background: var(--gold-wash);
  color: var(--gold-strong);
}
.band-ink .pm-point-icon { background: rgba(196, 164, 106, 0.16); color: var(--gold); }
.pm-point-icon .icon { width: 17px; height: 17px; }
.pm-point dt {
  align-self: center;
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}
.pm-point dd {
  grid-column: 2;
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.band-ink .pm-point dd { color: rgba(246, 244, 240, 0.68); }

/* ══════════════════════════════════════════════════════════════════
   .pm-bento — a set whose members are deliberately unequal
   ══════════════════════════════════════════════════════════════════ */

.pm-bento {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(11rem, auto);
}
@media (min-width: 780px) {
  .pm-bento { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Span classes carry the hierarchy. The largest tile is the one a
     reader should take away; the rest support it. */
  .pm-bento > [data-span="wide"]  { grid-column: span 2; }
  .pm-bento > [data-span="hero"]  { grid-column: span 2; grid-row: span 2; }
  .pm-bento > [data-span="tall"]  { grid-row: span 2; }
  .pm-bento > [data-span="full"]  { grid-column: 1 / -1; }
}
@media (max-width: 779px) {
  .pm-bento > [data-span="hero"],
  .pm-bento > [data-span="full"] { grid-column: 1 / -1; }
}

.pm-tile {
  container-type: inline-size;
  position: relative;
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding: var(--space-5);
  border: var(--border-hair) solid var(--surface-line);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  overflow: clip;
  transition:
    border-color var(--dur-base) var(--ease-out),
    box-shadow   var(--dur-base) var(--ease-out),
    transform    var(--dur-base) var(--ease-out);
}
.pm-tile:hover {
  transform: translateY(-2px);
  border-color: var(--gold-soft);
  box-shadow: var(--shadow-md);
}
.band-ink .pm-tile {
  background: rgba(255, 252, 247, 0.04);
  border-color: rgba(255, 252, 247, 0.1);
}
.band-ink .pm-tile:hover {
  border-color: rgba(196, 164, 106, 0.4);
  background: rgba(255, 252, 247, 0.07);
}

.pm-tile h3 {
  font-family: var(--ui);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-normal);
  line-height: var(--leading-snug);
}
.pm-tile p {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.band-ink .pm-tile p { color: rgba(246, 244, 240, 0.68); }

/* Container queries, not viewport queries: a tile grows its type because
   the tile is wide, regardless of how wide the window happens to be. */
@container (min-width: 26rem) {
  .pm-tile { padding: var(--space-6); gap: var(--space-4); }
  .pm-tile h3 {
    font-family: var(--display);
    font-size: var(--text-2xl);
    letter-spacing: var(--tracking-tight);
  }
  .pm-tile p { font-size: var(--text-base); }
}
@container (min-width: 34rem) {
  .pm-tile > p { max-width: var(--measure-lede); }
}

.pm-tile-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--gold-wash);
  color: var(--gold-strong);
}
.band-ink .pm-tile-icon { background: rgba(196, 164, 106, 0.16); color: var(--gold); }
.pm-tile-icon .icon { width: 20px; height: 20px; }

/* A tile whose evidence is an image sitting on its floor. */
.pm-tile-media {
  margin: var(--space-4) calc(-1 * var(--space-5)) calc(-1 * var(--space-5));
  align-self: end;
}
.pm-tile-media img {
  display: block;
  width: 100%;
  height: auto;
  border-top: var(--border-hair) solid var(--surface-line);
}
.band-ink .pm-tile-media img { border-top-color: rgba(255, 252, 247, 0.1); }
@container (min-width: 26rem) {
  .pm-tile-media { margin: var(--space-5) calc(-1 * var(--space-6)) calc(-1 * var(--space-6)); }
}

/* ══════════════════════════════════════════════════════════════════
   .pm-tour — one sticky artefact, several things to notice
   ══════════════════════════════════════════════════════════════════ */

/* This is the pattern the relationship-memory story needed. The product
   stays on screen while the argument advances beside it, so a reader sees
   the same book from four angles rather than reading four descriptions of
   it. Sticky, not pinned: the page never stops scrolling. */
.pm-tour {
  display: grid;
  gap: var(--space-6);
}
@media (min-width: 940px) {
  .pm-tour {
    /* The artefact is the argument in this band, so it takes the larger
       column. It was 0.85fr against the copy's 1fr, which made the one
       thing the reader is meant to look at the smaller of the two. */
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
  }
  .pm-tour[data-media="lead"] > .pm-tour-stage { order: -1; }
}

.pm-tour-stage {
  position: relative;
}
@media (min-width: 940px) {
  .pm-tour-stage {
    position: sticky;
    /* Header height plus a comfortable margin, then centred in what is
       left so a tall artefact is never clipped by the fixed header. */
    top: calc(72px + var(--space-6));
  }
}

.pm-tour-frames {
  position: relative;
  display: grid;
  grid-template-areas: "frame";
  isolation: isolate;
}
.pm-tour-frame {
  grid-area: frame;
  /* This is a <figure>. Without this the UA's `margin: 1em 40px` applies,
     which both narrows the frame and pushes it off the column's left edge. */
  margin: 0;
  opacity: 0;
  transform: translateY(10px) scale(0.985);
  transition:
    opacity   var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  pointer-events: none;
}
.pm-tour-frame[data-on] {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
/* Before JS resolves an active frame, show the first one so the band is
   never blank. */
.pm-tour-frames:not([data-ready]) .pm-tour-frame:first-child {
  opacity: 1;
  transform: none;
}

.pm-tour-steps {
  display: grid;
  gap: var(--space-5);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: pmstep;
}

.pm-tour-step {
  --pm-step-line: var(--surface-line);
  position: relative;
  padding: var(--space-5) 0 var(--space-5) var(--space-6);
  border-left: var(--border-firm) solid var(--pm-step-line);
  counter-increment: pmstep;
  transition:
    border-color var(--dur-base) var(--ease-out),
    opacity      var(--dur-base) var(--ease-out);
  opacity: 0.5;
}
.band-ink .pm-tour-step { --pm-step-line: rgba(255, 252, 247, 0.14); }
.pm-tour-step[data-on] {
  --pm-step-line: var(--gold);
  opacity: 1;
}
/* Keyboard and no-JS: an unresolved list is fully legible. */
.pm-tour-steps:not([data-ready]) .pm-tour-step { opacity: 1; }

.pm-tour-step::before {
  content: counter(pmstep, decimal-leading-zero);
  position: absolute;
  left: calc(-1 * var(--space-6) - 1px);
  top: var(--space-5);
  display: grid;
  place-items: center;
  width: calc(var(--space-6) * 2 - var(--space-2));
  padding-left: var(--space-2);
  font-family: var(--ui);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  color: var(--gold-strong);
  background: inherit;
}
.band-ink .pm-tour-step::before { color: var(--gold); }

.pm-tour-step h3 {
  margin-bottom: var(--space-2);
  font-family: var(--display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-snug);
}
.pm-tour-step p {
  color: var(--text-secondary);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  max-width: var(--measure-lede);
}
.band-ink .pm-tour-step p { color: rgba(246, 244, 240, 0.7); }

/* A step can carry its own proof link without becoming a card. */
@media (prefers-reduced-motion: reduce) {
  .pm-tour-stage { position: static; }
  .pm-tour-frame { position: static; opacity: 1; transform: none; transition: none; }
  /* All frames stacked and visible: the same information, no travel. */
  .pm-tour-frames { grid-template-areas: none; gap: var(--space-4); }
  .pm-tour-frame { grid-area: auto; }
  .pm-tour-step { opacity: 1; }
}

/* ══════════════════════════════════════════════════════════════════
   .pm-rail — a series you scan sideways
   ══════════════════════════════════════════════════════════════════ */

/* Native overflow with scroll-snap. No wheel hijack, no scroll-jacking,
   the real scrollbar stays, arrow keys work, and the whole thing becomes
   a vertical stack under reduced motion. A horizontal region is only
   honest when the user can still leave it. */
.pm-rail-wrap { position: relative; }

.pm-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(84vw, 26rem);
  gap: var(--space-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  /* Bleed to the viewport edge so the series reads as continuing past
     the fold rather than stopping inside a box. */
  margin-inline: calc(-1 * var(--gutter));
  padding: var(--space-2) var(--gutter) var(--space-5);
  scrollbar-width: thin;
  scrollbar-color: var(--gold-soft) transparent;
}
@media (min-width: 900px) {
  .pm-rail { grid-auto-columns: 24rem; }
}
.pm-rail::-webkit-scrollbar { height: 8px; }
.pm-rail::-webkit-scrollbar-track { background: transparent; }
.pm-rail::-webkit-scrollbar-thumb {
  border-radius: var(--radius-full);
  background: var(--gold-soft);
}
.band-ink .pm-rail::-webkit-scrollbar-thumb { background: rgba(196, 164, 106, 0.45); }

.pm-rail-item {
  container-type: inline-size;
  scroll-snap-align: start;
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0;
  border: var(--border-hair) solid var(--surface-line);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  overflow: clip;
}
.band-ink .pm-rail-item {
  background: rgba(255, 252, 247, 0.04);
  border-color: rgba(255, 252, 247, 0.1);
}
.pm-rail-item > figure {
  margin: 0;
  aspect-ratio: 16 / 10;
  background: var(--surface-sunken);
  overflow: hidden;
}
.band-ink .pm-rail-item > figure { background: rgba(0, 0, 0, 0.28); }
/* A frame whose subject is type rather than a scene. `cover` would crop a
   3:1 signature block down to its middle third and lose the name, which is
   the one thing it exists to show. */
.pm-rail-item > figure[data-fit="contain"] {
  background: var(--surface-raised);
}
.pm-rail-item > figure[data-fit="contain"] img {
  object-fit: contain;
  padding: var(--space-4);
}
.pm-rail-item > figure img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.pm-rail-body {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  padding: var(--space-5);
}
.pm-rail-body h3 {
  font-family: var(--ui);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}
.pm-rail-body p {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.band-ink .pm-rail-body p { color: rgba(246, 244, 240, 0.68); }

/* Progress under the rail. Driven natively where available. */
.pm-rail-progress {
  height: 2px;
  border-radius: var(--radius-full);
  background: var(--surface-line);
  overflow: hidden;
}
.band-ink .pm-rail-progress { background: rgba(255, 252, 247, 0.14); }
.pm-rail-progress::after {
  content: "";
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  transform: scaleX(var(--pm-rail-progress, 0.2));
  background: linear-gradient(90deg, var(--gold-strong), var(--gold));
}
@supports (animation-timeline: scroll()) {
  .pm-rail-progress::after {
    transform: scaleX(0);
    animation: pm-rail-fill linear both;
    animation-timeline: scroll(nearest inline);
  }
}
@keyframes pm-rail-fill {
  from { transform: scaleX(0.08); }
  to   { transform: scaleX(1); }
}

/* A hint that there is more to the right, which fades as you reach the
   end. Decorative only — the scrollbar is the real affordance. */
.pm-rail-wrap::after {
  content: "";
  position: absolute;
  inset: 0 calc(-1 * var(--gutter)) var(--space-7) auto;
  width: var(--space-8);
  pointer-events: none;
  background: linear-gradient(to right, transparent, var(--surface-page));
  opacity: 0.9;
  transition: opacity var(--dur-base) var(--ease-out);
}
.pm-rail-wrap[data-rail-end]::after { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .pm-rail {
    grid-auto-flow: row;
    grid-auto-columns: auto;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    overflow-x: visible;
    scroll-snap-type: none;
    margin-inline: 0;
    padding-inline: 0;
  }
  .pm-rail-progress,
  .pm-rail-wrap::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════════
   .pm-flow — an ordered process where sequence is the point
   ══════════════════════════════════════════════════════════════════ */

.pm-flow {
  display: grid;
  gap: var(--space-5);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: pmflow;
}
@media (min-width: 820px) {
  .pm-flow { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--space-4); }
}

.pm-flow-step {
  position: relative;
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding-top: var(--space-6);
  counter-increment: pmflow;
}
/* The connector: one continuous hairline across the row with a gold node
   per step, so the four steps read as one path instead of four boxes. */
.pm-flow-step::before {
  content: "";
  position: absolute;
  inset: 5px 0 auto 0;
  height: var(--border-hair);
  background: var(--surface-line);
}
.band-ink .pm-flow-step::before { background: rgba(255, 252, 247, 0.16); }
.pm-flow-step:last-child::before { right: calc(100% - 11px); }
.pm-flow-step::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: var(--border-firm) solid var(--gold);
  background: var(--surface-page);
}
.pm-flow-step[data-on]::after { background: var(--gold); }

.pm-flow-num {
  font-family: var(--ui);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--gold-strong);
}
.band-ink .pm-flow-num { color: var(--gold); }
.pm-flow-step h3 {
  font-family: var(--display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-snug);
}
.pm-flow-step p {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}
.band-ink .pm-flow-step p { color: rgba(246, 244, 240, 0.68); }

@media (max-width: 819px) {
  /* Vertical: the connector rotates to a left rail. */
  .pm-flow-step { padding: 0 0 0 var(--space-6); }
  .pm-flow-step::before {
    inset: 5px auto 0 5px;
    width: var(--border-hair);
    height: auto;
  }
  .pm-flow-step:last-child::before { bottom: calc(100% - 11px); right: auto; }
}

/* ══════════════════════════════════════════════════════════════════
   .pm-matrix — two things compared on the same axes
   ══════════════════════════════════════════════════════════════════ */

.pm-matrix-wrap {
  overflow-x: auto;
  border: var(--border-hair) solid var(--surface-line);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}
.band-ink .pm-matrix-wrap {
  background: rgba(255, 252, 247, 0.04);
  border-color: rgba(255, 252, 247, 0.1);
}
.pm-matrix {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.pm-matrix caption {
  padding: var(--space-4) var(--space-5) 0;
  text-align: left;
  color: var(--text-secondary);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.pm-matrix th,
.pm-matrix td {
  padding: var(--space-4) var(--space-5);
  text-align: left;
  vertical-align: top;
  border-bottom: var(--border-hair) solid var(--surface-line);
}
.band-ink .pm-matrix th,
.band-ink .pm-matrix td { border-bottom-color: rgba(255, 252, 247, 0.1); }
.pm-matrix tbody tr:last-child th,
.pm-matrix tbody tr:last-child td { border-bottom: 0; }
.pm-matrix thead th {
  font-family: var(--display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
}
.pm-matrix thead th:first-child {
  font-family: var(--ui);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-secondary);
}
.pm-matrix tbody th {
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  white-space: nowrap;
}
.pm-matrix td { color: var(--text-secondary); line-height: var(--leading-normal); }
.band-ink .pm-matrix td { color: rgba(246, 244, 240, 0.72); }
.pm-matrix .icon { color: var(--gold-strong); flex: none; }
.band-ink .pm-matrix .icon { color: var(--gold); }

/* ══════════════════════════════════════════════════════════════════
   .pm-editorial — long-form argument with media that keeps pace
   ══════════════════════════════════════════════════════════════════ */

.pm-editorial {
  display: grid;
  gap: var(--space-7);
}
@media (min-width: 960px) {
  .pm-editorial {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    gap: var(--space-8);
    align-items: start;
  }
  .pm-editorial[data-media="lead"] > .pm-editorial-media { order: -1; }
  .pm-editorial-media {
    position: sticky;
    top: calc(72px + var(--space-6));
  }
}
@media (min-width: 960px) and (prefers-reduced-motion: reduce) {
  .pm-editorial-media { position: static; }
}

.pm-editorial-body {
  display: grid;
  gap: var(--space-5);
  max-width: var(--measure-editorial);
}
.pm-editorial-body h2,
.pm-editorial-body h3 {
  font-family: var(--display);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-snug);
}
.pm-editorial-body p {
  color: var(--text-secondary);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}
.band-ink .pm-editorial-body p { color: rgba(246, 244, 240, 0.72); }

/* A pull-quote sized statement inside body copy: the one line a skimmer
   should catch. Von Restorff, used once per section at most. */
.pm-pull {
  margin: var(--space-2) 0;
  padding-left: var(--space-5);
  border-left: var(--border-firm) solid var(--gold);
  font-family: var(--display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-snug);
  color: var(--text-primary);
}
.band-ink .pm-pull { color: var(--paper); }

/* ══════════════════════════════════════════════════════════════════
   Media: device frames, figures, stages
   ══════════════════════════════════════════════════════════════════ */

/* Real product screenshots need a frame or they read as floating
   rectangles. These are drawn in tokens — no vendor device chrome, no
   imitation of anyone's hardware. */
.pm-device {
  position: relative;
  margin: 0;
  border-radius: var(--radius-xl);
  background: var(--ink);
  padding: var(--space-2);
  box-shadow: var(--shadow-lg);
  overflow: clip;
}
.pm-device img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(var(--radius-xl) - var(--space-2));
}
.pm-device[data-device="phone"] {
  width: min(100%, 20rem);
  margin-inline: auto;
  border-radius: 2.25rem;
  padding: 6px;
}
.pm-device[data-device="phone"] img { border-radius: calc(2.25rem - 6px); }
/* Speaker slot. One hairline detail, not a hardware replica. */
.pm-device[data-device="phone"]::before {
  content: "";
  position: absolute;
  z-index: 2;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  width: 22%;
  height: 4px;
  border-radius: var(--radius-full);
  background: rgba(246, 244, 240, 0.22);
}
.pm-device[data-device="browser"] { padding: 0; border-radius: var(--radius-lg); }
.pm-device[data-device="browser"] img { border-radius: 0; }
/* A browser chrome strip with the live URL, which is itself part of the
   product story: the card is a URL. */
.pm-device-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--ink-90);
  color: rgba(246, 244, 240, 0.6);
  font-family: var(--mono);
  font-size: var(--text-xs);
}
.pm-device-bar::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 0 3px rgba(196, 164, 106, 0.22);
}
.pm-device-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A caption belongs outside the frame it describes. `.pm-device` is a
   clipped box filled with ink, so a figcaption inside it gets cropped and
   sits on the wrong background: wrap the device in `.pm-media` and put the
   caption after it. */
.pm-media { margin: 0; }
.pm-media > .pm-device { margin-bottom: 0; }

.pm-media figcaption,
.pm-device figcaption,
.pm-figure figcaption {
  margin-top: var(--space-3);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
}
.band-ink .pm-media figcaption,
.band-ink .pm-device figcaption,
.band-ink .pm-figure figcaption { color: rgba(246, 244, 240, 0.56); }
.pm-device figcaption { padding-inline: var(--space-2); }

.pm-figure {
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: clip;
}
.pm-figure > img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
}
.pm-figure[data-tone="inset"] > img {
  border: var(--border-hair) solid var(--surface-line);
}

/* Two screenshots overlapped: the before/after or web/phone pairing that
   a single flat image cannot carry. */
.pm-stack {
  position: relative;
  padding-bottom: 12%;
  padding-right: 8%;
}
.pm-stack > :first-child { position: relative; z-index: 1; }
.pm-stack > :last-child {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 2;
  width: 42%;
  max-width: 15rem;
}
@media (max-width: 599px) {
  .pm-stack { padding-bottom: 18%; padding-right: 0; }
  .pm-stack > :last-child { width: 46%; right: auto; left: 4%; }
}

/* A WebGL / canvas figure. The poster is inside the same box so there is
   never a layout shift when the scene takes over, and the fallback is a
   real image rather than an empty rectangle. */
.pm-stage {
  position: relative;
  display: grid;
  grid-template-areas: "stage";
  isolation: isolate;
  border-radius: var(--radius-xl);
  overflow: clip;
  background: var(--ink);
  aspect-ratio: 4 / 3;
}
.pm-stage > img,
.pm-stage > canvas {
  grid-area: stage;
  width: 100%;
  height: 100%;
}
.pm-stage > img { object-fit: cover; }
.pm-stage > canvas {
  display: block;
  opacity: 0;
  /* The scene must never eat a vertical swipe on a phone. */
  touch-action: pan-y;
  transition: opacity var(--dur-slower) var(--ease-out);
}
.pm-stage[data-stage-mode="live"] > canvas { opacity: 1; }
.pm-stage[data-stage-mode="live"] > img { opacity: 0; }
.pm-stage[data-stage-mode="poster"] > canvas { display: none; }

.pm-stage-note {
  position: absolute;
  z-index: 3;
  left: var(--space-4);
  bottom: var(--space-4);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 10px;
  border: 1px solid rgba(246, 244, 240, 0.24);
  border-radius: var(--radius-full);
  background: rgba(11, 18, 32, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: rgba(246, 244, 240, 0.82);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
}

/* ══════════════════════════════════════════════════════════════════
   Opt-in model viewer
   ══════════════════════════════════════════════════════════════════ */

/* pulsar-model-viewer.js mounts a drag-to-rotate canvas into
   [data-model-viewer] after an explicit click. These are the styles the
   orphaned pulsar-3d.css used to carry, restated in tokens so the viewer
   works inside any marketing band rather than only on one legacy page.
   Three.js and the GLB stay unfetched until the button is pressed. */
[data-model-viewer] {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-xl);
  overflow: clip;
  background: #0E1626;
}
[data-model-viewer] > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--dur-slow) var(--ease-out);
}
/* The poster fades rather than being removed, so there is something to
   animate from and the box never collapses mid-swap. */
[data-model-viewer][data-model-ready="true"] > img {
  opacity: 0;
  pointer-events: none;
}
.model-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* grab, because the affordance is dragging to rotate. */
  cursor: grab;
  touch-action: none;
}
.model-canvas:active { cursor: grabbing; }

[data-model-load] {
  position: relative;
  z-index: 2;
  min-height: var(--tap-min);
  padding-inline: var(--space-5);
  border: 0;
  border-radius: var(--radius-full);
  background: var(--gold);
  color: var(--gold-ink);
  font-family: var(--ui);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  cursor: pointer;
  box-shadow: var(--shadow-lg);
  transition:
    background-color var(--dur-fast) var(--ease-out),
    transform        var(--dur-fast) var(--ease-out);
}
[data-model-load]:hover:not(:disabled) {
  background: var(--gold-strong);
  transform: translateY(-1px);
}
[data-model-load]:disabled { opacity: 0.7; cursor: default; transform: none; }

@media (prefers-reduced-motion: reduce) {
  [data-model-viewer] > img { transition: none; }
  [data-model-load]:hover { transform: none; }
}

/* ══════════════════════════════════════════════════════════════════
   Shared small parts
   ══════════════════════════════════════════════════════════════════ */

/* A proof link. Marketing claims should be one click from the page that
   substantiates them. */
.pm-proof {
  /* Block-level, not `inline-flex`. Every call site puts this after a
     sentence of body copy, and as an inline box it joined that sentence's
     last line — so a small-caps gold link in tracked uppercase ended up
     mid-paragraph, reading as though it were part of the prose. `flex` gives
     it its own line; `fit-content` keeps the hover and focus target the width
     of the label rather than the whole column. This is also what makes the
     `margin-top` below take effect, which it could not do while inline. */
  display: flex;
  width: fit-content;
  margin-top: var(--space-3);
  align-items: center;
  gap: var(--space-2);
  min-height: 32px;
  color: var(--gold-strong);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
}
.band-ink .pm-proof { color: var(--gold); }
.pm-proof .icon { width: 14px; height: 14px; transition: transform var(--dur-fast) var(--ease-out); }
a.pm-proof:hover { color: var(--gold); }
a.pm-proof:hover .icon { transform: translateX(2px); }
.band-ink a.pm-proof:hover { color: var(--paper); }

/* An honest qualifier. Used wherever a capability has a real limit, so
   the limit travels with the claim instead of being omitted. */
.pm-note {
  display: block;
  margin-top: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  font-style: normal;
}
.band-ink .pm-note { color: rgba(246, 244, 240, 0.54); }
/* Standing on its own after a pattern rather than inside a card, where it
   needs a reading measure and a rule to separate it from what it qualifies. */
.pm-note:not([class*="pm-point"]):not(dd .pm-note) {
  max-width: var(--measure-editorial);
}
.pm-tour + .pm-note,
.pm-bento + .pm-note,
.pm-rail-wrap + .pm-note {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
  font-size: var(--text-sm);
}
.band-ink .pm-tour + .pm-note,
.band-ink .pm-bento + .pm-note,
.band-ink .pm-rail-wrap + .pm-note { border-top-color: rgba(246, 244, 240, 0.14); }

/* Small caps label used above groups inside a band. */
.pm-kicker {
  display: block;
  margin-bottom: var(--space-3);
  color: var(--gold-strong);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.band-ink .pm-kicker { color: var(--gold); }

/* Anchor offset so a jump link never lands under the fixed header. */
.band[id],
[data-anchor] { scroll-margin-top: calc(72px + var(--space-4)); }

/* ══════════════════════════════════════════════════════════════════
   View transitions
   ══════════════════════════════════════════════════════════════════ */

@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--dur-base);
  animation-timing-function: var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════
   .pm-act — an editorial act on /how
   ══════════════════════════════════════════════════════════════════
   /how explains a mechanism, so it is prose-led rather than card-led. The
   band it replaced ran 13 numbered <li> items down a 45%-wide column of a
   1440px viewport, which left more than half the page empty and gave the
   reader no way to tell act from footnote.

   Two columns: a short sticky heading that names the act, and the body. The
   heading pins so the reader always knows which act they are inside — the
   thing a flat numbered list cannot do once the number scrolls away. */
.pm-act {
  display: grid;
  gap: var(--space-5);
  padding-block: var(--band-y-tight);
  border-top: 1px solid var(--line);
}
.pm-act:first-of-type { border-top: 0; padding-top: 0; }
.band-ink .pm-act { border-top-color: rgba(255, 252, 247, 0.12); }

@media (min-width: 900px) {
  .pm-act {
    /* The aside is a label, not a column of content: narrow, and fixed so
       every act's body starts on the same left edge down the page. */
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    gap: var(--space-8);
  }
  .pm-act-head {
    position: sticky;
    top: calc(72px + var(--space-6));
    align-self: start;
  }
}

.pm-act-num {
  display: block;
  color: var(--gold-strong);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.band-ink .pm-act-num { color: var(--gold); }

.pm-act-head h2 {
  margin-top: var(--space-3);
  font-size: var(--text-xl);
  line-height: var(--leading-snug);
  text-wrap: balance;
}

.pm-act-body {
  display: grid;
  gap: var(--space-5);
  /* A reading measure. Without it the body runs the full 1fr and lines reach
     100+ characters on a wide screen, which is where /how lost people. */
  max-width: var(--measure-editorial);
  /* So the points below can respond to this column's width rather than the
     viewport's — the column is narrower than the page and is what they sit in. */
  container-type: inline-size;
}
/* The grid `gap` already spaces these; margins would double it. */
.pm-act-body > p { margin: 0; }

/* A sub-point inside an act: the detail that would have been its own
   numbered step on the old page, demoted to where it belongs. */
.pm-act-points {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
@container (min-width: 34rem) {
  .pm-act-points[data-cols="2"] { grid-template-columns: 1fr 1fr; }
}
.pm-act-point h3 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
}
.pm-act-point p {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}
.band-ink .pm-act-point p { color: rgba(246, 244, 240, 0.7); }

/* ══════════════════════════════════════════════════════════════════
   .pm-field — the scroll-scrubbed NFC explainer
   ══════════════════════════════════════════════════════════════════
   The host is deliberately tall: its height minus one viewport is the scroll
   distance the canvas scrubs across, which is how the five acts get room to
   read. The canvas pins inside it. */
.pm-field {
  position: relative;
  margin: 0;
  /* One extra viewport of travel. Longer turns the explanation into a chore;
     shorter and the acts flick past before they can be read. */
  height: 200vh;
}

.pm-field-stage {
  position: sticky;
  top: 0;
  display: grid;
  align-content: center;
  gap: var(--space-5);
  height: 100vh;
  padding-block: var(--space-6);
}

.pm-field-plate {
  position: relative;
  width: 100%;
  /* Matches the module's virtual 1000×560 space, so the drawing is never
     letterboxed against its own box. */
  aspect-ratio: 1000 / 560;
}

.pm-field-plate > canvas,
.pm-field-plate > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The poster carries the explanation until (and unless) the canvas draws.
   Swapping on a flag the module sets — rather than hiding the poster up
   front — is what keeps a blocked or failed import from leaving a blank box. */
.pm-field-plate > canvas { display: none; }
[data-field-ready] .pm-field-plate > canvas { display: block; }
[data-field-ready] .pm-field-plate > img { display: none; }

.pm-field figcaption {
  max-width: var(--measure-lede);
  margin-inline: auto;
  color: var(--muted);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  text-align: center;
  /* Reserve two lines. The caption changes as the reader scrubs, and without
     a floor the plate above it jumps every time a shorter act lands. */
  min-height: calc(var(--text-base) * var(--leading-relaxed) * 2);
}
.band-ink .pm-field figcaption { color: rgba(246, 244, 240, 0.72); }

/* The same five statements as prose, for anyone not scrubbing a canvas:
   no-JS, reduced motion, and screen readers. Information parity, not a
   consolation prize. */
.pm-field-steps {
  display: grid;
  gap: var(--space-3);
  max-width: var(--measure-editorial);
  margin: var(--space-6) auto 0;
  padding: 0;
  counter-reset: pmfield;
}
.pm-field-steps li {
  display: grid;
  grid-template-columns: 1.75rem 1fr;
  gap: var(--space-3);
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  list-style: none;
}
.pm-field-steps li::before {
  counter-increment: pmfield;
  content: counter(pmfield, decimal-leading-zero);
  color: var(--gold-strong);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
.band-ink .pm-field-steps li { color: rgba(246, 244, 240, 0.66); }
.band-ink .pm-field-steps li::before { color: var(--gold); }

/* With motion suppressed there is nothing to pin or scrub: show the plate at
   its most explanatory moment, at natural height, and let the prose carry the
   sequence. `height: auto` is what releases the 200vh scroll runway. */
@media (prefers-reduced-motion: reduce) {
  .pm-field { height: auto; }
  .pm-field-stage { position: static; height: auto; padding-block: 0; }
  .pm-field figcaption { min-height: 0; }
}

/* Same reasoning for short viewports, where a 100vh sticky stage plus a
   caption does not fit and would clip the drawing. */
@media (max-height: 620px) {
  .pm-field { height: auto; }
  .pm-field-stage { position: static; height: auto; }
}

/* Five live card looks — product explanation, not a customer claim. */
.pm-looks-cinema {
  position: relative;
  height: min(380px, 46vw);
  margin: 0 0 var(--space-4);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #10141a;
}
.pm-looks-canvas { width: 100%; height: 100%; display: block; }
@media (prefers-reduced-motion: reduce) {
  .pm-looks-cinema { display: none; }
}
.pm-looks-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(13.5rem, 16rem);
  gap: var(--space-3);
  overflow-x: auto;
  padding-block: var(--space-4);
  scroll-snap-type: x mandatory;
}
.pm-looks-card {
  scroll-snap-align: start;
  margin: 0;
  padding: var(--space-2);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-md);
  background: var(--raised);
}
.pm-looks-card figcaption {
  display: grid;
  gap: 2px;
  padding: var(--space-2) var(--space-1) var(--space-1);
}
.pm-looks-card strong { font-size: var(--text-sm); }
.pm-looks-card span { font-size: var(--text-xs); color: var(--text-secondary); }
.pm-look-skin {
  display: block;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: #fff;
  min-height: 9rem;
}
.pm-look-skin img {
  display: block;
  width: 100%;
  height: 2.6rem;
  object-fit: cover;
}
.pm-look-skin i,
.pm-look-skin b,
.pm-look-skin em {
  display: block;
  font-style: normal;
  font-weight: inherit;
}
.pm-look-skin i:first-child {
  height: 2.4rem;
  background: linear-gradient(120deg, var(--brand), var(--gold));
}
.pm-look-skin b {
  width: 1.6rem;
  height: 1.6rem;
  margin: -0.8rem auto 0;
  border-radius: 999px;
  border: 2px solid #fff;
  background: var(--brand);
}
.pm-look-skin em {
  width: 42%;
  height: 6px;
  margin: 8px auto 4px;
  border-radius: 99px;
  background: var(--ink);
}
.pm-look-skin em + em {
  width: 28%;
  opacity: 0.4;
  margin-top: 0;
}
.pm-look-skin[data-look="orbit"] { background: #111827; }
.pm-look-skin[data-look="orbit"] em { background: #f4f1ea; }
.pm-look-skin[data-look="editorial"] b { border-radius: 6px; margin-left: 12px; }
.pm-look-skin[data-look="editorial"] em { margin-left: 12px; }
.pm-look-skin[data-look="spatial"] {
  background:
    radial-gradient(80% 50% at 80% 0%, color-mix(in srgb, var(--brand) 22%, transparent), #f6f4f0);
}
.pm-look-skin[data-look="aura"] i:first-child {
  mask-image: linear-gradient(to bottom, #000 50%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .pm-looks-rail { scroll-snap-type: none; }
}
