@import '../tokens.css?v=0f47e1e2';

/* ── Showcase deck ──────────────────────────────────────────────────────
 * The project-showcase page type: a five-to-six-slide peel deck (one
 * slide, one idea) that sits alongside a project's full case study.
 * The peel mechanics (pinning, parallax drag, reveal light, edge
 * shadows) come wholesale from home-featured.css — this file styles
 * what sits ON each plane, project-agnostically.
 *
 * Per-project skin (e.g. nrb-showcase.css) sets, on .peel-stack:
 *   --sc-accent        brand colour — plate grounds, washes, the check
 *   --sc-vignette-ink  hero vignette edge colour (default near-black)
 *   --sc-hero-wash     hero wash opacity (default 0.7)
 * plus any content-specific crops (see .sc-zoom) and hero-mark sizing.
 *
 * Scaffold: ux-case-studies/_showcase-template.html. Pair this file
 * with js/page/showcase.js (progress line, header ink, close replay).
 *
 * Ink and ground constants in here (#fff, #000, #0d0d0d) are
 * media-locked, not theme tokens — same rationale as home-featured.css.
 *
 * Deck rules that live in the markup, not here: max six slots (the
 * peel timelines are enumerated to six); the LAST slot must carry
 * .peel-slot--last (200dvh, never peels); light planes carry
 * .sc-panel--light so showcase.js flips the header ink. */

/* ── Slide statement — one idea per plane, at display scale, centred at
 * the bottom of the plane ──────────────────────────────────────────── */
.sc-statement {
  position: absolute;
  left: var(--panel-gutter);
  right: var(--panel-gutter);
  bottom: var(--panel-gutter);
  margin-inline: auto;
  max-width: 24ch;
  text-align: center;
  font-size: var(--font-size-display-l);
  line-height: var(--line-height-display-l);
  letter-spacing: var(--letter-spacing-display-l);
  font-weight: var(--font-weight-display-l);
  color: var(--panel-ink);
  z-index: 2;
}

.sc-statement small {
  display: block;
  margin-top: var(--space-3);
  margin-inline: auto;
  max-width: 44ch;
  text-wrap: balance;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  letter-spacing: var(--letter-spacing-body);
  font-weight: var(--font-weight-body);
}

/* ── Full-bleed photo slide with a tuned crop ───────────────────────────
 * The wrapper takes the peel drag (it is the media's direct child); the
 * img inside carries the zoom, so the two transforms never fight.
 * Only ever set the crop props (in the skin) — width, the drag-headroom
 * height, and object-fit come from the .feature-panel-media img rule in
 * home-featured.css. Overriding those re-opens a white gap at the
 * bottom edge that the headroom pair exists to prevent. */
.sc-zoom {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.sc-zoom img {
  object-position: var(--sc-zoom-pos, 50% 50%);
  transform: var(--sc-zoom-transform, none);
  transform-origin: var(--sc-zoom-origin, center);
}

/* ── Hero: ONE in-flow centred column — mark, tagline 40px below, CTAs
 * — vertically centred in the viewport. In-flow (not absolute) so the
 * pieces can never overlap on short viewports. ─────────────────────── */
.peel-panel--hero .peel-inner {
  /* safe center: if the stack ever exceeds the panel, align to the top
   * instead of clipping the mark's head off both ends. */
  justify-content: safe center;
  align-items: center;
  padding: var(--panel-gutter);
}

/* Really short viewports: the CTAs are the thing that gives — the mark
 * stays whole and centred. */
@media (max-height: 500px) {
  .sc-cta-row.sc-hero-ctas {
    display: none;
  }
}

/* Near-solid brand wash over the hero media, under the mark. */
.peel-panel--hero .feature-panel-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--sc-accent, #000000);
  opacity: var(--sc-hero-wash, 0.7);
  mix-blend-mode: multiply;
}

/* Vignette over the wash, under the mark — the ground deepening toward
 * the frame edges. Same media-locked-vignette precedent as the R&N
 * panel's Ellipse 1 in home-featured.css. */
.peel-panel--hero .feature-panel-media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(120vw at 50% 50%, transparent 40%, var(--sc-vignette-ink, rgba(0, 0, 0, 0.55)) 100%);
}

/* View-wheel: the hero's decorative mark/logo overlays the media and would
 * otherwise swallow pointer events over the centre of the hero — killing the
 * wheel cursor and its click-to-case-study. Both are aria-hidden decoration,
 * so let hits fall through to the [data-wheel] media beneath. */
.peel-panel--hero .cs-hero-mark,
.peel-panel--hero .feature-panel-logo { pointer-events: none; }

/* Plain white ink for the hero line — difference blend over a solid
 * brand ground can invert to colours this site never uses. */
.peel-panel--hero .feature-panel-text {
  mix-blend-mode: normal;
  position: relative;
  inset: auto;
  margin-top: var(--space-10);
  max-width: none;
  text-align: center;
  z-index: 1;
}

.peel-panel--hero .feature-panel-desc {
  margin-inline: auto;
  /* Even out the lines so the tagline never strands a widow. */
  text-wrap: balance;
}

