/* ==========================================================================
   Dynamic Mockups — base primitives
   --------------------------------------------------------------------------
   Every rule is scoped to `.dm`, and every class carries the `dm-` prefix, so
   this file cannot reach legacy pages or collide with legacy class names
   (`.container`, `.btn`, `.card` all exist in the old stylesheets).

   Opt a template in by putting `dm` on its wrapper:
     <div id="dm-pricing" class="dm"> … </div>

   SPECIFICITY CONTRACT. Every rule stays scoped — the lookbooks and Mockup API
   pages already use names like `dm-btn` and `dm-container`, so an unscoped
   primitive would restyle them — but the scope is written `:where(.dm)`, which
   contributes nothing to specificity. Element targets go in :where() too. So
   every primitive here weighs exactly one class (0,1,0):

     :where(.dm) .dm-card   not  .dm .dm-card
     .dm :where(p)          not  .dm p

   A page rule of the same weight then wins on load order, since page CSS is
   enqueued after base.css. Write `.dm .dm-card` here (two classes) and
   `.dm-path-card--dark` in a page file silently loses — that trap cost the dark
   two-path card its background, the hero stack its absolute positioning, and
   the CTA paragraph its auto margins.

   Design language: white/paper ground, hairlines used sparingly, modest
   display type, mono only where it is literally code, no full-bleed colour
   fields — dark ink is the only heavy fill.
   ========================================================================== */

.dm {
  --dm-pad: 48px;
  font-family: var(--dm-font-body);
  font-size: 16px;
  line-height: var(--dm-leading-body);
  color: var(--dm-ink);
  background: var(--dm-surface-page);
  /* Back to the platform default, deliberately. The theme's own style.css sets
     `-webkit-font-smoothing: antialiased` on body, and that property inherits,
     so every glyph inside .dm was rendering without the stem darkening macOS
     applies — measurably thinner than the design source (7-26% fewer solid
     stroke pixels at identical size, weight and colour). The design is a
     standalone file with no such rule, so `auto` is what matches it. */
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
  /* `clip`, not `hidden`: `hidden` forces overflow-y:auto and turns the
     wrapper into a nested scroll container, which breaks touch scrolling. */
  overflow-x: clip;
}

.dm :where(*, *::before, *::after) {
  box-sizing: border-box;
}

.dm :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, dd, figure, button) {
  margin: 0;
  padding: 0;
}

.dm :where(ul, ol) {
  list-style: none;
}

.dm :where(a) {
  color: var(--dm-ink);
  text-decoration: none;
  transition: color .12s ease, background-color .12s ease, box-shadow .12s ease;
}

.dm :where(a):hover {
  color: var(--dm-pink-700);
  text-decoration: none;
}

.dm :where(img) {
  max-width: 100%;
}

.dm :where(summary) {
  list-style: none;
}

.dm :where(summary)::-webkit-details-marker {
  display: none;
}

/* ---------- screen-reader-only text ----------
   Our own components use this class (the nav carets, the burger), and until now
   it was styled only by the legacy theme stylesheet — so the labels rendered as
   visible text anywhere that sheet is absent, the static export included. The
   scope owns it now. */
.dm :where(.screen-reader-text) {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- focus ----------
   A keyboard ring in the brand's ink, and nothing on a mouse click — the
   browser default was a blue halo that belongs to no palette here. */
.dm :where(a, button, summary, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--dm-ink);
  outline-offset: 2px;
}

.dm :where(a, button, summary, input, textarea, [tabindex]):focus:not(:focus-visible) {
  outline: none;
}

@media (prefers-reduced-motion: reduce) {
  .dm :where(a),
  :where(.dm) .dm-btn {
    transition: none;
  }
}

/* ---------- layout ---------- */
:where(.dm) .dm-container {
  max-width: 1344px;
  margin: 0 auto;
  padding-inline: var(--dm-pad);
}

/* a standard section: 96px of breathing room top and bottom */
:where(.dm) .dm-std .dm-container {
  padding-block: 96px;
}

:where(.dm) .dm-section {
  margin: 0;
}

/* ---------- type ---------- */
:where(.dm) .dm-h2 {
  font-family: var(--dm-font-display);
  font-weight: 400;
  font-size: 40px;
  line-height: 1.1;
  letter-spacing: var(--dm-tracking-display);
  color: var(--dm-ink);
}

/* The plain cut of the same heading. Sections the reader SCANS rather than
   reads — the template carousel, the integrations grid — plus pure wayfinding
   labels ("What our users are saying", "Frequently asked questions"). Identical
   size and rhythm to .dm-h2; only the voice changes, so the serif is left
   carrying the argument alone and stops reading as "what an h2 looks like".
   Geist 400 sits lighter than Fraunces 400 at this size, hence 500. */
