/* The free-tier paywall: dark panel on the dashboard, mirrors the
   gallery CTA panel. Plan cards come from the shared pricing surface
   (shared/_pricing_plans), stacked to fit the narrow dashboard column. */

.paywall {
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-5);
  margin-top: var(--space-4);
  display: grid;
  gap: var(--space-4);
}

.paywall-kicker {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-soft);
}

.paywall h2 {
  font-size: var(--text-xl);
}

.paywall-sub {
  opacity: 0.85;
  max-width: 36rem;
}

.paywall-unlocks {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.paywall-unlocks li::before {
  content: "✓ ";
  color: var(--accent-soft);
  font-weight: 600;
}

.paywall-pricing {
  display: grid;
  gap: var(--space-4);
  justify-items: center;
}

.paywall-pricing .plan-grid {
  grid-template-columns: 1fr;
  max-width: 30rem;
}

/* Stacked cards: the featured card's upward break is a 3-column move,
   it just cramps a single column. */
.paywall-pricing .plan-card.is-featured {
  margin-top: 0;
  padding-top: var(--space-5);
}

.paywall-pricing .hero-note {
  font-size: var(--text-sm);
  opacity: 0.75;
  text-align: center;
  max-width: 36rem;
}

/* The two-card ladder (USAGE.md, 2026-09-08): Yearly access and the
   3-day trial, the trial preselected. Shared by /paywall and the
   homepage pricing section. Each card is the competitors' row: title
   and sub on the left, the face number on the right; no counts, no
   anchors. Selection is pure CSS: the card highlights via
   :has(:checked) with the accent ring and a faint accent wash; the
   paywall Stimulus controller only swaps the CTA label, so the form
   works without JS either way. No scale on selection any more: a
   full-width phone card grown by 6 percent overflowed the viewport. */

.ladder-form {
  display: grid;
  gap: var(--space-4);
  justify-items: center;
  width: 100%;
}

.ladder {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  width: 100%;
  max-width: 36rem;
  align-items: stretch;
}

.ladder-card {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  text-align: left;
  padding: var(--space-4);
  background: var(--paper-card);
  color: var(--ink);
  border: var(--border-hard);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: box-shadow 150ms ease, background-color 150ms ease;
}

.ladder-card:has(.ladder-radio:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-deep);
  background: color-mix(in srgb, var(--accent) 5%, var(--paper-card));
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .ladder-card {
    transition: none;
  }
}

.ladder-card:has(.ladder-radio:focus-visible) {
  outline: 3px solid var(--accent-soft);
  outline-offset: 2px;
}

/* Top-right, the app pattern, clear of the title. The badge pings
   (owner ask 2026-09-08): every few seconds a thin accent outline
   grows out of the pill and fades, like a plan's dimension ring, and
   the pill itself lifts a hair at the same beat. Staggered a second
   behind the CTA sheen so the two never fire together; decorative, so
   reduced motion removes both. Transform is free to animate here: the
   badge is placed by top and right, not by a transform. */
.ladder-badge {
  position: absolute;
  top: -1em;
  right: var(--space-3);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.04em;
  background: var(--ink);
  color: var(--paper);
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  animation: ladder-badge-lift 3.6s ease-out 1s infinite;
}

.ladder-badge::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  border: 1.5px solid var(--accent);
  opacity: 0;
  pointer-events: none;
  animation: ladder-badge-ping 3.6s ease-out 1s infinite;
}

/* The badged card only: the badge overlaps the top border, so the price
   below it gets 6px of extra air (owner QC 2026-09-09: badge bottom sat
   on the $1.35 box at 375 and 320). */
.ladder-card:has(> .ladder-badge) {
  padding-top: calc(var(--space-4) + 0.375rem);
}

@keyframes ladder-badge-lift {
  0%, 20%, 100% { transform: scale(1); }
  6% { transform: scale(1.08); }
}

/* Peak 1.5, not 1.9 (2026-09-09): at 1.9 the ring reached 15px past the
   viewport on 375 and 320 phones, where the body clips it, so its right
   arc vanished mid-ping. 1.5 stays inside the container gutter. */
@keyframes ladder-badge-ping {
  0% { transform: scale(1); opacity: 0.9; }
  45%, 100% { transform: scale(1.5); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ladder-badge,
  .ladder-badge::after {
    animation: none;
  }
}

.ladder-card:has(.ladder-radio:checked) .ladder-badge {
  background: var(--accent);
}

.ladder-copy {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.ladder-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-base);
  line-height: 1.15;
}