/* Hero CTA pair centred under the line. The ghost's theme-ink text and
 * border vanish on a dark brand ground — white ink there. */
.sc-cta-row.sc-hero-ctas {
  margin-top: var(--space-6);
  justify-content: center;
}

.peel-panel--hero .btn-ghost {
  color: #ffffff;
  border-color: #ffffff;
}

/* While the header floats over the deck's dark planes, its black brand
 * and hamburger are illegible — white ink everywhere except planes
 * marked .sc-panel--light. Toggled per-slide by js/page/showcase.js;
 * gated off the open menu so the takeover keeps its own colours. */
body:not(.menu-open).is-sc-ink-white .header .brand {
  color: #ffffff;
}

body:not(.menu-open).is-sc-ink-white .header .menu-icon-bar {
  background: #ffffff;
}

/* ── Plate slide: content on a solid brand ground, with an optional
 * photo in .feature-panel-media knocked way down under a brand
 * multiply wash — a barely-there ground, not a photo feature. ───────── */
.sc-panel--plate {
  background: var(--sc-accent, #0d0d0d);
}

/* ── Dark slide: same treatment on the stack's shadow ground. ───────── */
.sc-panel--dark {
  background: #0d0d0d;
}

.sc-panel--plate .feature-panel-media img,
.sc-panel--dark .feature-panel-media img {
  filter: brightness(0.3) saturate(0.25);
}

/* The wash sits on the container (not the dragging img) so the tint
 * never shears during the peel. */
.sc-panel--plate .feature-panel-media::after,
.sc-panel--dark .feature-panel-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--sc-accent, #000000);
  opacity: 0.7;
  mix-blend-mode: multiply;
}

/* ── Phones: one or two device screenshots standing on a plate ──────── */
.sc-phones {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  z-index: 1;
  /* Headroom above the statement so phones and type never collide.
   * Keep IDENTICAL to .sc-video so phones match height across slides. */
  padding: var(--space-16) var(--panel-gutter) calc(var(--panel-gutter) + 120px);
}

.sc-phones img {
  height: 100%;
  width: auto;
  max-width: 42vw;
  object-fit: contain;
}

/* One phone tells the story below desktop — two-up gets cramped and
 * rides over the statement well before the phone breakpoint. */
@media (max-width: 1024px) {
  .sc-phones {
    gap: 0;
  }
  .sc-phones img {
    max-width: 74vw;
  }
  .sc-phones img + img {
    display: none;
  }
}

/* ── Framed video: a demo clip inside the project's own phone frame.
 * The overlay PNG is generated from the same geometry recipe as the
 * project's framed screenshots (see the NRB generator:
 * no-road-block/06-code/tools/nrb_tool_phone-frame-generator.py —
 * 790x1686 body, 102px status band, 6px bezel, r85/r79), so the frames
 * are pixel-identical; the video sits in the screen hole, insets
 * written as percentages of that geometry. ─────────────────────────── */
.sc-video {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Identical padding to .sc-phones — same phone height on both. */
  padding: var(--space-16) var(--panel-gutter) calc(var(--panel-gutter) + 120px);
}

.sc-frame-phone {
  position: relative;
  height: 100%;
  aspect-ratio: 790 / 1686;
}

.sc-frame-phone video {
  position: absolute;
  top: 6.0498%;    /* 102 / 1686 */
  left: 0.7595%;   /*   6 /  790 */
  right: 0.7595%;
  bottom: 0.3559%; /*   6 / 1686 */
  width: 98.481%;
  height: 93.5943%;
  object-fit: cover;
  /* The overlay's screen hole rounds its bottom corners (r79 of the
   * 790 geometry); clip the video to the same curve so its square
   * corners never poke past the frame silhouette. */
  border-bottom-left-radius: 10.154% 5.006%;
  border-bottom-right-radius: 10.154% 5.006%;
}

.sc-frame-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ── Compare slide: a full-bleed .cs-compare (case-study.css) sweeping
 * one state over another edge to edge. Capture both states from the
 * live product at the identical viewport so they align exactly. ─────── */
.sc-compare-wrap {
  position: absolute;
  inset: 0;
}

.sc-compare-wrap .cs-compare {
  width: 100%;
  height: 100%;
  background: transparent;
}

.sc-compare-wrap .cs-compare-img--before {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Drop the labels below the floating header's zone. */
.sc-compare-wrap .cs-compare-label {
  top: var(--space-16);
}

/* Two-ink statement layers. Each spans the full compare box so the
 * clip's --pos percentage means the same thing here as it does on the
 * after-image layer — the white copy's clip edge and the divider are
 * the same line. The statement inside anchors bottom-centre as usual. */
.sc-compare-ink {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}

/* Ink variant for statements on light grounds — the compare's base
 * layer, and any .sc-panel--light plane whose media is light enough
 * that the plain white ink would sink. */
.sc-statement--on-light {
  color: #0d0d0d;
}

.sc-compare-ink--over {
  clip-path: inset(0 0 0 var(--pos, 50%));
}

/* ── The close: page surface, theme ink, centred column ─────────────── */
.sc-panel--cta {
  background: var(--color-surface-page);
  /* Not a .feature-panel, so supply the gutter its count expects. */
  --panel-gutter: var(--page-gutter);
}

.sc-cta-count {
  color: var(--color-text-tertiary);
}

/* The deck's last plane never peels. 200dvh: the -100dvh slot overlap
 * eats the first viewport (the close pinned under the previous slide's
 * travel), the second is its hold — then it scrolls off normally into
 * the footer. */
.peel-stack .peel-slot--last {
  height: 200dvh;
}

.sc-panel--cta .peel-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  padding: var(--panel-gutter);
  text-align: center;
}

