/* ==========================================================================
   Hero helix: the example sites on a slow vertical-axis helix around the
   headline, in CSS 3D. The static pose below comes from the custom properties
   tools/build.mjs stamps on each card (--i, --depth, --flip), so the ring reads
   as a helix before JS runs and without it. js/sections/helix.js then writes
   each card's transform and --depth every frame.
   ========================================================================== */

/* The stage is the headline's box and the one 3D space: the headline sits at
   z = 0, the cards at ±radius, so some pass in front of it and some behind. */
.hero__stage {
  --card-w: clamp(120px, 12vw, 190px);
  --helix-k: 2.7;        /* radius = card width × k (helix.js reads it) */
  --helix-y: 59%;        /* the ring's centre, down the headline */
  --helix-tilt: -8deg;   /* the ring seen a little from above (helix.js uses the same) */
  --rise: .06;           /* vertical rise per card, × card width */
  position: relative;
  z-index: 1;
  grid-area: 1 / 1;
  width: 100%;
  perspective: calc(var(--card-w) * var(--helix-k) * 6);
  perspective-origin: 50% var(--helix-y);
  transform-style: preserve-3d;
  touch-action: pan-y;    /* vertical swipes scroll the page, horizontal ones spin the ring */
  user-select: none;
  -webkit-user-select: none;
}
/* The headline is the plane at z = 0. Cards take a z-index from their depth (0-100), so the back half
   sits under it and the front half over it even where a browser paints 3D without sorting. */
.hero__stage .hero__title {
  z-index: 50;
  transform: translateZ(0);
  pointer-events: none;   /* cards behind the letters stay clickable */
}
.js .hero__stage { cursor: grab; }
.hero__stage.is-dragging { cursor: grabbing; }

.helix__card {
  --step: calc(360deg / var(--n, 10));
  position: absolute;
  z-index: calc(var(--depth, 1) * 100);
  top: calc(var(--helix-y) - var(--card-w) * 2 / 3);
  left: calc(50% - var(--card-w) / 2);
  display: block;
  width: var(--card-w);
  color: var(--pearl);
  text-decoration: none;
  transform-origin: 50% calc(var(--card-w) * 2 / 3);   /* the ring's centre */
  transform:
    rotateX(var(--helix-tilt))
    translateY(calc((var(--i) - (var(--n, 10) - 1) / 2) * var(--rise) * var(--card-w)))
    rotateY(calc(var(--i) * var(--step)))
    translateZ(calc(var(--card-w) * var(--helix-k)))
    rotateY(var(--flip, 0deg));
  -webkit-user-drag: none;
  -webkit-tap-highlight-color: transparent;
}
.helix__card img { -webkit-user-drag: none; }
/* With JS the cards wait for the intro, then helix.js fades and lifts them in. If animation frames
   never come (power saving, a hidden tab), is-ready shows them anyway once the intro is over;
   a frame that did run has set the card's own opacity, which wins over this. */
.js .helix__card { opacity: 0; }
.js .hero__stage.is-ready .helix__card { opacity: 1; }

.helix__frame {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border: 1px solid var(--hair);
  border-radius: 12px;
  background: var(--graphite);
  transition: border-color .5s var(--ease-out);
}
.helix__frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}
/* depth dimming: the back of the ring stays readable, only darker */
.helix__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--ink);
  opacity: calc((1 - var(--depth, 1)) * .62);
  pointer-events: none;
}

.helix__caption {
  display: block;
  margin-top: .7em;
  overflow: hidden;
  font-family: var(--sans);
  font-size: max(.5rem, 10px);
  line-height: 1.15;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-overflow: ellipsis;
  white-space: nowrap;
  opacity: calc(.3 + .55 * var(--depth, 1));
  transition: color .5s var(--ease-out);
}

.helix__card:hover .helix__frame { border-color: var(--hair-strong); }
.helix__card:hover .helix__caption { color: var(--champagne); }
.helix__card:focus-visible { outline: none; }
.helix__card:focus-visible .helix__frame {
  border-color: var(--champagne);
  box-shadow: 0 0 0 3px var(--ink), 0 0 0 4px var(--champagne);
}
.helix__card:focus-visible .helix__caption { color: var(--champagne); }

