/* ==========================================================================
   THE WALL DEMO ENGINE — shared by every coded recreation on this site
   The camera, the bezel, the screen it looks through, and the confetti canvas
   that sits over it. Nothing here knows which tab is being recreated.

   A tab is authored at the wall's own size (1284px wide) and SCALED by
   transform rather than rendered small: DOM scales without resampling, so a
   zoom stays sharp where a screenshot would turn to mush. That is the whole
   reason these are coded and not captured.

   Its partner is wall-demo.js, which measures --fit and runs the beats. A page
   adds its own tab markup, its own styles and its own camera tracks; see
   chores-demo.* and calendar-demo.* for the two that exist.
   ========================================================================== */

@property --z  { syntax: "<number>"; initial-value: 1; inherits: false; }
@property --tx { syntax: "<number>"; initial-value: 0; inherits: false; }
@property --ty { syntax: "<number>"; initial-value: 0; inherits: false; }

/* A grid item defaults to min-width:auto, so the track sizes to the item's
   min-content. The .mock this replaced capped itself at 540px and capped the
   track with it; the stage has no such cap, so the hero's column sized itself
   from the tab and came out 1187px wide inside a 390px phone. The item is told
   it may shrink. */
.page-hero-visual,
.pane-visual { min-width: 0; }

/* The confetti canvas. The app fires a full-screen burst; here it is scoped to
   the screen inside the bezel, because a marketing page throwing confetti over
   its own headline is a different thing entirely. Above the camera, below
   nothing — it is the topmost layer in the frame. */
.ct-confetti {
  position: absolute;
  inset: 0;
  /* Stated, not inferred. A canvas falls back to its intrinsic 300x150 when
     nothing sizes it, and `inset` alone did not — so the burst was firing from
     the edges of a small box in the corner rather than from the screen. */
  width: 100%;
  height: 100%;
  /* Above the celebration, not below it. The component fires at the very top
     of the stacking order (CELEBRATION_Z_INDEX); here the canvas sat at 3 and
     the card's backdrop at 4, so every piece of the redemption confetti was
     thrown behind a 14px blur and a 72% black wash — fired correctly and
     invisible. */
  z-index: 6;
  pointer-events: none;
}

/* ── the bezel ──────────────────────────────────────────────────────────── */
/* The same frame the homepage explore panels wear (.explore-frame): every
   other piece of product UI on this site sits in one, and without it these
   stages were bare rectangles whose background is nearly the page's own — you
   could not tell where the wall stopped and the page began. The screen keeps
   its own clipping; the frame only surrounds it. */
.ct-frame {
  padding: 12px;
  border-radius: 26px;
  background: #14181b;
  box-shadow:
    0 46px 90px rgba(23, 35, 38, 0.22),
    0 10px 28px rgba(16, 18, 38, 0.1);
}


/* ── the camera ─────────────────────────────────────────────────────────── */
/* --fit is written by chores-demo.js from the stage's measured width: the
   scale that makes the 1284px tab exactly fill it. Every zoom is a multiple of
   that, so the tracks below are resolution-independent and the same numbers
   work at any breakpoint. */
.ct-stage {
  position: relative;
  /* Width stated outright. The .mock this replaced was capped at 540px, which
     also capped the grid track it sat in; without a cap the hero's column sized
     itself from this box and came out 1187px wide inside a 390px phone. */
  width: 100%;
  min-width: 0;
  max-width: 100%;
  aspect-ratio: 1284 / 883;
  overflow: hidden;
  border-radius: 15px;   /* the frame's 26 less its 12 of padding */
  /* clip-path as well as overflow. .ct-cam is a promoted layer (will-change),
     and a promoted child is clipped by the compositor, which antialiases the
     rounded corner against whatever the stage itself paints — this near-white.
     The result was a dotted white stair-step along each arc whenever something
     was animating. clip-path clips the same box crisply. */
  clip-path: inset(0 round 15px);
  background: #fdf9f3;
}

.ct-cam {
  position: absolute;
  top: 0;
  left: 0;
  width: 1284px;
  transform-origin: 0 0;
  transform: scale(calc(var(--fit, 0.4) * var(--z)))
             translate(calc(var(--tx) * 1px), calc(var(--ty) * 1px));
  /* No will-change here. A standing `will-change: transform` promotes this to
     its own compositor layer permanently, and the stage's rounded clip is then
     applied to that layer by the compositor — which antialiases its edge
     against nothing and composites the result over the black bezel as a bright
     fringe. That is the dotted white line that appeared along the corners of
     all three screens whenever anything moved. The transform is driven by CSS
     keyframes, so it gets a composited layer for the duration of the animation
     anyway; measured, the promotion the animation itself asks for produces no
     fringe. The hint bought nothing and cost the corners. */
}

/* The wall mounts <Toaster position="top-center" richColors />, so a success
   lands as Sonner's green pill at the top of the screen rather than the dark
   one this had. Colours are Sonner's own success tokens. */
