/* 평소 → 1회 진입 → 올릴때 — one scrolling screen, not three pages.
   assets/enter.js turns the scroll into three numbers:

     --p  raw fold 0 → 1
     --s  stage one, 0 at 평소 → 1 at 1회 진입 (p 0.2). Only the bottom bar and the card
          edge read it; nothing inside the hero moves during it.
     --q  stage two, 0 at 1회 진입 → 1 at 올릴때. Everything that moves reads this one.

   ONE horizontal line runs through the whole thing — the bottom of the card, the top of
   the bottom bar, and the top of the rising Explore bar are all the same edge, measured
   up from the bottom of the frame:

     edge inset = 80 + 37·s + 432·q        →  80 at 평소, 117 at 1회 진입, 549 at 올릴때
     Explore bar top inset = 300·p + LIFT  →  LIFT = 9 + 240·p

   Those two are algebraically identical for p ≥ 0.2 (both reduce to 9 + 540·p), which is
   what lets the fixed 평소 bar hand over to the scrolling Explore bar without a seam.
   Below 0.2 the Explore bar is simply behind the 평소 bar. Change one constant and every
   other one has to move with it. */

/* THE ARRIVAL STATE, in CSS. enter.js opens the page on 1회 진입 (`lead = STOP`, so
   p 0.2 / s 1 / q 0) — but it is a deferred script, and until it has run its first paint
   every var(--s, 0) below falls back to 평소. That first frame is real: the bottom bar
   showed "877 joined this week" and then flipped to Explore Packs a frame or two later.
   It is worse than a flicker on a cross-document view transition, which freezes the
   incoming document's FIRST frame for the length of the fade — the wrong bar then sits
   there for the whole half second.
   Declared, not defaulted per-var: enter.js writes these as inline styles, which outrank
   a stylesheet, so this only ever describes the state before the first paint. */
:root, .dj-screen { --p: 0.2; --s: 1; --q: 0; }

/* Vertical only. Left as auto, the horizontal axis rubber-bands under a sideways drag
   and the 16px content margins visibly slide with it. */
.dj-screen { background: var(--color-black); overflow-x: hidden; overscroll-behavior-x: none; touch-action: pan-y; }

/* No entrance on the frame. This page used to arrive with `opacity: 0; scale(0.88)` on
   .phone, which is the DEVICE — so the status bar grew with everything else and the whole
   screen washed out to the page's white on the way in. A status bar has to be the one
   thing that never moves, and the wash was the "blink" this transition kept showing.
   The page arrives as itself; the fold's own intro (Explore This Pack rolling down) is
   the motion this screen is supposed to have. */

