:root {
  --bg: #000;
  --fg: #fff;
  --card-bg: rgba(255, 255, 255, 0.1);
  --card-size-min: 0.82;
  --card-size-max: 1;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: 'Inter', 'Noto Sans JP', sans-serif;
  font-weight: 200;
  -webkit-font-smoothing: antialiased;
}

/* Scroll snap on the works page: each card locks to the center of the
   body area (viewport minus the 65px sticky header) — the same center the
   contents padding, the ring anchor and the script.js focus math all use.
   Without the scroll-padding the snapport would be the full viewport and
   every card would rest 32.5px above that center. */
html:has(body.works-page) {
  scroll-snap-type: y mandatory;
  scroll-padding-top: 65px;
  scroll-behavior: smooth;
}

a { color: inherit; text-decoration: underline; }

/* ---------------- Home ---------------- */
.home-page { height: 100vh; height: 100dvh; overflow: hidden; }

.home {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 140px;
  /* Animate the desktop <-> mobile value jumps at the 600px breakpoint. */
  transition: gap 0.5s ease;
}

.home__logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: translateY(-00px);
}

/* Generative logo canvas: data area is 334x279 with a 34px glow pad on all
   sides (402x347 total). Negative margins cancel the pad so the layout box
   matches the old 334px-wide logo image and the glow just overflows. */
.home__logo canvas {
  width: 402px;
  aspect-ratio: 402 / 347;
  display: block;
  margin: -34px;
  /* generative-logo.js watches the element with a ResizeObserver, so the
     ring re-renders sharply on every frame of this transition. */
  transition: width 0.5s ease;
}

/* Wordmark matching the original logo.png raster: Montserrat Regular 24px,
   letter-spacing 0.25em (cap top sat 12px below the ring data area). */
.home__logo-text {
  font-family: 'Montserrat', 'Inter', sans-serif;
  font-weight: 400;
  font-size: 24px;
  letter-spacing: 0.25em;
  line-height: 1;
  /* The canvas paints opaque black over its full box, whose 34px glow pad
     overlaps this whole wordmark. iOS Safari composites the accelerated
     canvas layer above later in-flow content, hiding the text; an own
     stacking context guarantees the wordmark stays on top. */
  position: relative;
  z-index: 1;
  /* -0.25em cancels the trailing letter-space so the glyphs stay centered. */
  margin: 8px -0.25em 0 0;
  white-space: nowrap;
  transition: font-size 0.5s ease;
}


.home__nav {
  display: flex;
  gap: 140px;
  transition: gap 0.5s ease;
}

.home__nav a {
  font-size: 20px;
  letter-spacing: 0.08em;
  font-weight: 200;
  min-width: 70px;
  text-align: center;
}

/* Home on narrow screens: the fixed 402px canvas, 24px wordmark and 140px
   nav gap all overflow a phone viewport, so they go fluid below 600px.
   generative-logo.js re-renders from the canvas CSS width on resize. */
@media (max-width: 600px) {
  .home { gap: clamp(60px, 12vh, 140px); }

  /* 90% of the desktop formula (ring and wordmark run 10% smaller in
     smartphone mode). Layout box = width - 68px (the glow pad margins),
     so this keeps generous side margins for the ring. */
  .home__logo canvas { width: min(362px, calc((100vw + 20px) * 0.9)); }

  .home__logo-text { font-size: clamp(13.5px, 5.4vw, 21.6px); }

  .home__nav { gap: clamp(28px, 12vw, 140px); }
}

@media (prefers-reduced-motion: reduce) {
  .home,
  .home__logo canvas,
  .home__logo-text,
  .home__nav {
    transition: none;
  }
}

/* ---------------- Header (works page) ---------------- */
.header {
  position: sticky;
  top: 0;
  /* Above the drawer (30) and its backdrop (25) so the hamburger button
     stays clickable while the drawer is open. */
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 35px;
  padding: 8px 9px;
  background: var(--bg);
}

.header__brand img {
  width: 59px;
  height: 49px;
  object-fit: cover;
  display: block;
}