:where(.dm) .dm-h2--plain {
  font-family: var(--dm-font-body);
  font-weight: 500;
  letter-spacing: -0.02em;
}

:where(.dm) .dm-eyebrow {
  font-size: 13px;
  font-weight: 600;
  line-height: var(--dm-leading-label);
  color: var(--dm-text-tertiary);
}

/* Reaches a screen reader, never the screen. For a label whose field is
   self-evident to a sighted reader — a single search box, one domain field. */
:where(.dm) .dm-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

:where(.dm) .dm-lead {
  font-size: 16px;
  line-height: 1.5;
  color: var(--dm-text-secondary);
  max-width: 38ch;
}

/* section head: title left, controls right, bottoms aligned */
:where(.dm) .dm-sec-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 32px;
  margin-bottom: 56px;
  flex-wrap: wrap;
}

/* ---------- buttons: dark by default, md size by default ---------- */
:where(.dm) .dm-btn {
  display: inline-flex;
  align-items: center;
  background: var(--dm-ink);
  color: var(--dm-on-ink);
  border: 0;
  border-radius: var(--dm-radius-button);
  padding: 10px 18px;
  font-family: var(--dm-font-body);
  font-weight: 400;
  font-size: 14px;
  line-height: var(--dm-leading-label);
  cursor: pointer;
  white-space: nowrap;
}

:where(.dm) .dm-btn:hover {
  background: var(--dm-neutral-900);
  color: var(--dm-on-ink);
}

:where(.dm) .dm-btn--sm { padding: 8px 16px; }
:where(.dm) .dm-btn--lg { padding: 12px 22px; font-size: 15px; }

/* The one button whose ground is paper rather than ink. On an ink island (a
   dark card sitting on a light page) `--dm-ink` is re-declared light, which
   would take this label with it — so the island sets --dm-ink-on-paper and this
   reads it. Undefined everywhere else, where --dm-ink is already right. */
:where(.dm) .dm-btn--inverse {
  background: var(--dm-surface-page);
  color: var(--dm-ink-on-paper, var(--dm-ink));
}

:where(.dm) .dm-btn--inverse:hover {
  background: var(--dm-surface-white);
  color: var(--dm-ink-on-paper, var(--dm-ink));
}

/* Semantic tokens, not ramp steps: a ramp step is the same colour on a dark
   page, where this button would then be light-on-light. The hairline is what
   keeps it readable there, its ground sitting close to the page. */
:where(.dm) .dm-btn--ghost {
  background: var(--dm-surface-card);
  color: var(--dm-ink);
  box-shadow: inset 0 0 0 1px var(--dm-border-card);
}

:where(.dm) .dm-btn--ghost:hover {
  background: var(--dm-surface-card);
  color: var(--dm-ink);
  box-shadow: inset 0 0 0 1px var(--dm-border-hairline);
}

/* ---------- badges ---------- */
:where(.dm) .dm-badge {
  display: inline-flex;
  align-items: center;
  border-radius: var(--dm-radius-badge);
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 600;
  line-height: var(--dm-leading-label);
}

:where(.dm) .dm-badge--cyan   { background: var(--dm-cyan-50);   color: var(--dm-cyan-800); }
:where(.dm) .dm-badge--pink   { background: var(--dm-pink-0);   color: var(--dm-pink-700); }
:where(.dm) .dm-badge--purple { background: var(--dm-purple-0); color: var(--dm-purple-700); }
:where(.dm) .dm-badge--lime   { background: var(--dm-lime-0);   color: var(--dm-lime-900); }
:where(.dm) .dm-badge--orange { background: var(--dm-orange-0); color: var(--dm-orange-900); }

/* ---------- cards, fill images, pills ---------- */
:where(.dm) .dm-card {
  background: var(--dm-surface-card);
  border-radius: var(--dm-radius-card);
}

/* a fixed box an image fills — the box is the contract, the image follows */
:where(.dm) .dm-render {
  position: relative;
  border-radius: var(--dm-radius-card);
}

:where(.dm) .dm-render > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