/* ── Hero ────────────────────────────────────────────────────────────────── */
.dj-hero {
  position: sticky;
  /* Sticky only for as long as the fold needs it. Past the open stop --over grows with
     the scroll and drags the sticky line up out of the frame, so the header leaves the
     way any header leaves instead of parking over the rest of the page. */
  top: calc(-1 * var(--over, 0px));
  height: 100%;
  /* Above the sheet, not under it. The sheet is a normal scroller and momentum carries it
     past the fold; at z-index 1 it slid OVER the folded header and sliced the creator line
     and the tag pill off. It scrolls beneath the header now, which is what a folded header
     is for. */
  z-index: 3;
  /* Clipped, not resized: the photo is cropped from the bottom rather than squashed,
     and the card rounds off as it folds. Layout height stays 780 so the flow below it
     is unaffected — that is what keeps the edge equation linear.
     Measured up from the BOTTOM, so it scales with the viewport instead of assuming 780.
     Nothing about the corners changes for the whole fold — the concave 40 junction of 평소
     is the same junction the folded header keeps, so the card only ever rises. */
  --edge: calc(80px + var(--s, 0) * 37px + var(--q, 0) * 432px);
  --fillet: 40px;
  /* One shape, one rasterisation. The bottom corners used to be a union of four gradient
     mask layers, and a gradient has no shape antialiasing — its arc met the clip-path's
     bottom edge at a visible angle instead of running into it. `round` on the inset draws
     the same r40 card, antialiased as a shape, and the corner arrives flat. */
  clip-path: inset(0 0 var(--edge) 0 round 0 0 var(--fillet) var(--fillet));
  /* The fold's first frame was the expensive one: at rest nothing here has been painted
     for a while, and the moment the drag starts the whole header — photograph, video,
     blur — is invalidated and rasterised again inside one frame. Measured at 6x CPU
     throttling: a 141ms frame at the start of the pull and 8ms after it. Declaring what
     moves keeps the layer alive across the rest, so that first frame has something to
     work from. */
  will-change: clip-path, top;
  /* The fold is driven by the handler in enter.js for the finger as well as the mouse,
     so the browser must not ALSO scroll this — that would move the fold twice. Same
     reason .dj-bar opts out below. */
  touch-action: none;
}
.dj-photo {
  position: absolute;
  inset: 0;
  background-size: cover;
  /* centre, not 30%/20%: the still IS the clip's first frame now, and the video on top is
     object-fit: cover — which is background cover centred. Any other origin here and the
     two are framed differently, so the moment playback starts the picture shifts. */
  background-position: center;
  transform: scale(calc(1 + 0.06 * var(--q, 0)));
  /* Only at the very end, where it becomes the blurred 올릴때 header — a full-height
     blur running the whole way would cost frames on a phone for nothing. */
  filter: blur(calc(max(0, var(--q, 0) - 0.55) * 50px));
}
/* The warm blurred band the title sits on. Heavy and tall, as drawn: the legs behind
   the copy are meant to go soft, not just tint. It travels with the copy, so the text
   keeps its footing the whole way down. */
.dj-hero::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 62%;
  z-index: 1;
  pointer-events: none;
  /* Sampled from the bottom third of this pack's own frame by packs.js. The pair below
     is Sean's, kept as the fallback so the markup's own pack looks the same as it
     always did if the sample never lands. */
  background: linear-gradient(180deg, var(--pack-wash-a, rgba(108,94,67,0.42)) 0%, var(--pack-wash-b, rgba(210,183,130,0.78)) 100%);
  -webkit-backdrop-filter: blur(30px);
  backdrop-filter: blur(30px);
  /* Reaches full strength early (40%, not 70%) — that is most of the difference in
     how blurred the band reads. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 40%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 40%);
  transform: translateY(calc(var(--q, 0) * -432px));
}
/* …and the flat gradient fills in behind everything as the fold completes, which is
   what the 올릴때 header sits on. */
.dj-wash {
  position: absolute;
  inset: 0;
  z-index: 2;
  /* The same sample as the band above it. This was the one surface on the page still
     wearing the hand-picked tan: the band at the foot of the clip took the pack's own
     colour, and then the fold completed and dropped a fixed gold over the whole header,
     so every pack ended up the same warm screen at 올릴때 no matter what the clip was.
     Sean's pair stays as the fallback so the markup's own pack is unchanged if the
     sample never lands. */
  background: linear-gradient(180deg,
              var(--pack-wash-a, rgba(120,105,72,0.5)) 0%,
              var(--pack-wash-b, rgba(214,188,138,0.72)) 100%);
  opacity: calc(var(--q, 0) * 2.2 - 1.2);
  pointer-events: none;
}
.dj-hero .status-bar,
.dj-hero .app-bar-transparent { position: absolute; left: 0; right: 0; z-index: 6; }
.dj-hero .status-bar { top: 0; }
/* top comes from main.css: 44px on the desktop mockup, 0 on a phone where our
   status bar is gone and this row belongs directly under the phone's own. */

/* Copy and pills keep a constant gap to the card edge for the whole fold: the edge rises
   37px through 1회 진입 and 432 more after it, so they ride both. They do not move
   RELATIVE to the card — the set stays intact — they just go up with it. */