.header__nav {
  display: flex;
  gap: 35px;
}

.header__nav a {
  font-size: 20px;
  letter-spacing: 0.08em;
  font-weight: 200;
}

/* ---------------- Header mobile (hamburger + drawer) ---------------- */
/* body.header-mobile is toggled by the header module in script.js at the
   same MOBILE_BREAKPOINT (800px) as the works/profile smartphone modes.
   On the switch the nav's characters fly apart and re-assemble into the
   hamburger's three bars (WAAPI animation in script.js); the nav is only
   display:none'd (.is-collapsed) after that morph has finished. */
.header__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 6px;
  width: 44px;
  height: 44px;
  margin-left: auto;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}

body.header-mobile .header__toggle { display: flex; }

.header__bar {
  width: 26px;
  height: 2px;
  border-radius: 1px;
  background: var(--fg);
  transition: transform 0.3s ease, opacity 0.3s ease;
}

/* Open state: the outer bars cross into an X (bar centers sit 8px apart). */
.header__toggle.is-open .header__bar:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}
.header__toggle.is-open .header__bar:nth-child(2) { opacity: 0; }
.header__toggle.is-open .header__bar:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

/* Each nav character is wrapped in a span so it can fly individually. */
.header__char { display: inline-block; }

.header.is-morphing .header__nav a {
  text-decoration: none;
  pointer-events: none;
}

body.header-mobile .header__nav.is-collapsed { display: none; }

/* Slide-in drawer holding the nav links (element built by script.js). */
.header-drawer {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 30;
  width: min(75vw, 300px);
  height: 100vh;
  height: 100dvh;
  padding: 95px 30px 30px;
  background: rgba(8, 8, 8, 0.96);
  border-left: 1px solid rgba(255, 255, 255, 0.15);
  transform: translateX(100%);
  transition: transform 0.35s ease;
}

.header-drawer.is-open { transform: translateX(0); }

.header-drawer__nav {
  display: flex;
  flex-direction: column;
  gap: 30px;
}

.header-drawer__nav a {
  font-size: 20px;
  letter-spacing: 0.08em;
  font-weight: 200;
}

.header-drawer__backdrop {
  position: fixed;
  inset: 0;
  z-index: 25;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;
}

.header-drawer__backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  .header__bar,
  .header-drawer,
  .header-drawer__backdrop {
    transition: none;
  }
}

/* ---------------- Works layout ---------------- */
.works {
  display: flex;
  align-items: stretch;
  gap: 30px;
  min-height: calc(100vh - 65px);
}

.works__side {
  position: sticky;
  top: 65px;
  align-self: flex-start;
  /* 176px arc area + 12px clearance so the active dot isn't clipped. */
  width: 188px;
  height: calc(100vh - 65px);
  flex-shrink: 0;
  overflow: visible;
  /* Keep in sync with MORPH_MS in script.js (circle <-> line morph). */
  transition: width 0.5s ease;
}

.works__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