.ct-toast {
  position: absolute;
  top: 14px;
  left: 50%;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 10px;
  background: hsl(143, 85%, 96%);
  border: 1px solid hsl(145, 92%, 91%);
  color: hsl(140, 100%, 27%);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 6px 20px rgba(16, 18, 38, 0.12);
  opacity: 0;
  transform: translate(-50%, -14px);
}
.ct-toast.is-on { opacity: 1; transform: translate(-50%, 0); transition: opacity .3s ease, transform .35s cubic-bezier(.2,.9,.3,1); }
.ct-toast.is-off { opacity: 0; transform: translate(-50%, -10px); transition: opacity .3s ease, transform .3s ease; }

/* The press the shared `press()` helper triggers. The keyframe is here rather
   than on a page because the helper is: a page that borrows the gesture should
   not have to carry a copy of what it looks like. */
@keyframes ct-press { 0% { transform: scale(1) } 40% { transform: scale(.84) } 100% { transform: scale(1) } }

/* ── the camera's easing ────────────────────────────────────────────────── */
/* One curve for every track on the site, so two sections on two pages move
   with the same hand. A track that wants a different feel overrides it on its
   own keyframe, as ct-tour does for its steady pan. */
.ct-stage[data-track] .ct-cam { animation-timing-function: cubic-bezier(.65,.02,.31,1); }

/* Motion is the argument on these pages, so with it off a section settles on
   the frame that carries its point rather than freezing mid-move. Only the
   camera is reset here — what else a page has to still is the page's business,
   and it stops it by name in its own stylesheet. */
@media (prefers-reduced-motion: reduce) {
  .ct-stage[data-track] .ct-cam { animation: none !important; --z: 1; --tx: 0; --ty: 0; }
  /* The toast is the engine's, so stilling it is the engine's job too. */
  .ct-toast { transition: none !important; }
}

/* ── subtitles ──────────────────────────────────────────────────────────── */
/* A guided build needs captions, and captions are the one thing on these
   stages that is NOT product UI — the wall has nothing like them. So they read
   as captions: a dark pill at the foot of the screen, plainly an overlay.

   On the STAGE, never inside .ct-cam. The camera scales everything it wraps,
   so a caption in there would grow and shrink with the zoom and drift with the
   pan — unreadable at one end of a track and enormous at the other. Out here
   it is a fixed size in screen pixels, which is what a caption has to be. */
.ct-sub {
  position: absolute;
  left: 50%;
  bottom: 12px;
  z-index: 7;
  max-width: 88%;
  padding: 6px 12px;
  border-radius: 8px;
  background: rgba(18, 22, 25, 0.84);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.35;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translate(-50%, 6px);
  transition: opacity 260ms ease, transform 260ms cubic-bezier(.2, .9, .3, 1);
  pointer-events: none;
}
.ct-sub.is-on { opacity: 1; transform: translate(-50%, 0); }

/* A caption is only worth its room if it can be read. Below the breakpoint
   where the stage stops being wide enough for 12px text at full length, they
   go — the animation still carries the argument, which is the whole premise of
   these sections.

   This applies to the pill INSIDE the stage. A caption under the bezel is not
   competing with the screen for room and stays. */
@media (max-width: 600px) {
  .ct-stage > .ct-sub { display: none; }
}

/* ── captions under the bezel ───────────────────────────────────────────── */
/* The other place a caption can go. Inside the stage it has to be a pill,
   because it is lying over a lit screen; out here it is on the page's own
   ground and can be what it actually is — a line of narration under a figure.
   It also stops covering the product: the pill sat across an action row on one
   chapter and a Back button on another, which is a poor trade for twelve
   pixels of text.

   The wrapper is a GRID with every caption in the one cell, so the row is as
   tall as the tallest caption and nothing moves when they swap. Reserving a
   height by hand is the same job done worse: it has to be re-guessed every time
   a caption is rewritten, and it is wrong at the width where one of them wraps.

   Not `position: absolute`, for the same reason — the row has to take part in
   layout or the section below it sits under the captions. */
[data-ct-wrap] { position: relative; }

.ct-caps {
  display: grid;
  margin-top: 16px;
  padding-inline: 8px;
}

[data-ct-wrap] .ct-sub {
  grid-area: 1 / 1;
  position: static;
  max-width: none;
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--muted, #5f6178);
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: 0;
  text-align: center;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  transform: translateY(4px);
}

[data-ct-wrap] .ct-sub.is-on { transform: none; }

/* These stay at phone width. The rule above takes away the pill inside the
   screen, which is addressed as `.ct-stage > .ct-sub` precisely so that it
   cannot reach these — a caption under the bezel has the column's full width
   and is the most readable thing on the page there. */
@media (max-width: 600px) {
  .ct-caps { margin-top: 12px; }
  .ct-caps .ct-sub { font-size: 0.82rem; }
}