/* A product-lifted celebration mark above the close title — the check
 * geometry is the NRB confirmation; swap the SVG per project. Plays
 * each time the slide becomes current (showcase.js toggles .is-live). */
.sc-done {
  position: relative;
  display: flex;
  justify-content: center;
}

.sc-check {
  width: 108px;
  height: 108px;
}

.sc-check circle,
.sc-check path {
  fill: none;
  stroke: var(--sc-accent, #0d0d0d);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.sc-check circle {
  stroke-dasharray: 139; /* 2π · r22 */
  stroke-dashoffset: 139;
}

.sc-check path {
  stroke-dasharray: 30;
  stroke-dashoffset: 30;
}

.sc-panel--cta.is-live .sc-done {
  animation: sc-check-pop 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.sc-panel--cta.is-live .sc-check circle {
  animation: sc-check-draw 0.45s ease-out forwards;
}

.sc-panel--cta.is-live .sc-check path {
  animation: sc-check-draw 0.3s ease-out 0.3s forwards;
}

/* The dot burst — twelve ink dots fling outward as the mark lands; one
 * scaling box-shadow ring carries them all (ripped from the NRB report
 * confirmation). */
.sc-panel--cta.is-live .sc-done::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: transparent;
  box-shadow:
    44px 0 0 var(--sc-accent, #0d0d0d),
    38px 22px 0 -1px var(--sc-accent, #0d0d0d),
    22px 38px 0 var(--sc-accent, #0d0d0d),
    0 44px 0 -1px var(--sc-accent, #0d0d0d),
    -22px 38px 0 var(--sc-accent, #0d0d0d),
    -38px 22px 0 -1px var(--sc-accent, #0d0d0d),
    -44px 0 0 var(--sc-accent, #0d0d0d),
    -38px -22px 0 -1px var(--sc-accent, #0d0d0d),
    -22px -38px 0 var(--sc-accent, #0d0d0d),
    0 -44px 0 -1px var(--sc-accent, #0d0d0d),
    22px -38px 0 var(--sc-accent, #0d0d0d),
    38px -22px 0 -1px var(--sc-accent, #0d0d0d);
  opacity: 0;
  animation: sc-check-dots 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.35s;
}

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

@keyframes sc-check-pop {
  0% { transform: scale(0.3); }
  55% { transform: scale(1.12); }
  100% { transform: scale(1); }
}

@keyframes sc-check-dots {
  0% { transform: scale(0.25) rotate(0deg); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: scale(2.1) rotate(28deg); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .sc-panel--cta.is-live .sc-done,
  .sc-panel--cta.is-live .sc-done::before,
  .sc-panel--cta.is-live .sc-check circle,
  .sc-panel--cta.is-live .sc-check path {
    animation: none;
  }
  .sc-check circle,
  .sc-check path {
    stroke-dashoffset: 0;
  }
}

.sc-cta-title {
  font-size: var(--font-size-display-l);
  line-height: var(--line-height-display-l);
  letter-spacing: var(--letter-spacing-display-l);
  font-weight: var(--font-weight-display-l);
  color: var(--color-text-heading);
}

.sc-cta-sub {
  max-width: 44ch;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  letter-spacing: var(--letter-spacing-body);
  font-weight: var(--font-weight-body);
  color: var(--color-text-secondary);
}

.sc-cta-row {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  justify-content: center;
}

/* ── More showcases — the two .case-card links to the other decks,
 * inside .main after the stack. The stack's negative margins eat the
 * page's top padding, so this section supplies its own breath between
 * the close plane and the cards. ────────────────────────────────────── */
.sc-more {
  margin-top: var(--space-24);
}

/* ── Deck progress — a 4px line along the bottom tracking how far
 * through the stack's runway the reader is (driven by showcase.js).
 * White + black hairline, NOT difference blend — difference inverts to
 * off-brand colours over saturated grounds. ─────────────────────────── */
.sc-progress {
  position: fixed;
  bottom: 0;
  left: 0;
  height: 4px;
  width: 0;
  background: #ffffff;
  border-top: 1px solid #000000;
  z-index: 50;
  pointer-events: none;
}

/* ── Small screens ───────────────────────────────────────────────────── */
@media (max-width: 640px) {
  .sc-statement {
    font-size: var(--font-size-display-m);
    line-height: var(--line-height-display-m);
    letter-spacing: var(--letter-spacing-display-m);
    font-weight: var(--font-weight-display-m);
  }
}
