/* =============================================================
   ENTRY PICKER
   -------------------------------------------------------------
   Full-bleed two-panel chooser that sits in front of the site.
   Left  = Photography (white, landscape photos, leftward loop)
   Right = Development (warm dark grey, ASCII wave shader)
   A vertical split tracks the cursor inversely and clamps 70/30.
   Panels are revealed by a clip mask, so there is zero layout
   shift. Choosing a side transitions continuously (no remount,
   no blank frames, no popping).
   ============================================================= */

:root {
  --pk-dark: #262521;          /* warm Claude-code grey */
  --pk-white: #ffffff;
  --pk-ink: #16150f;           /* text on the white side */
  --pk-split: 50;              /* live split position, in percent (JS drives it) */
  --pk-ease: cubic-bezier(0.22, 1, 0.36, 1);        /* fast start, gentle arrival - simple fades */
  --pk-ease-exit: cubic-bezier(0.76, 0, 0.24, 1);   /* pronounced ease-in-out - eases in AND out */
}

/* The chooser is the hero of one continuous site. While choosing or on the
   development hold, hide the site's tweak panel and the site cursor; once in
   the photography galleries, hide the chooser's own cursor and show the site's.
   Scroll locking is driven from JS (App sets html/body overflow by mode). */
html:not([data-mode="photo"]) #tweaks-root { display: none; }
html:not([data-mode="photo"]) .cursor { display: none; }
html[data-mode="photo"] .pk__cursor { display: none; }

/* ---------- Stage (the hero section) ---------- */
.pk {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  /* NOT overflow:hidden (see below) - this box's own size (100vh) already
     keeps it from affecting page layout/scroll regardless of overflow. */
  overflow: visible;
  background: var(--pk-dark);
  cursor: none;
  user-select: none;
  -webkit-user-select: none;
  opacity: 1;
  /* Root cause of the falling collage getting hidden behind the gallery's
     background on scroll: neither this element nor the gallery content below
     it (.cat/.gallery/.tile) ever had an explicit z-index anywhere in the
     site. Two elements that are both "auto" break their stacking tie by
     document order - whichever is LATER in the HTML wins and paints on top.
     The gallery comes after this stage in the markup, so the instant
     scrolling brought both into the same screen space (which never happens
     before you scroll - this stage fills exactly the first screen - but can
     happen the moment you scroll while a transition is still playing), the
     gallery's own background would win that tie and paint over this whole
     stage, including the still-falling collage. An explicit z-index removes
     the tie entirely: this stage now always renders above ordinary page
     content, regardless of scroll timing. (Site UI that must stay above the
     stage - Nav, Lightbox, Contact, the custom cursor - all use z-index values
     well north of this, so they are unaffected.)

     This stage does NOT clip its own overflow (see above) - clipping was
     never actually required for "stay on top" or "don't affect page scroll"
     (both come from the z-index and this box's own fixed 100vh height, which
     apply regardless of overflow). The only thing overflow:hidden was ever
     doing was cutting the falling collage off at a fixed line one screen down
     from the top of the PAGE (not the current viewport) - so scrolling during
     the fall made that fixed line appear in an unfamiliar spot on screen,
     reading as "the collage is hiding behind something" when it was actually
     clipping exactly where it always would. Removing the clip lets the
     collage keep falling, visible, past that line - toward the gallery it's
     heading for - and the z-index above already guarantees it renders on top
     of that gallery rather than under it. See floating-photos.js's morphDown
     for how the photos are kept from lingering once they've landed, so
     removing this clip doesn't leave them cluttering the page forever. */
  z-index: 5;
}
.pk.is-fadeout { opacity: 0; transition: opacity 1.4s ease; }

/* Both panels fill the whole stage; the right one is clipped to
   reveal only the area to the right of the split. Not overflow:hidden either,
   for the same reason as .pk above - the seam's own clip-path (see .pk__left)
   already does the actual left/right containment that matters, independent
   of overflow. */
.pk__panel {
  position: absolute;
  inset: 0;
  overflow: visible;
  /* This transform duration is the RETURN/arrival pace (used whenever a
     mode-specific override isn't in effect - see pk__left's dev-mode exit
     below, which gets its own, slower, unhurried departure timing). */
  transition: opacity 1.4s var(--pk-ease), transform 1.5s var(--pk-ease);
}
/* Each panel is its own stacking context so a motif's z-index can never
   escape its side and cross the seam.
   Layering (flipped from the original): the WHITE photography side + collage is
   the full-screen base, and the DEVELOPMENT side is a dark overlay painted ON
   TOP of it, clipped to the right of the split. So the seam is the dark
   overlay's own edge (dark over the cards) rather than the white panel's clipped
   edge over the dark - which is what removed the bright white shimmer that used
   to sweep along the seam as the split tracked the cursor. Whichever side loses
   on a pick still has the always-painted other side beneath it, so no blank
   frame. (Original arrangement - white clipped on top of a full wave base - is
   in git history if we ever need it.) */