.dj-copy, .dj-side, .dj-hero .fire-icons {
  transform: translateY(calc(var(--s, 0) * -37px + var(--q, 0) * -432px));
}
.dj-copy, .dj-side {
  opacity: calc(1 - 3 * max(0, var(--q, 0) - 0.4));
}
.dj-side {
  position: absolute;
  right: 20px; bottom: 136px;
  display: flex; flex-direction: column; gap: 11px;
  z-index: 4;
}
.dj-copy {
  position: absolute;
  left: 20px; right: 88px; bottom: 128px;
  z-index: 4;
  color: #fff;
  cursor: pointer;
}
.dj-title { margin: 0 0 12px; max-width: 210px; font-family: var(--font-supreme-bold); font-size: 32px; line-height: 1.2; letter-spacing: -1.5px; }
.dj-desc {
  margin: 0;
  max-width: 205px;
  font-family: var(--font-supreme);
  font-size: 15px; line-height: 1.4; letter-spacing: -0.5px;
  color: var(--color-neutral-50);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: 42px;
  transition: max-height .5s cubic-bezier(.22,1.2,.36,1);
}
.dj-copy.desc-open .dj-desc { -webkit-line-clamp: unset; }

.dj-hero .fire-icons { position: absolute; left: 46px; bottom: 92px; z-index: 4; opacity: calc(1 - 1.6 * var(--s, 0)); }

/* The profile is the one thing that carries across, so it is the one thing that moves —
   and only in the second stage. It travels to the centre of the folded header and grows
   into the 올릴때 avatar.
   At rest it sits 8px above the copy, whatever height the copy currently is: tapping the
   description to read it in full pushes the profile up rather than letting the title run
   through it. The travel is written as rest → 636 rather than rest + 369, so it lands in
   the same place whether the description was open or closed. --copyh is kept in sync in
   pyeongso.html. */
.dj-profile {
  position: absolute;
  left: 20px;
  --dj-rest: calc(136px + var(--copyh, 131px) + var(--s, 0) * 37px);
  /* 636 FROM THE BOTTOM, which is the only end that means anything here: the fold's edge
     is measured up from the bottom too (549 at q=1), so 636 is a fixed 87 above it on a
     frame of any height. Written as 100% - 144px it was 87 above the edge at exactly one
     height, 780 — installed, where the status inset takes 32 off the frame, the avatar and
     the creator line under it slid down past the edge and the sheet covered them. */
  bottom: calc(var(--dj-rest) + (636px - var(--dj-rest)) * var(--q, 0));
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  color: #fff;
  transform: translateX(calc(var(--q, 0) * 109px));
}
.dj-profile img {
  width: calc(24px + var(--q, 0) * 77px);
  height: calc(24px + var(--q, 0) * 77px);
  border-radius: 999px;
  display: block;
  object-fit: cover;
  border: solid rgba(255,255,255,var(--q, 0));
  border-width: calc(var(--q, 0) * 2px);
  box-sizing: border-box;
}
.dj-profile .who { display: flex; flex-direction: column; gap: 2px; opacity: calc(1 - 2 * var(--q, 0)); }
.dj-profile .name { font-family: var(--font-supreme-bold); font-size: 15px; letter-spacing: -0.5px; }
/* The creator line only belongs to the folded state, so it arrives last. */
.dj-meta {
  position: absolute;
  left: 0; right: 0;
  /* Same reason as the avatar above: anchored to the BOTTOM, because that is where the
     fold's edge is. 575 leaves the pill 26 clear of the edge at q=1 and keeps doing so
     on a frame that is not 780 tall. Anchored to the top it was clear on the design frame
     and 11px under the sheet in the installed app — the creator's name, cut in half. */
  bottom: 575px;
  z-index: 5;
  text-align: center;
  color: #fff;
  opacity: calc(var(--q, 0) * 3.5 - 2.4);
  pointer-events: none;
}
.dj-meta .sub { margin: 0 0 13px; font-family: var(--font-supreme); font-size: 14px; letter-spacing: -0.42px; }


/* ── What rises into the gap ──────────────────────────────────────────────── */
/* LIFT: they come up faster than the scroll, which is what makes the top area read as
   shrinking rather than merely scrolling away. */
.dj-explore, /* The explore bar gives its height back to the flow as the fold runs, which relaid the
   whole sheet under it on every frame. The sheet's own contents never change while that
   happens, so its layout is contained and only its position moves. */
.dj-sheet { contain: layout style; }
.dj-sheet { transform: translateY(calc(-9px - var(--p, 0) * 240px)); }