:where(.dm) .dm-pill {
  position: absolute;
  left: 14px;
  top: 14px;
  pointer-events: none;
  background: rgba(33, 34, 29, 0.82);
  color: var(--dm-surface-page);
  border-radius: var(--dm-radius-badge);
  padding: 5px 8px;
  font-size: 10px;
  line-height: var(--dm-leading-label);
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ---------- check lists ---------- */
:where(.dm) .dm-checks {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

:where(.dm) .dm-checks li {
  display: flex;
  gap: 10px;
  font-size: 15px;
  color: var(--dm-neutral-900);
}

:where(.dm) .dm-checks li::before {
  content: "✓";
  color: var(--dm-lime-700);
}

/* ---------- the arrow chip on linked tiles ----------
   Answers on hover; touch devices get it permanently, since there is no
   hover to answer with. */
:where(.dm) .dm-tile-arrow {
  position: absolute;
  top: 22px;
  right: 22px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--dm-surface-white);
  color: var(--dm-ink);
  box-shadow: inset 0 0 0 1px rgba(33, 34, 29, 0.06);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .15s var(--dm-ease-press), transform .15s var(--dm-ease-press);
}

:where(.dm) .dm-tile-arrow svg {
  width: 16px;
  height: 16px;
  display: block;
}

:where(.dm) a:hover > .dm-tile-arrow,
:where(.dm) a:focus-visible > .dm-tile-arrow {
  opacity: 1;
  transform: none;
}

@media (hover: none) {
  :where(.dm) .dm-tile-arrow { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  :where(.dm) .dm-tile-arrow { transition: none; }
}

/* ---------- grain ----------
   The duo-tone noise tile that rides over every gradient surface (CTA panel,
   integration tiles, the AI editor ground) at 0.35. Inline SVG so it costs no
   request and scales with nothing. */
:where(.dm) .dm-grain::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.35;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g' x='0' y='0' width='180' height='180' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='2 2' numOctaves='3' seed='861' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='luminanceToAlpha' result='a'/%3E%3CfeComponentTransfer in='a' result='m1'%3E%3CfeFuncA type='discrete' tableValues='1 0'/%3E%3C/feComponentTransfer%3E%3CfeFlood flood-color='black' flood-opacity='0.25' result='f1'/%3E%3CfeComposite operator='in' in='f1' in2='m1' result='c1'/%3E%3CfeComponentTransfer in='a' result='m2'%3E%3CfeFuncA type='discrete' tableValues='0 1'/%3E%3C/feComponentTransfer%3E%3CfeFlood flood-color='white' flood-opacity='0.25' result='f2'/%3E%3CfeComposite operator='in' in='f2' in2='m2' result='c2'/%3E%3CfeMerge%3E%3CfeMergeNode in='c1'/%3E%3CfeMergeNode in='c2'/%3E%3C/feMerge%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E") repeat;
  background-size: 180px 180px;
}

/* ---------- media slot ----------
   A placeholder box carrying its capture spec. The box (aspect, radius,
   position) is the contract the real <video>/<img> inherits later. */
:where(.dm) .dm-media-slot {
  position: relative;
  background: var(--dm-surface-card);
  overflow: hidden;
}

:where(.dm) .dm-media-slot img.dm-glyph {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 72px;
  height: 72px;
  object-fit: contain;
  opacity: 0.14;
  border-radius: 0;
}


/* A render floating on the slot's gradient rather than filling it — a full
   bleed would cover the canvas entirely and there would be no point painting
   one. Sits above the faint glyph and below the play badge and caption, all of
   which are absolutely positioned already. */
/* Framed the way the AI editor shot is: sitting ON the gradient rather than
   filling the box, with a white mount, a soft radius and a deep shadow. Width
   is held under 100% so the gradient reads as a margin all the way round. */
:where(.dm) .dm-media-slot .dm-slot-shot {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 72%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border: 6px solid #fff;
  border-radius: 10px;
  box-shadow: 0 22px 54px rgba(0, 0, 0, 0.22);
}


/* ---------- carousel primitive (drag + snap + arrow controls) ---------- */
:where(.dm) .dm-car-ctl {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* `display: flex` above beats the `[hidden] { display: none }` in the theme's
   style.min.css — same specificity, and this file loads later. So the JS
   setting `hidden` on a carousel with nothing to scroll had no visible effect.
   Any element in this system that both sets `display` and gets hidden from
   script needs its own [hidden] rule like this. */
:where(.dm) .dm-car-ctl[hidden] { display: none; }

:where(.dm) .dm-car-ctl button {
  width: 40px;
  height: 40px;
  /* Explicit: the theme's style.min.css pads bare buttons (.5rem 1rem). Left
     in place it shrinks this 40px box's content area to 8px, the auto grid
     track overflows it, and the icon sits 5px right of the circle's centre. */
  padding: 0;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--dm-ink);
  color: var(--dm-ink);
  font-size: 15px;
  display: grid;
  place-items: center;
  font-family: inherit;
  transition: box-shadow .12s ease, opacity .12s ease;
}

:where(.dm) .dm-car-ctl button svg {
  display: block;
  width: 18px;
  height: 18px;
}

:where(.dm) .dm-car-ctl button:hover { box-shadow: inset 0 0 0 2px var(--dm-ink); }

:where(.dm) .dm-car-ctl button:disabled {
  opacity: 0.3;
  cursor: default;
  box-shadow: inset 0 0 0 1px var(--dm-ink);
}

:where(.dm) .dm-car-count {
  font-family: var(--dm-font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--dm-text-tertiary);
  min-width: 52px;
  text-align: center;
}

/* overflow-x: auto forces the vertical axis to `auto` as well, so the cards'
   4px hover lift was being clipped at the top edge of the scroller. The padding
   buys that room back and the matching negative margin keeps the section's
   rhythm unchanged — the track sits exactly where it did.

   The right edge fades always, because there is always more to scroll toward.
   The left edge only fades once there is something scrolled off to fade INTO;
   a permanent left fade would dim the first card while the carousel sits at
   rest, which reads as a rendering fault rather than an affordance. home.js
   toggles is-scrolled from the same handler that enables the arrows. */
:where(.dm) .dm-carousel {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-block: 10px;
  margin-block: -10px;
  mask-image: linear-gradient(90deg, #000 0, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 94%, transparent);
}

:where(.dm) .dm-carousel.is-scrolled {
  mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 94%, transparent);
}

:where(.dm) .dm-carousel::-webkit-scrollbar { display: none; }

:where(.dm) .dm-carousel.is-dragging {
  scroll-snap-type: none;
  cursor: grabbing;
  user-select: none;
}

:where(.dm) .dm-car-track {
  display: flex;
  gap: 20px;
}

/* ==========================================================================
   Motion pass — "the swatch edge"
   One easing, 70ms ripple staggers, rises ≤24px. Hidden initial states exist
   only under html.dm-motion, which the script adds when reduced motion is
   off — so no-JS and reduced-motion render the page complete and still.
   ========================================================================== */
.dm-motion :where(.dm) [data-dm-reveal],
.dm-motion :where(.dm) [data-dm-reveal-group] > * {
  opacity: 0;
  transform: translateY(22px);
}

.dm-motion :where(.dm) [data-dm-reveal].is-in,
.dm-motion :where(.dm) [data-dm-reveal-group].is-in > * {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--dm-dur-in) var(--dm-ease-press),
              transform var(--dm-dur-in) var(--dm-ease-press);
  transition-delay: calc(var(--i, 0) * var(--dm-stagger));
}