.works__ring .ring-path {
  fill: none;
  stroke: rgba(255, 255, 255, 0.9);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.works__ring .ring-dot {
  fill: #d9d9d9;
  transition: r 0.2s ease, fill 0.2s ease;
}

.works__ring .ring-dot.is-active {
  fill: #ffffff;
}

.works__label {
  position: absolute;
  left: 15.5px;
  top: 50%;
  transform: translate(-50%, -50%) rotate(90deg);
  transform-origin: center;
  font-weight: 900;
  font-size: 24px;
  margin: 0;
  white-space: nowrap;
}

.works__years {
  position: absolute;
  left: 63px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  font-weight: 900;
  font-size: 20px;
  width: 57px;
  text-align: center;
  transition: left 0.5s ease;
}

.works__contents {
  flex: 1 1 auto;
  min-width: 0;
  /* The focused (--t=1) card's rendered height: the .work-card height calc
     with its 550px min-height floor, times the line-mode window-shrink zoom
     (overridden below; --card-scale is 1 outside line mode anyway). */
  --focused-card-h: max(calc(var(--vh-px, 100vh) - 400px), 550px);
  /* Exactly the top/bottom room that lets the FIRST and LAST cards center
     in the body area (viewport minus the 65px header): any less and the
     scroll range ends before the snap point, leaving those cards stuck
     off-center. */
  padding: max(0px, calc((var(--vh-px, 100vh) - 65px - var(--focused-card-h)) / 2)) 15px;
  display: flex;
  flex-direction: column;
  gap: 30px;
  align-items: center;
}

/* In line mode the whole card is zoomed by --card-scale, so the rendered
   focused height shrinks with it and the end cards need more padding to
   reach the center. */
body.works-line .works__contents {
  --focused-card-h: calc(
    max(calc(var(--vh-px, 100vh) - 400px), 550px) * var(--card-scale, 1)
  );
}

/* ---------------- Work card with scroll-driven scale ---------------- */
.work-card {
  --t: 0; /* 0..1 focus progress, set by JS */
  /* --vh-px is the window height in px, set by script.js. Using it instead
     of 100vh keeps the pics-width math consistent when the card is zoomed
     in line mode (viewport units would escape the zoom). */
  --card-h-main: calc(var(--vh-px, 100vh) - 400px);
  /* Actual rendered height (the height calc below floored by min-height),
     used to derive the 16:9 pics-column width. The 550px floor applies to
     the FOCUSED card; the focus factor scales it too, so the previous/next
     cards stay at 78% of the main card even in short windows. */
  --card-h: calc(max(var(--card-h-main), 550px) * (0.78 + 0.22 * var(--t)));
  background: var(--card-bg);
  /* Lock the aspect ratio to 205:108; width is derived from height. */
  aspect-ratio: 205 / 108;
  /* main (t=1): fills viewport minus 200px top/bottom.
     previous/next (t=0): ~78% of main height. */
  height: calc(var(--card-h-main) * (0.78 + 0.22 * var(--t)));
  max-height: var(--card-h-main);
  min-height: calc(550px * (0.78 + 0.22 * var(--t)));
  /* The ratio-derived width grows with the viewport height; never let it
     spill out of the contents column over the side design. */
  max-width: 100%;
  padding: calc(13px + 3px * var(--t)) calc(28px + 6px * var(--t));
  display: flex;
  flex-direction: column;
  gap: 20px;
  overflow: hidden;
  transition: height 0.25s ease, padding 0.25s ease, opacity 0.25s ease;
  opacity: calc(0.55 + 0.45 * var(--t));
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

.work-card__title {
  margin: 0;
  font-size: calc(40px + 8px * var(--t));
  letter-spacing: 0.08em;
  font-weight: 200;
  text-decoration: underline;
  line-height: 1.2;
  transition: font-size 0.25s ease;
}

.work-card__detail {
  display: flex;
  gap: 15px;
  /* Center the text block and the pics column vertically in the card. */
  align-items: center;
  flex: 1 1 auto;
  min-height: 0;
}

.work-card__main {
  flex: 1 1 auto;
  /* Never narrower than the width where the Accel Code intro wraps as
     「ホワイトハッカー、依頼だ！世界的ハッカー集団 (549.4px at the focused
     24px font; +1 char needs 573px). */
  min-width: 552px;
  /* Fill the detail height (overriding its centering) so the link row can
     pin to the card bottom while the texts stay centered above it. */
  align-self: stretch;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.work-card__texts {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  font-size: calc(20px + 4px * var(--t));
  line-height: 1.4;
  transition: font-size 0.25s ease;
}

.work-card__texts p { margin: 0; }

/* Store links pinned to the bottom edge of the text column. */
.work-card__links {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  padding: 14px 0 4px;
}

.work-card__link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 9px 18px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: inherit;
  text-decoration: none;
  font-size: calc(15px + 1px * var(--t));
  letter-spacing: 0.06em;
  white-space: nowrap;
  transition: background 0.2s ease, font-size 0.25s ease;
}

.work-card__link:hover { background: rgba(255, 255, 255, 0.12); }

.work-card__link img {
  width: 1.2em;
  height: 1.2em;
  display: block;
}

/* Not released yet: keep the button visible but dimmed and inert. */
.work-card__link--disabled {
  opacity: 0.35;
  pointer-events: none;
}

.work-card__pics {
  /* Fill the card's full content height (overrides the detail centering)
     while each pic keeps 16:9, so the column width is derived from the
     height the two pics get:
     detail-h = card height - v-padding - title (line-height 1.2) - gap,
     pic width = (detail-h - 15px gap) / 2 * 16/9. */
  --detail-h: calc(
    var(--card-h) - (26px + 6px * var(--t)) - (48px + 9.6px * var(--t)) - 20px
  );
  /* This column never shrinks: when the texts are at their min-width and
     the window keeps narrowing, script.js switches the layout mode
     (circle -> line -> mobile) instead. */
  flex-shrink: 0;
  align-self: stretch;
  width: calc((var(--detail-h) - 15px) * 8 / 9);
  display: flex;
  flex-direction: column;
  gap: 15px;
  transition: width 0.25s ease;
}

.work-card__pic {
  /* Height from the 16:9 ratio; at the column's full height-derived width
     the two pics add up to exactly the detail height. */
  flex: 0 1 auto;
  min-height: 0;
  aspect-ratio: 16 / 9;
  background: #d9d9d9;
  overflow: hidden;
}

.work-card__pic img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (prefers-reduced-motion: reduce) {
  .works__side,
  .works__years {
    transition: none;
  }
}

/* ---------------- Works responsive ---------------- */
/* Stage 1: "narrow" circle mode (body.works-narrow, toggled by script.js
   when the full aspect-ratio card no longer fits beside the circle): the
   cards give up the fixed 205:108 aspect ratio and shrink horizontally
   with the window, the texts' 552px min-width being the floor. While the
   window narrows towards the min-width card, --card-squeeze eases 0 -> 1
   and scales the previous/next cards (via their focus factor) from full
   size down to the line-mode 78%, so the later switch to line mode is
   visually seamless. */
body.works-narrow .work-card {
  aspect-ratio: auto;
  /* The width factor starts from the card's natural (default) size ratio
     (--nb-start .. 1 by focus, set by script.js from the aspect-ratio
     heights) and eases to the line-mode 78% .. 100% as --card-squeeze goes
     0 -> 1, so entering this stage does not change any card's size. The
     zoom starts from 1 (fonts/pics untouched at entry) and eases to the
     same line-mode factor. */
  width: calc(
    (
      (var(--nb-start, 1) + (1 - var(--nb-start, 1)) * var(--t))
        * (1 - var(--card-squeeze, 0))
      + (0.78 + 0.22 * var(--t)) * var(--card-squeeze, 0)
    ) * 100%
  );
  zoom: calc(1 - 0.22 * var(--card-squeeze, 0) * (1 - var(--t)));
}

/* Stage 2: line mode. The card's minimum content width (56-68 padding +
   552 texts + 15 gap + height-derived pics column) depends on the window
   HEIGHT, so the switch is computed by script.js, not a media query: when
   that width no longer fits beside the 188px circle column it toggles
   body.works-line and the side circle collapses into a straight vertical
   line. The card keeps filling the available width; once even the
   min-width card no longer fits, script.js scales the whole card down via
   --card-scale (1 .. 0.8) and past 20% shrink switches to mobile mode. */
body.works-line .works__side { width: 130px; }
/* Keep the years clear of the line and its dots (line sits at ~114px). */
body.works-line .works__years { left: 40px; }

body.works-line .work-card {
  /* Also covers line mode above the 1300px media query (tall windows). */
  aspect-ratio: auto;
  /* In line mode the card box must track --t instantly (opacity may keep
     easing): the box size feeds the snap geometry through the zoom, and
     Chrome does not re-snap when a size transition finishes after the
     snap scroll has settled — with the 0.25s height/padding easing the
     focused card rests tens of px above center with --t stuck below 1.
     (Circle mode is the opposite: there the late transition is exactly
     what triggers Chrome's re-snap, so the base rule keeps it.) */
  transition: opacity 0.25s ease;
  /* Each card is zoomed by its focus factor (0.78 .. 1) on top of the
     window-shrink scale, keeping the previous/next cards smaller than the
     focused one. The percentage width is pre-multiplied by the same factor
     because a plain 100% would visually fill the row regardless of zoom
     (percentages resolve inside the zoomed coordinate space). */
  width: calc((0.78 + 0.22 * var(--t)) * 100%);
  zoom: calc(var(--card-scale, 1) * (0.78 + 0.22 * var(--t)));
}

/* Stage 3: smartphone layout, entered when the line layout can no longer
   clear the card even at 80% scale (script.js toggles body.works-mobile).
   The side column becomes a plain heading row, cards size to their
   content and stack text above the pictures. These rules come after the
   works-line ones so they win when both classes are set.
   No scroll snapping in mobile: cards can be taller than the phone
   viewport, and center-snapping them makes their top/bottom unreachable —
   the page just scrolls freely. */
html:has(body.works-mobile) { scroll-snap-type: none; }

body.works-mobile .works {
  flex-direction: column;
  gap: 0;
}

body.works-mobile .works__side {
  position: static;
  width: auto;
  height: auto;
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 18px 16px 0;
}

body.works-mobile .works__ring { display: none; }

body.works-mobile .works__label {
  position: static;
  transform: none;
}

body.works-mobile .works__years {
  position: static;
  transform: none;
  flex-direction: row;
  width: auto;
  gap: 4px;
  font-size: 16px;
}

body.works-mobile .works__contents {
  /* No snap in mobile, so no need for the tall bottom pad that let the
     last card center-snap. */
  padding: 30px 16px 60px;
  gap: 24px;
}

body.works-mobile .work-card {
  width: 100%;
  height: auto;
  max-height: none;
  min-height: 0;
  padding: 16px;
  /* Mobile cards are never zoomed (overrides the narrow/line formulas). */
  zoom: 1;
  /* No scroll-driven focus effects in mobile: every card stays fully
     visible and nothing snaps or pulses while scrolling. */
  opacity: 1;
  scroll-snap-align: none;
  scroll-snap-stop: normal;
}

body.works-mobile .work-card__title { font-size: 28px; }

body.works-mobile .work-card__detail {
  flex-direction: column;
  /* Column direction: centering would shrink-wrap and center the text
     block; stretch keeps text and pics at full card width. */
  align-items: stretch;
  gap: 12px;
}

body.works-mobile .work-card__main {
  flex: none;
  min-width: 0;
  align-self: auto;
}

body.works-mobile .work-card__texts {
  flex: none;
  width: auto;
  min-width: 0;
  overflow: visible;
  font-size: 16px;
}

body.works-mobile .work-card__links {
  padding: 10px 0 0;
  gap: 10px;
}

body.works-mobile .work-card__link {
  font-size: 14px;
  padding: 7px 14px;
}

body.works-mobile .work-card__pics {
  width: 100%;
  flex-direction: row;
  gap: 10px;
  /* Mobile order: title, pics, then the text block. */
  order: -1;
}

body.works-mobile .work-card__pic {
  flex: 1 1 0;
  aspect-ratio: 16 / 9;
}

/* ---------------- Profile (horizontal swipe pager) ---------------- */
.profile-page { height: 100vh; overflow: hidden; }

.profile {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 65px);
  /* Bottom padding reserves room for the fixed pager bar + its label.
     Keep in sync with CHROME_V in script.js. */
  padding: 50px 0 90px;
}

.profile__viewport {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* Keep horizontal overscroll from triggering browser back/forward. */
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.profile__viewport::-webkit-scrollbar { display: none; }

.profile__track {
  display: flex;
  align-items: center;
  gap: 30px;
  height: 100%;
  width: max-content;
  /* Side padding so the first/last cards can sit exactly centered at the
     scroll extremes. script.js keeps --track-pad = (viewport width -
     rendered card width) / 2. */
  padding: 0 var(--track-pad, 118px);
}

/* Card sizing (computed by applyLayout in script.js, which sets
   --card-w / --card-h): the focused card always keeps its default
   proportions — works-formula height (window height minus 400px, floored
   at 550px) and the same 205:108 ratio width (the Figma 1044x550 profile
   card is that ratio too). Whenever the window can't fit that card,
   --profile-scale zooms the WHOLE card down uniformly; the inner layout
   (text column width included) never reflows. Unfocused neighbours scale
   to 78% like the works cards. */
.profile-card {
  --t: 0; /* 0..1 focus progress, set by JS */
  flex-shrink: 0;
  width: var(--card-w, calc(550px * 205 / 108));
  height: var(--card-h, 550px);
  background: var(--card-bg);
  padding: 30px;
  display: flex;
  gap: 30px;
  overflow: hidden;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  zoom: var(--profile-scale, 1);
  /* Same focus factor as the works cards: previous/next at 78%. */
  transform: scale(calc(0.78 + 0.22 * var(--t)));
  /* translate is the FLIP animation on mode switches (script.js moves
     each card from its old screen position to the new one); its 0.5s
     matches the pager's home-to-home animation. */
  transition: transform 0.25s ease, translate 0.5s ease;
}

.profile-card__photo {
  flex-shrink: 0;
  height: 100%;
  aspect-ratio: 300 / 400;
  background: #f6f6f6 center / cover no-repeat;
}

.profile-card__body {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 30px;
}

.profile-card__name {
  margin: 0;
  font-size: 48px;
  letter-spacing: 0.08em;
  font-weight: 200;
  text-decoration: underline;
  line-height: 1.2;
}

.profile-card__texts {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  font-size: 24px;
  line-height: 1.4;
}

.profile-card__texts p { margin: 0; }

/* Work-style card inside the profile pager (same look as .work-card). */
.profile-card--work {
  flex-direction: column;
  gap: 20px;
  padding: 16px 34px;
}

.profile-card__title {
  margin: 0;
  font-size: 40px;
  letter-spacing: 0.08em;
  font-weight: 200;
  text-decoration: underline;
  line-height: 1.2;
}

.profile-card__detail {
  display: flex;
  gap: 15px;
  align-items: stretch;
  flex: 1 1 auto;
  min-height: 0;
}

.profile-card__detail .profile-card__texts { font-size: 20px; }

.profile-card__pics {
  flex-shrink: 0;
  width: 321px;
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.profile-card__pic {
  flex: 1 1 0;
  min-height: 0;
  background: #d9d9d9;
}

/* The pager is fixed so it can animate between its two homes: centered
   near the bottom edge (horizontal pager) and rotated 90deg against the
   left edge (vertical pager). Only left / top / transform change between
   the modes, so the move is a single smooth slide + spin. */
.profile__pager {
  position: fixed;
  z-index: 20;
  left: 50%;
  top: calc(100% - 55px);
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  transition: left 0.5s ease, top 0.5s ease, transform 0.5s ease;
}

/* "Profile" label, styled like .works__label. Absolutely positioned just
   below the dash row, so the pager's 90deg rotation carries it along:
   below the bar in horizontal mode, left of the bar (vertical text) in
   vertical mode — one element, one shared animation. */
.profile__label {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin: 8px 0 0;
  font-weight: 900;
  font-size: 24px;
  line-height: 1.2;
  white-space: nowrap;
}

.profile__dash {
  height: 2px;
  width: 30px;
  padding: 0;
  border: none;
  border-radius: 3px;
  background: #fff;
  cursor: pointer;
  transition: width 0.25s ease;
}

.profile__dash.is-active { width: 150px; }

/* ---------------- Profile vertical mode ---------------- */
/* Entered at the works circle -> line boundary (body.profile-vertical,
   toggled by script.js). The pager scrolls vertically instead of
   horizontally and the dash bar glides from the bottom edge to the left
   edge (rotate(90deg) maps the first card's dash to the top, matching
   the scroll direction). The cards themselves keep the exact horizontal
   card (size formulas included), so the switch never changes the focused
   card's size — script.js only FLIP-animates the positions. On further
   narrowing --profile-scale keeps zooming the card down uniformly. */
body.profile-vertical .profile { padding: 0; }

body.profile-vertical .profile__viewport {
  overflow-x: hidden;
  overflow-y: auto;
  /* Mandatory center snap so the focused card always settles centered. */
  scroll-snap-type: y mandatory;
  overscroll-behavior: contain;
}

body.profile-vertical .profile__track {
  flex-direction: column;
  /* Center short content without making tall content unreachable. */
  justify-content: safe center;
  width: auto;
  height: auto;
  min-height: 100%;
  /* The top/bottom paddings are computed by script.js as (viewport
     height - card height) / 2 so the first/last cards can sit exactly
     centered at the scroll extremes. Cards center horizontally via the
     track's align-items; the zoom keeps them clear of the pager gutter. */
  padding: var(--track-pad-top, 30px) 0 var(--track-pad-bottom, 40px);
}

body.profile-vertical .profile__pager {
  /* Far enough from the edge that the rotated label (screen-left of the
     bar) still fits on screen. */
  left: 48px;
  top: 50%;
  transform: translate(-50%, -50%) rotate(90deg);
}

/* ---------------- Profile mobile mode ---------------- */
/* Entered below the works MOBILE_BREAKPOINT (body.profile-mobile,
   toggled by script.js): the same smartphone layout as the works page.
   The page scrolls normally, the pager collapses into a plain "Profile"
   heading row, and each card sizes to its content: the name on top, the
   photo to the left of the texts below it. These rules come after the
   vertical-mode ones so they win when both classes could apply.
   No scroll snapping in mobile (same reason as works): center-snapping a
   card taller than the phone viewport hides its top/bottom. */
html:has(body.profile-mobile) { scroll-snap-type: none; }

body.profile-page.profile-mobile {
  height: auto;
  overflow: visible;
}

body.profile-mobile .profile {
  height: auto;
  padding: 0;
}

body.profile-mobile .profile__viewport {
  overflow: visible;
  scroll-snap-type: none;
}

body.profile-mobile .profile__track {
  flex-direction: column;
  align-items: stretch;
  width: auto;
  height: auto;
  gap: 24px;
  /* No snap in mobile, so no need for the tall bottom pad that let the
     last card center-snap. */
  padding: 30px 16px 60px;
}

/* Plain heading row above the cards (same as the works mobile header). */
body.profile-mobile .profile__pager {
  position: static;
  transform: none;
  order: -1;
  justify-content: flex-start;
  padding: 18px 16px 0;
  transition: none;
}

body.profile-mobile .profile__label {
  position: static;
  transform: none;
  margin: 0;
}

body.profile-mobile .profile__dash { display: none; }

body.profile-mobile .profile-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    'name name'
    'photo texts';
  align-items: start;
  align-content: start;
  width: 100%;
  height: auto;
  gap: 12px;
  padding: 16px;
  /* Mobile cards are never zoomed, focus-scaled or snapped. */
  zoom: 1;
  transform: none;
  scroll-snap-align: none;
  scroll-snap-stop: normal;
}

/* Name on top, photo to the left of the texts: the body wrapper unwraps
   (display: contents) so its children can place on the card grid. */
body.profile-mobile .profile-card__body { display: contents; }

body.profile-mobile .profile-card__name {
  grid-area: name;
  font-size: 28px;
}

body.profile-mobile .profile-card__photo {
  grid-area: photo;
  width: min(35vw, 300px);
  height: auto;
  /* Once the photo is shorter than the texts, keep it vertically
     centered beside them instead of pinned to the top of the row. */
  align-self: center;
}

body.profile-mobile .profile-card__texts {
  grid-area: texts;
  overflow: visible;
  font-size: 16px;
}

@media (prefers-reduced-motion: reduce) {
  .profile__pager {
    transition: none;
  }
}