/* Height, not opacity: it has to give its 100px back to the flow by the time the sheet
   reaches the folded header, or the sheet lands 100px too low. Holds full size well past
   1회 진입, where it is the whole point of the state. */
.dj-explore {
  position: relative;
  z-index: 2;
  height: calc(117px * clamp(0, (1 - var(--q, 0)) * 1.35, 1));
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-black);
  opacity: clamp(0, (1 - var(--q, 0)) * 3, 1);
  cursor: pointer;
}

.dj-sheet {
  position: relative;
  z-index: 2;
  background: var(--color-black);
  /* The sheet is lifted 249px at full fold, which otherwise leaves exactly that much
     dead scroll under the last card. Take it back out of the flow height. */
  margin-bottom: -249px;
}

/* ── Bottom bars ──────────────────────────────────────────────────────────── */
/* ONE bar. Its top edge IS the card edge, so it grows 80 → 117 across stage one while
   the joined row rolls away upward and Explore Packs rolls in from below — the swipe
   moves one strip's contents, it never lays a second frame over the first.
   Fully opaque throughout: the old crossfade let the photo show through the black,
   which is what read as a tan frame appearing.
   absolute, not fixed: fixed anchors to the browser viewport, which on desktop is the
   grey page around the mock. .phone is position:relative and fills the viewport on a
   real phone, so absolute is the same thing there and correct here. */
.dj-bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 8;
  /* No safe-area term. It grew the bar UPWARD from bottom:0, so on an iPhone the bar's
     top sat 34px above the card edge — over the fillets, which is why the inverted corner
     disappeared there — and 34px above the in-flow Explore bar it hands over to. The
     roll's content lives in the bar's top 52px, well clear of the home indicator, so the
     term was never buying anything. */
  height: calc(80px + var(--s, 0) * 37px);
  background: var(--color-black);
  /* NO upward overshoot. There used to be 2px of black painted above this bar's top edge
     to hide a hairline of photo that opened up mid-drag between the bar's box edge and
     the card's clip edge. It was painted across the FULL width, so it also lay over the
     bottom 2px of the card's r40 corners — the exact strip where the arc is flattening
     out. The corner was left ending on a steep slope instead of running level into the
     bottom edge, which is what read as the frame and the black below it overlapping.
     The hairline it guarded is gone anyway: the corner is one clip-path now, not a
     gradient mask, so the edge and the bar's top rasterise together. Measured at a
     fractional mid-roll state — the only state it ever showed in — the seam is clean. */
  overflow: hidden;
  /* Cut the instant the fold starts, not over the next 7px of scroll. Past q 0 the bar's
     top stops tracking the card edge while the in-flow Explore bar keeps going, so any
     crossfade window shows the two of them a few px apart — that was the doubled
     "Explore Packs" with a pale line under it. At q 0 they coincide exactly, so an
     instant cut is invisible. */
  opacity: calc(1 - var(--q, 0) * 200);
  /* iOS hands the gesture to its own scrolling unless the element opts out, which left
     the strip's drag handle dead on a phone. */
  touch-action: none;
}
.dj-roll { transform: translateY(calc(var(--s, 0) * -80px)); }
.dj-roll-joined { height: 80px; padding-top: 8px; box-sizing: border-box; }
.dj-roll-explore { height: 117px; display: flex; align-items: center; justify-content: center; }

/* Slot machine — and ONLY while it is turning. enter.js adds .dj-rolling for 0 < s < 1,
   so at either rest the two rows carry no transform, no 3D context and no opacity of
   their own: they rasterise exactly as flat text, and the crease where the faces meet
   never parks on the card edge (that was the stray hairline under the frame).
   Each row turns ON THE SPOT, about its own horizontal centre line, and stays inside the
   black strip — the joined row tips backwards out of sight and Explore Packs tips up into
   its place. Nothing travels: the earlier versions carried the row upward and it read as
   the text sliding out under the card above, which is not what a slot does.
   The strip's own 80px translate is off for the same reason. Each row is then sitting at
   the position it rests at in its own state, which is also what makes the two boundaries
   seamless — at s 0 and s 1 the transform is identity and the row has not moved. */