.ladder-sub {
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

.ladder-face {
  display: grid;
  justify-items: end;
  text-align: right;
  flex: none;
}

.ladder-face-figure {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-lg);
  line-height: 1;
}

.ladder-face-unit {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}

.ladder-cta {
  width: 100%;
  max-width: 24rem;
  position: relative;
  overflow: hidden;
}

/* The CTA shimmer (owner ask 2026-08-25, the app-paywall pattern): a
   sheen sweeps the button, rests, sweeps again. Purely decorative, so
   reduced motion removes it outright. */
.ladder-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-130%) skewX(-18deg);
  /* A narrow band: wider washes the button's right half in a glare
     blob mid-sweep instead of reading as a sheen. */
  background: linear-gradient(100deg, transparent 42%, rgb(255 255 255 / 0.35) 50%, transparent 58%);
  animation: ladder-shimmer 2.8s ease-in-out infinite;
  pointer-events: none;
}

@keyframes ladder-shimmer {
  0% { transform: translateX(-130%) skewX(-18deg); }
  55%, 100% { transform: translateX(130%) skewX(-18deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ladder-cta::after {
    content: none;
  }
}

.ladder-trust {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-align: center;
}

.ladder-fine {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-align: center;
  max-width: 34rem;
}

.ladder-fine a {
  color: inherit;
}

/* Stacked on phones, Year above the trial (the screenshot order). */
@media (max-width: 640px) {
  .ladder {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }

  .ladder-card {
    padding: var(--space-3) var(--space-4);
  }
}

/* The /paywall page shell. Since 2026-08-26 it renders in the funnel
   layout with bare chrome (no header, no footer): the section takes
   back the funnel-main padding and supplies its own clearances, so the
   geometry matches the old application-layout render exactly, minus
   the header. */
/* Block, not the funnel's centering grid: as a grid item the section's
   automatic minimum size balloons to the drifting strips' max-content
   track (4,360px wide, QC find 2026-08-26), shoving the page sideways.
   Block layout clips them exactly like the application layout did. */
.funnel-main:has(.paywall-page) {
  display: block;
  padding: 0;
}

.funnel-main .paywall-page {
  width: 100%;
  /* Clears the absolutely-placed X; also the whole top offset now
     that .section's band padding is for pages below a site header. */
  padding-top: var(--space-7);
  padding-bottom: var(--space-7);
}

.paywall-page-head {
  text-align: center;
  display: grid;
  gap: var(--space-2);
  justify-items: center;
  margin-bottom: var(--space-5);
}

.paywall-page-sub {
  color: var(--ink-soft);
}

/* The app-style paywall top (owner ask 2026-08-10): personalized proof
   strip and benefits checklist above the ladder. */

.paywall-strip {
  overflow: hidden;
  margin-bottom: var(--space-5);
  -webkit-mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
}

.paywall-strip-track {
  display: flex;
  width: max-content;
  animation: paywall-drift 50s linear infinite;
}

.paywall-strip:hover .paywall-strip-track {
  animation-play-state: paused;
}

.paywall-strip-tile {
  width: 9.5rem;
  flex-shrink: 0;
  margin-right: var(--space-3);
  display: grid;
  /* Pinned to the tile width: the default auto track sizes to the
     nowrap caption's min-content, and the longest style name then
     inflates img's width:100% past the tile (the Mid-Century Modern
     blowout, owner phone QC 2026-08-26). */
  grid-template-columns: 100%;
  gap: var(--space-1);
  justify-items: center;
}

.paywall-strip-tile img {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: var(--border-hard);
}

.paywall-strip-tile.is-pick img {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}

.paywall-strip-tile figcaption {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  white-space: nowrap;
  /* The backstop for names the tile cannot fit: truncate, never
     resize. Tile and font sizes per breakpoint are chosen so every
     current catalog name fits untruncated; this guards future ones. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.paywall-strip-tile.is-pick figcaption {
  color: var(--accent);
  font-weight: 500;
}

@keyframes paywall-drift {
  to {
    transform: translateX(-50%);
  }
}

/* The tools strip (owner call 2026-08-25): breadth under depth. The
   11 tools counter-drift beneath the styles as a drawing legend: ink
   line icons, mono spec labels, no containers, framed by two fine
   rules like a sheet's title block. Hover pause and the
   reduced-motion park come from the shared classes. */
.paywall-strip:has(+ .paywall-strip-tools) {
  margin-bottom: var(--space-4);
}

.paywall-strip-tools {
  border-block: 1px solid var(--line);
  padding-block: var(--space-3);
}

.paywall-strip-tools .paywall-strip-track {
  animation-name: paywall-drift-back;
  animation-duration: 60s;
  align-items: center;
}

.paywall-tool-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  margin-right: var(--space-7);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

.paywall-tool-item .tool-icon {
  width: 1.05rem;
  height: 1.05rem;
  margin-bottom: 0;
  color: var(--ink);
  flex-shrink: 0;
}

@keyframes paywall-drift-back {
  from {
    transform: translateX(-50%);
  }

  to {
    transform: translateX(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* The tools selector must outrank its own animation-name override
     above; the bare class alone would lose the specificity contest. */
  .paywall-strip-track,
  .paywall-strip-tools .paywall-strip-track {
    animation: none;
  }
}

.paywall-benefits {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  justify-items: start;
  width: fit-content;
  margin: 0 auto var(--space-2);
  text-align: left;
  font-weight: 500;
}

/* Hanging indent (2026-09-09): the inline check is 1.05em plus its
   0.45em gap, so a line that wraps on a 320px phone starts under the
   text, not under the check. */
.paywall-benefits li {
  padding-left: 1.5em;
  text-indent: -1.5em;
}

/* The checks draw themselves once on arrival (2026-09-09): the same
   stroke draw the proof-wall tool icons use (pathLength 1, one keyframe
   on the dash offset), staggered down the list, never repeated, parked
   under reduced motion. Delays are per position because the CSP blocks
   inline styles, so no per-item custom property. */
.paywall-benefits .check-icon path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: check-draw 420ms cubic-bezier(0.2, 0.7, 0.2, 1) 120ms forwards;
}

.paywall-benefits li:nth-child(2) .check-icon path { animation-delay: 190ms; }
.paywall-benefits li:nth-child(3) .check-icon path { animation-delay: 260ms; }
.paywall-benefits li:nth-child(4) .check-icon path { animation-delay: 330ms; }
.paywall-benefits li:nth-child(5) .check-icon path { animation-delay: 400ms; }
.paywall-benefits li:nth-child(6) .check-icon path { animation-delay: 470ms; }

@keyframes check-draw {
  to { stroke-dashoffset: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .paywall-benefits .check-icon path {
    stroke-dashoffset: 0;
    animation: none;
  }
}

/* The drawn check (2026-08-25): the helper's inline svg in the tool
   icons' stroke family, replacing the platform-dependent U+2713
   glyph. */
.paywall-benefits .check-icon,
.ladder-note .check-icon {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  color: var(--accent);
  margin-right: 0.45em;
  vertical-align: -0.15em;
}

.ladder-note {
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: center;
}

/* The dock: note plus CTA as one unit. Inert in the flow by default
   (the homepage pricing section, desktop paywall); the paywall pins it
   at phone widths, below. The tight note-to-CTA gap replaces the old
   negative margin on the note. */
.ladder-dock {
  display: grid;
  gap: var(--space-2);
  justify-items: center;
  width: 100%;
}

/* The X (the app pattern): quiet, top-left, a plain link back to the
   studio since REVEAL.md (2026-09-03). */

.paywall-close {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  background: none;
  border: 0;
  padding: 0;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
}

.paywall-close:hover {
  color: var(--ink);
}

.paywall-page {
  position: relative;
}

/* The phone fold (owner report 2026-08-26, iPhone 13 Pro Max): the
   CTA sat below the fold, worse on smaller phones. Three moves at
   phone widths: the proof strips and page head compress, the ladder
   cards shorten, and the dock pins to the viewport bottom, full-bleed
   with a hairline top rule, so the CTA is on screen from first paint.
   It unsticks into the flow once the trust and legal lines arrive. */
@media (max-width: 640px) {
  /* 7.5rem with the 11px caption fits the catalog's longest name
     (Mid-Century Modern, 18 mono chars at 0.6em advance = 118.8px)
     with no truncation. */
  .paywall-strip-tile {
    width: 7.5rem;
    margin-right: var(--space-2);
  }

  .paywall-strip-tile figcaption {
    font-size: 0.6875rem;
  }

  .paywall-strip {
    margin-bottom: var(--space-4);
  }

  .paywall-strip:has(+ .paywall-strip-tools) {
    margin-bottom: var(--space-3);
  }

  .paywall-strip-tools {
    padding-block: var(--space-2);
  }

  .paywall-page-head {
    margin-bottom: var(--space-4);
  }

  .paywall-page-head h1 {
    font-size: clamp(1.55rem, 7vw, 2rem);
  }

  .paywall-benefits {
    font-size: var(--text-sm);
    gap: var(--space-1);
  }

  /* Full-bleed via the container's known 1.25rem gutters, never vw
     units: a vw width on a grid item inflates the form's shared column
     track and drags the ladder row off center (QC find 2026-08-26). */
  .paywall-page .ladder-dock {
    position: sticky;
    bottom: 0;
    z-index: 2;
    width: calc(100% + 2.5rem);
    margin-inline: -1.25rem;
    padding: var(--space-3) 1.25rem max(var(--space-3), env(safe-area-inset-bottom));
    background: var(--paper);
    border-top: 1px solid var(--line);
  }
}

/* The visitor's own held design leading the page (REVEAL.md decision
   4, 2026-09-03): the server-side blurred preview in the card language,
   a mono tag over it naming the style. Clears the X above it. */

.paywall-reveal {
  position: relative;
  overflow: hidden;
  border: var(--border-hard);
  border-radius: var(--radius-md);
  box-shadow: 5px 5px 0 rgba(16, 16, 16, 0.9);
  background: var(--ink);
  min-height: 10rem;
  margin: calc(var(--space-4) + 44px) 0 var(--space-5);
}

/* The design shows whole (owner call 2026-09-07, after the tall-photo
   repro): under the height cap a portrait render used to be
   center-cropped, which cut a 9:19.5 phone photo to its middle 45
   percent, ceiling and floor gone, and with the blur nothing said
   "room". Now the photo letterboxes inside the cap and a second copy
   of the same preview stretches behind it, cover and dimmed, so the
   side bands read as a wash of the room's own light. A landscape
   render fills the box and hides the backdrop entirely. */
.paywall-reveal-photo {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
  max-height: 42vh;
  object-fit: contain;
}

.paywall-reveal-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.7);
}

.paywall-reveal-tag {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: calc(100% - 2 * var(--space-4));
  background: var(--paper-card);
  border: var(--border-hard);
  border-radius: var(--radius-sm);
  box-shadow: 4px 4px 0 rgba(16, 16, 16, 0.9);
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-align: center;
  color: var(--ink);
}

/* With a design waiting, photo and head share a row on wide screens so
   the ladder starts inside the first screen at 1280x800. */

.paywall-reveal-row {
  display: grid;
  gap: var(--space-5);
  align-items: center;
  margin-top: calc(var(--space-4) + 44px);
}

.paywall-reveal-row .paywall-reveal {
  margin: 0;
}

.paywall-reveal-row .paywall-page-head {
  margin-bottom: 0;
}

@media (min-width: 720px) {
  .paywall-reveal-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    margin-bottom: var(--space-5);
  }

  .paywall-reveal-row .paywall-page-head {
    text-align: left;
    justify-items: start;
  }

  .paywall-reveal-row .paywall-reveal-photo {
    max-height: 34vh;
  }
}

/* Lock, then the line, balanced over two rows on phones; a style's
   hyphen never breaks. */
.paywall-reveal-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  /* width: max-content, not shrink-to-fit: with left: 50% an absolute
     box would size itself to the half it sits in and wrap four times. */
  width: max-content;
  max-width: min(22rem, calc(100% - 2 * var(--space-4)));
  white-space: normal;
  text-align: left;
  text-wrap: balance;
  line-height: 1.5;
}

.paywall-reveal-tag .nowrap {
  white-space: nowrap;
}

.paywall-reveal-tag .lock-icon {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--accent);
}

/* The scratched state on the paywall (SCRATCH.md decision 7): the
   photo's own box, the canvas over it, read-only. */
.paywall-reveal-stage {
  position: relative;
  line-height: 0;
}

.paywall-reveal-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