.pk__right {
  background: var(--pk-dark);
  /* Clipped to the RIGHT of the split and sitting on top (z-index above the
     white base). Only the left inset does real work (it tracks --pk-split);
     top/bottom are unbounded so nothing is trimmed vertically. */
  clip-path: inset(-9999px 0 -9999px calc(var(--pk-split) * 1%));
  will-change: clip-path, transform;
  z-index: 2;
  isolation: isolate;
  /* transform (the losing-side slide) is driven every frame from JS, in lockstep
     with its own clip-path, so it opts out of the shared panel
     transform-transition - two animators on the same property would fight. */
  transition: opacity 1.4s var(--pk-ease);
}
.pk__left {
  background: var(--pk-white);
  /* Full-bleed BASE now, with NO clip of its own. The white photography side and
     the drifting collage span the entire screen; the development side (.pk__right)
     is a dark overlay painted on top of the right half. Two wins from this:
       1. The seam is the dark overlay's edge, not a white clipped edge, so the
          white shimmer that swept along the seam with the cursor is gone.
       2. With no clip here at all, the collage can never be trimmed - it drifts
          full-width (covered on the right by the dark overlay) and, on the
          photo-mode morph, falls as far as it likes with nothing to slice it. */
  will-change: transform;
  z-index: 1;
  isolation: isolate;
  /* transform (the losing-side slide) is driven every frame from JS; it opts out
     of the base panel transform-transition so JS is the only animator. */
  transition: opacity 1.4s var(--pk-ease);
}

/* Motif holders */
.pk__photos,
.pk__wave { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Own stacking context so the collage photos' z-index stays below the title. */
.pk__photos { z-index: 1; isolation: isolate; }
.pk__wave { display: block; }

/* Soft darkening over the wave so the title stays dominant. */
.pk__right::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 70% 40%, rgba(30, 29, 26, 0) 0%, rgba(30, 29, 26, 0.45) 100%);
  pointer-events: none;
}

/* Main hero image that slides in from above when Photography is picked, and
   back out when returning to the chooser. This base duration (2.2s) is the
   RETURN pace only - it matches MORPH_MS in floating-photos.js, which still
   governs morphUp (the collage flying back to its chooser positions), so the
   two move together on the way back. The forward ENTRANCE gets its own,
   quicker override below (.pk.mode-photo .pk__hero) - the collage's entrance
   fall (morphDown) is fast and per-photo now, not a single MORPH_MS beat, so
   there is no one shared pace to lock the hero to on the way in. */
.pk__hero {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 3;
  transform: translateY(-101%);
  transition: transform 2.2s var(--pk-ease-exit);
  pointer-events: none;
}
.pk__hero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- Section titles ---------- */
.pk__label {
  position: absolute;
  top: 54%;
  transform: translate(-50%, -50%);
  font-family: "Big Shoulders Display", "Antonio", sans-serif;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 0.92;
  font-size: clamp(30px, 4.4vw, 74px); /* mobile gets a 15% larger override below - see the mobile media block */
  text-align: center;
  z-index: 6;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity 1.2s var(--pk-ease);
}
.pk__label--photo { color: var(--pk-ink); }
/* Pure white, not the previous off-white (#d9d6cf) - that soft grey blended
   into the bright, busy ASCII dot pattern behind it and made the solid text
   (there is no actual opacity/blend-mode on this label) read as translucent.
   Full white gives it a fully solid, opaque edge against that background. */