/* one spring for every button */
.dm-motion :where(.dm) .dm-btn {
  transition: color .12s ease, background-color .12s ease, box-shadow .12s ease,
              transform .3s var(--dm-ease-press);
}

.dm-motion :where(.dm) .dm-btn:hover { transform: translateY(-1px); }
.dm-motion :where(.dm) .dm-btn:active { transform: translateY(0) scale(0.98); }

/* ---------- responsive ---------- */
@media (max-width: 720px) {
  .dm { --dm-pad: 24px; }

  :where(.dm) .dm-h2 {
    font-size: clamp(28px, 8vw, 34px);
    line-height: 1.1;
  }

  /* the sans variant drops under 36px here; -2% is too tight for Geist */
  :where(.dm) .dm-h2--plain { letter-spacing: -0.01em; }

  :where(.dm) .dm-std .dm-container { padding-block: 72px; }
}

/* ---------- code tokens ----------
   The scanner in home.js only decides what each token IS; the colour lives
   here, on palette ramps picked for a dark ground. Sized so the full 22-line
   call fits both terminals without clipping — at 12px it ran nine lines long. */
:where(.dm) .dm-term-body {
  font-size: 10px;
  line-height: 1.4;
}

:where(.dm) .dm-tok-k { color: var(--dm-purple-200); }   /* const, await, new  */
:where(.dm) .dm-tok-s { color: var(--dm-lime-200); }     /* strings            */
:where(.dm) .dm-tok-n { color: var(--dm-orange-200); }   /* numbers            */
:where(.dm) .dm-tok-l { color: var(--dm-orange-300); }   /* true / false / null */
:where(.dm) .dm-tok-p { color: var(--dm-cyan-200); }     /* object keys        */
:where(.dm) .dm-tok-f { color: var(--dm-blue-200); }     /* calls              */
:where(.dm) .dm-tok-i { color: var(--dm-neutral-400); }  /* plain identifiers  */
:where(.dm) .dm-tok-c { color: var(--dm-neutral-500); }  /* comments           */
:where(.dm) .dm-tok-u { color: var(--dm-neutral-500); }  /* punctuation        */