/* phones: smaller cards on a tighter ring, tilted further so the front and back arcs part */
@media (max-width: 767px) {
  .hero__stage {
    --card-w: 26vw;
    --helix-k: 1.75;
    --helix-y: 57%;
    --helix-tilt: -13deg;
    --rise: .08;
  }
  .helix__caption { font-size: 9px; letter-spacing: .03em; }
  .helix__cat { display: none; }
}

/* ==========================================================================
   Viewer: one example site, full screen, in an iframe (built by helix.js)
   ========================================================================== */
.is-viewer-open,
.is-viewer-open body { overflow: hidden; }

.viewer {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-cursor) + 1);
}
.viewer[hidden] { display: none; }

.viewer__panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  clip-path: inset(0 round 0);
}
.viewer.is-animating .viewer__panel { transition: clip-path .75s var(--ease-out); }

.viewer__bar {
  display: flex;
  flex: none;
  align-items: center;
  gap: 1.25rem;
  padding: .7rem 1rem .7rem 1.4rem;
  border-bottom: 1px solid var(--hair-faint);
  transition: opacity .45s var(--ease-out) .2s;
}
.viewer.is-growing .viewer__bar { opacity: 0; transition-delay: 0s; }

.viewer__id { display: flex; align-items: baseline; gap: .9rem; min-width: 0; }
.viewer__title {
  font-family: var(--serif);
  font-size: 1.6rem;
  font-weight: 400;
  line-height: 1;
  white-space: nowrap;
}
.viewer__meta { color: var(--smoke); white-space: nowrap; }
.viewer__note {
  margin-left: auto;
  color: var(--ash);
  opacity: .7;
  white-space: nowrap;
}
.viewer__actions { display: flex; flex: none; align-items: center; gap: .6rem; }
.viewer__close {
  display: grid;
  place-items: center;
  width: 2.3rem;
  height: 2.3rem;
  border: 1px solid var(--hair-strong);
  border-radius: 50%;
  font-size: 1.15rem;
  line-height: 1;
  transition: border-color .5s var(--ease-out), color .5s var(--ease-out);
}
.viewer__close:hover { border-color: var(--champagne); color: var(--champagne); }

.viewer__body { position: relative; flex: 1; min-height: 0; background: var(--graphite); }
.viewer__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--graphite);
  opacity: 0;
  transition: opacity .5s var(--ease-out);
}
.viewer.is-loaded .viewer__frame { opacity: 1; }

/* the quiet loading state: the brand and a hairline that breathes */
.viewer__loading {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1rem;
  color: var(--ash);
  transition: opacity .4s var(--ease-out);
}
.viewer.is-loaded .viewer__loading { opacity: 0; pointer-events: none; }
.viewer__loading-name { font-family: var(--serif); font-size: 2.4rem; line-height: 1; }
.viewer__loading-line {
  width: 6rem;
  height: 1px;
  background: var(--hair-strong);
  transform-origin: left center;
  animation: viewer-line 1.6s var(--ease-in-out) infinite;
}
@keyframes viewer-line {
  0%   { transform: scaleX(0); transform-origin: left center; }
  50%  { transform: scaleX(1); transform-origin: left center; }
  51%  { transform: scaleX(1); transform-origin: right center; }
  100% { transform: scaleX(0); transform-origin: right center; }
}

@media (max-width: 767px) {
  .viewer__bar { flex-wrap: wrap; gap: .35rem .8rem; padding: .6rem .7rem .6rem 1rem; }
  .viewer__id { flex-direction: column; gap: .3rem; }
  .viewer__title { font-size: 1.45rem; }
  .viewer__note { order: 3; flex-basis: 100%; margin-left: 0; }
  .viewer__actions { margin-left: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .viewer.is-animating .viewer__panel,
  .viewer__bar,
  .viewer__frame,
  .viewer__loading { transition: none; }
  .viewer__loading-line { animation: none; transform: none; opacity: .6; }
}

/* the focus trap's two edges: focusable, never seen */
.viewer__edge {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