.dj-rolling .dj-bar { perspective: 300px; }
.dj-rolling .dj-roll { transform: none; transform-style: preserve-3d; }
.dj-rolling .dj-roll-joined,
.dj-rolling .dj-roll-explore { backface-visibility: hidden; }
.dj-rolling .dj-roll-joined {
  transform: rotateX(calc(var(--s, 0) * 82deg));
  opacity: calc(1 - var(--s, 0) * 1.35);
}
/* Lifted onto its own resting slot first: it lives 80px lower in the flow, which is how
   the two rest states work when nothing is turning. */
.dj-rolling .dj-roll-explore {
  transform: translateY(-80px) rotateX(calc((var(--s, 0) - 1) * 82deg));
  opacity: calc(var(--s, 0) * 1.35 - 0.35);
}
.dj-roll .joined-row { padding: 0 16px; }

/* Start is the action on the OPEN screen, so it never scrolls away — and it stays out of
   1회 진입, which has no button in the original. It lives OUTSIDE the scroller: .dj-sheet
   is transformed, and a transformed ancestor becomes the containing block for it, which
   would have pinned the button to the sheet instead of the screen. The copy inside the
   content is hidden in its favour. */
.dj-screen .creator-start { display: none; }
.dj-start {
  position: absolute;
  left: 16px; right: 16px;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  width: auto;
  z-index: 9;
  opacity: calc(var(--q, 0) * 5 - 1.2);
  pointer-events: none;
  /* Solid, as drawn. The button is not the glass — the BAND BEHIND IT is (.dj-blur), so
     everything that scrolls under the button goes soft while the pill itself stays flat
     and fully opaque. */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.24), 0 10px 28px rgba(0, 0, 0, 0.28);
}

/* Figma 829:13834 / blur.svg — the veil the Start button sits on. Content scrolling into
   the bottom of the frame is blurred, ramping in from nothing so there is no edge where
   it starts; the button is above it and untouched. The blur is the whole effect.
   The svg also carries a soft #F2F2F2 bloom (165x55 at the box's middle, 2.8% alpha),
   which was reproduced here as a radial and has been removed: this band only ever sits on
   the pack sheet, which is #0A0A0A, and 2.8% white over near-black is not a subtle
   warmth — it measured as a +5 lift over rgb(6,10,11) and read as a white smudge behind
   the button on an otherwise flat black. A light bloom needs something light to bloom on.
   Tied to --q like the button, so 평소 keeps the hero's own blurred band and nothing else. */
.dj-blur {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  /* 200 reached well above the button and the softening was readable a third of the way
     up the sheet; 120 keeps the ramp inside the button's own row. */
  height: 120px;
  z-index: 7;                       /* under .dj-bar and .dj-start, over the sheet */
  pointer-events: none;
  opacity: calc(var(--q, 0) * 5 - 1.2);
  -webkit-backdrop-filter: blur(3px);
          backdrop-filter: blur(3px);
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 58%);
          mask-image: linear-gradient(180deg, transparent 0, #000 58%);
}
/* opacity alone leaves both Starts clickable, so the handover is a real one:
   enter.js sets .dj-open once the fold is past the crossfade. */
.dj-open .dj-start { pointer-events: auto; }
.dj-open .dj-bar { pointer-events: none; }
/* Same trap, four more layers: the title/description, the bookmark and share pills and
   the fire icons all fade to nothing over the folded header but stay in the hit path,
   so a drag on the profile area was landing on an invisible caption and a tap was
   toggling a description nobody can see. The photo goes with them — its tap is
   play/pause, which means nothing once it is a blurred header. What is left under the
   thumb is .dj-hero itself, which is inside the scroller and therefore drags the fold. */
.dj-open .dj-copy,
.dj-open .dj-side,
.dj-open .dj-photo,
.dj-open .dj-hero .fire-icons { pointer-events: none; }
/* The content already ends with it, so leave room rather than letting it sit on the copy. */
.dj-sheet .creator-content { padding-bottom: 108px; }

/* Inside .dj-photo, so the fold's scale and blur come for free. */
.dj-photo video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