.pk__label--dev   { color: #ffffff; }

/* ---------- Non-focused side dim ----------
   A slight dark tint over the ENTIRE side you are not hovering (photos/wave
   and its label alike, since it sits above both at z-index 7). Neutral (no
   hover) leaves both sides fully bright. Opacity is set directly from JS
   (picker.jsx), only at the moment the hovered side actually changes - not
   every frame - so this stays a plain, cheap two-state fade. */
.pk__dim {
  position: absolute;
  inset: 0;
  z-index: 7;
  background: rgba(0, 0, 0, 0.3); /* the one number to tune for "slight" */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

/* ---------- Isak Persson brand (two-tone across the seam) ----------
   Two full-stage layers hold the same text at the same spot. The dark
   layer is clipped to the photo side, the light layer to the dev side,
   so the word is dark over white and white over the wave, splitting
   exactly at the seam. No blend modes. */
.pk__brandlayer { position: absolute; inset: 0; z-index: 8; pointer-events: none; }
.pk__brandlayer--dark  { clip-path: inset(0 calc((100 - var(--pk-split)) * 1%) 0 0); }
.pk__brandlayer--light { clip-path: inset(0 0 0 calc(var(--pk-split) * 1%)); }
.pk__brandpos {
  position: absolute;
  top: 22%; left: 50%;
  transform: translate(-50%, -50%);
}
.pk__brandtext {
  font-family: "Big Shoulders Display", "Antonio", sans-serif;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: clamp(26px, 3vw, 46px);
  white-space: nowrap;
  /* No transition here - its position (including sliding fully off-screen
     on exit, no fade) is driven every frame from JS on the parent
     .pk__brandpos, at the same rate as the labels, so there is exactly one
     thing moving it. */
}
.pk__brandlayer--dark  .pk__brandtext { color: var(--pk-ink); }
.pk__brandlayer--light .pk__brandtext { color: #ffffff; }

/* ---------- Hint (mobile only) ----------
   Hidden by default (desktop): pure white text can't adapt to which panel
   it's sitting over, and desktop's bottom-centre position straddles both the
   white photo side and the dark dev side depending on the live split - it
   would vanish against the white side. Mobile's bottom edge is always inside
   the dark development band during 'choose' (only its SIZE changes with the
   split, never its colour), so pure white reads reliably there - see the
   mobile media block below for the actual visible styling. */
.pk__hint {
  display: none;
  position: absolute;
  bottom: 30px; left: 50%;
  width: max-content;
  max-width: 80vw;
  text-align: center;
  transform: translate(-50%, 0);
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: #ffffff;
  z-index: 8;
  pointer-events: none;
  transition: opacity 1.0s var(--pk-ease), transform 1.0s var(--pk-ease);
}
/* Picking a side: always slides DOWN as it fades (regardless of which side -
   unlike the wordmark, this one direction is constant). Returning to the
   chooser is not a separate rule - it's the SAME transform/opacity
   transitioning back to the base state above, which naturally plays as a
   slide back UP. */
.pk.mode-photo .pk__hint,
.pk.mode-dev .pk__hint {
  opacity: 0;
  transform: translate(-50%, 40px);
}

/* ---------- Back to chooser (only during dev hold) ----------
   Placed on the RIGHT, matching the dark/dev panel it belongs to. Keeping
   it there (rather than at the left) means the cursor stays over the dev
   half after clicking, so the hover-driven side-detection does not
   mistake the click for a move onto the photography side and pause the
   video underneath the user's own cursor. */
.pk__devback {
  /* Fixed to the viewport (matching pk-siteback), not absolute-within-.pk -
     anchoring directly to the viewport rather than indirectly through a
     container's own position is the more robust choice. Keeps its own
     outlined/transparent design (unlike the photography page's dark-pill
     back button) - only recoloured to white so it reads as one family with
     the white custom cursor (.pk__cursor--light) used over this dark side. */
  position: fixed;
  top: 26px; right: 28px;
  z-index: 9;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.4);
  padding: 10px 16px;
  cursor: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1.0s var(--pk-ease), border-color 0.6s var(--pk-ease), color 0.6s var(--pk-ease);
}
.pk__devback:hover { border-color: rgba(255, 255, 255, 0.9); color: #ffffff; }

/* Back to chooser button on the (light) photography page. Positioned once
   near the top of the page (not the viewport), so it scrolls away with the
   rest of the gallery content instead of staying pinned. Same dark-pill
   treatment as the bottom-chrome controls (.audio / .view-toggle) - a solid
   dark fill with no border, rather than an outline - so all three floating
   controls on the photography page read as one consistent design language. */
.pk-siteback {
  position: absolute;
  top: 24px; left: 26px;
  z-index: 9500;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #ffffff;
  background: #0e0e10;
  border: 0;
  border-radius: 999px;
  padding: 10px 18px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  cursor: none;
  transition: opacity 0.6s var(--pk-ease);
  opacity: 0.9;
  /* This button is mounted via useDelayedUnmount (app.jsx), not a plain
     `mode === 'photo'` conditional, specifically so it can play a real CSS
     animation both on the way IN (mounts fresh - see below) and on the way
     OUT (kept mounted a beat longer with .is-leaving added, see below) rather
     than a plain conditional unmounting it instantly the moment you click.
     "both" holds the start state before the animation begins (avoiding a
     one-frame flash at the resting opacity/position) and keeps the end state
     after, which the :hover opacity transition then layers on top of. */
  animation: pk-siteback-in 0.7s var(--pk-ease) both;
}
.pk-siteback:hover { opacity: 1; }

/* Reverses the entrance - slides back up and fades out - while
   useDelayedUnmount keeps the button mounted after a click (mode has already
   left 'photo'). Duration must stay under the 500ms delay passed to
   useDelayedUnmount, or the button would vanish mid-animation. Uses
   --pk-ease-exit (the same curve the rest of the chooser uses for exits,
   e.g. .pk__hero's return trip), not --pk-ease, so entering and leaving feel
   like a matched pair rather than the same motion played backwards. */
.pk-siteback.is-leaving {
  pointer-events: none;
  animation: pk-siteback-out 0.45s var(--pk-ease-exit) both;
}

@keyframes pk-siteback-out {
  from { opacity: 0.9; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-28px); }
}

@keyframes pk-siteback-in {
  from { opacity: 0; transform: translateY(-28px); }
  to   { opacity: 0.9; transform: translateY(0); }
}

/* ---------- Custom cursor ----------
   Same two-layer technique as the Isak Persson brand: two full-viewport
   layers, each clipped to one side of the live split. Whichever layer's
   clip region actually contains the cursor's screen position is the one
   that shows through - so the ring reads dark over the white panel and
   white over the wave, with no mix-blend-mode and no inversion trick. */
.pk__cursorlayer { position: fixed; inset: 0; z-index: 9999; pointer-events: none; }
.pk__cursorlayer--dark  { clip-path: inset(0 calc((100 - var(--pk-split)) * 1%) 0 0); }
.pk__cursorlayer--light { clip-path: inset(0 0 0 calc(var(--pk-split) * 1%)); }

.pk__cursor {
  position: fixed;
  top: 0; left: 0;
  width: 34px; height: 34px;
  border: 1px solid rgba(150, 148, 140, 0.9);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.pk__cursor::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 3px; height: 3px;
  background: rgba(200, 198, 191, 1);
  border-radius: 50%;
  transform: translate(-50%, -50%);
}
.pk__cursorlayer--dark .pk__cursor { border-color: rgba(22, 21, 15, 0.85); }
.pk__cursorlayer--dark .pk__cursor::after { background: rgba(22, 21, 15, 0.9); }
.pk__cursor--light { border-color: rgba(255, 255, 255, 0.9); }
.pk__cursor--light::after { background: #ffffff; }

/* ---------- Mode choreography ----------
   The losing side always slides fully off-screen (never a fade). The
   surviving label glides to centre - its position is set every frame from
   JS (its own, faster-easing value - see picker.jsx), not by a CSS
   transition, so there is exactly one thing driving its motion and no
   fight between two competing animators. Isak Persson slides up. */

/* Photography chosen: pk__left has no override here at all. Its clip-path
   is already driven every frame by the live --pk-split value (see
   picker.jsx), so it grows to full-bleed smoothly as the split eases to
   100 - the same mechanism that already works cleanly for the dev side.
   pk__right's slide-away is ALSO driven from that same split value (in
   picker.jsx, not here), so the two can never desync or leave a gap of
   plain background between the retreating characters and the advancing
   white edge; it never rises above pk__left in stacking order either, so
   it can never "pop" on top before the slide starts. */
/* The hero photo enters with a brief hold, then a QUICKER slide than the base/
   return transition. The collage's per-photo morphDown (floating-photos.js)
   sends each photo flying well past the first screen while fading, so the
   collage clears the screen fast - a slower hold+slide here would leave a
   stretch of plain white visible after the collage is gone and before the
   hero has slid down far enough to cover it. Mobile gets its own, slower
   override below (its collage fall is slowed to match) - this base timing
   is desktop's. */
.pk.mode-photo .pk__hero { transform: translateY(0); transition: transform 1.4s var(--pk-ease-exit) 0.08s; }

/* Development chosen: the white side is wiped away purely by its clip-path
   receding (driven every frame from --pk-split in picker.jsx), NOT by a
   translate. This makes it behave identically to the photography-chosen
   direction: in both, the losing content stays anchored and the single clip
   seam sweeps across to mask/reveal it, so the two exits can never look
   inconsistent (one sliding fast, the other crawling). The photo label,
   being a child of pk__left, gets clipped in place as the seam passes it -
   the "stays put, gets masked by the edge" look, matching the collage.

   Old translate-based exit kept here (commented) for easy rollback:
   .pk.mode-dev .pk__left { transform: translateX(-100%); transition: transform 2.4s var(--pk-ease-exit); }
*/
.pk.mode-dev .pk__devback { opacity: 1; pointer-events: auto; }

/* The carried-over "Photography portfolio" hero title is longer than the
   old "Isak / Persson", so it needs a smaller size to fit on one line. */
.hero__title--portfolio .hero__line { font-size: clamp(44px, 17vw, 232px); letter-spacing: -0.04em; }

/* =============================================================
   MOBILE / TOUCH  -  top (photography) over bottom (development)
   -------------------------------------------------------------
   Same single-number engine (--pk-split, driven by picker.jsx),
   only the AXIS flips: the seam is now horizontal, the losing side
   slides up/down instead of left/right, and titles sit centred in
   their band (JS drives their `top`, not `left`). There is no hover
   on touch, so both motifs animate at all times and the split drifts
   on its own (idle) with device-tilt layered on when available. The
   custom ring cursor is dropped (it needs a mouse to move), and the
   "Isak Persson" wordmark drops its two-tone seam split for a single,
   always-legible placement in the white top band.
   ============================================================= */
@media (max-width: 800px), (pointer: coarse) {
  /* The dark development panel is now the BOTTOM overlay: clip off the top
     portion (its top inset tracks --pk-split) so only the area below the seam
     shows. split -> 100 clips it fully away (photography fills); split -> 0
     reveals all of it (development fills). Mirror of the desktop left inset. */
  .pk__right {
    clip-path: inset(calc(var(--pk-split) * 1%) 0 0 0);
  }

  /* Titles centred horizontally; JS sets their vertical position each frame.
     ~52% larger than desktop (15%, then +10%, then +20% on top) - mobile has
     more empty vertical room per band. */
  .pk__label { left: 50%; font-size: clamp(45.6px, 6.68vw, 112.2px); }

  /* Hint: shown here only (see base rule above for why) - the bottom edge is
     always inside the dark development band on mobile, so pure white is
     always legible regardless of the live split. */
  .pk__hint { display: block; }

  /* Wordmark sits AT the seam - "in the middle of the two sides" - restoring
     the same two-tone split technique the desktop version uses (dark layer
     over the white top band, light layer over the dark bottom band), just
     mirrored onto the vertical axis: clip top/bottom instead of left/right,
     tracking --pk-split the same way .pk__right does above. 10% larger than
     desktop, same reasoning as the labels above. */
  .pk__brandlayer--dark  { clip-path: inset(0 0 calc((100 - var(--pk-split)) * 1%) 0); }
  .pk__brandlayer--light { clip-path: inset(calc(var(--pk-split) * 1%) 0 0 0); }
  .pk__brandpos { top: 50%; }
  .pk__brandtext { font-size: clamp(31.5px, 3.63vw, 55.7px); }

  /* The hero's quick desktop entrance (picker.css base rule above) was tuned
     to close a white-gap at 1.4s; mobile's collage fall is deliberately
     slower (see floating-photos.js fallDurationMul), so its hero entrance
     is slowed to match and stay in sync. */
  .pk.mode-photo .pk__hero { transition: transform 2.0s var(--pk-ease-exit) 0.08s; }

  /* No mouse means the ring cursor can never move - hide both cursor layers. */
  .pk__cursorlayer { display: none; }

  /* Back-to-chooser (development hold): top of the page, centred. */
  .pk__devback {
    top: 16px; left: 50%; right: auto;
    transform: translateX(-50%);
  }

  /* Back-to-chooser (photography page): also top-centre. Its entrance/exit
     animate the transform, so give it mobile keyframes that keep the -50%
     horizontal centring while sliding vertically. */
  .pk-siteback {
    left: 50%; top: 16px;
    animation-name: pk-siteback-in-m;
  }
  .pk-siteback.is-leaving { animation-name: pk-siteback-out-m; }
  @keyframes pk-siteback-in-m {
    from { opacity: 0; transform: translate(-50%, -28px); }
    to   { opacity: 0.9; transform: translate(-50%, 0); }
  }
  @keyframes pk-siteback-out-m {
    from { opacity: 0.9; transform: translate(-50%, 0); }
    to   { opacity: 0; transform: translate(-50%, -28px); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .pk__panel, .pk__label, .pk__brandtext, .pk__hero, .pk__dim, .pk__hint, .pk.is-fadeout { transition-duration: 0.01ms; }
  /* .pk-siteback.is-leaving must be listed explicitly, not just .pk-siteback:
     its two-class selector outranks a one-class selector regardless of source
     order, so without this the exit animation's own duration would win over
     this override. */
  .pk-siteback, .pk-siteback.is-leaving { animation-duration: 0.01ms; }
}
