/* ==========================================================================
   Sujin Kim — portfolio
   Desktop layout, reference width 1440px, stable from ~1280px to ~1920px.
   ========================================================================== */

@font-face {
  font-family: "Le Murmure";
  src: url("../assets/fonts/le-murmure.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Iosevka Charon";
  src: url("../assets/fonts/iosevka-charon-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Iosevka Charon";
  src: url("../assets/fonts/iosevka-charon-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Iosevka Charon";
  src: url("../assets/fonts/iosevka-charon-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Iosevka Charon";
  src: url("../assets/fonts/iosevka-charon-medium-italic.woff2") format("woff2");
  font-weight: 500;
  font-style: italic;
  font-display: swap;
}

:root {
  /* Fonts */
  --font-display: "Le Murmure", "Bodoni Moda", "Didot", serif;
  --font-script: "vivaldi-mn", "Apple Chancery", "URW Chancery L", serif;
  --font-mono: "Iosevka Charon", "Iosevka", ui-monospace, "SF Mono", monospace;
  --font-body: "Inter", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;

  /* Colors */
  --bg-landing-top: #6983a2;
  --bg-landing-bottom: #7694b1;
  --bg-experience: #f4f0e8;
  --red: #e23b32;

  /* Grain (landing) */
  --grain-opacity: 0.32;
  --grain-size: 180px;

  /* Breakpoints (by viewport width). Media queries can't read custom
     properties, so the queries below repeat these numbers; js reads them.
       mobile   up to 599px    (reference width 390px)
       tablet   600px - 1179px (reference width 835px)
       desktop  1180px and up  (reference width 1440px)  */
  --bp-tablet: 600px;
  --bp-desktop: 1180px;
  --gutter: var(--side);   /* page side margin: 40px tablet, 30px mobile */

  /* Layout unit: 1px at the 1440×900 reference, scaled to the viewport */
  --u: clamp(0.75px, min(100vw / 1440, 100vh / 900), 1.4px);
  --side: calc(60 * var(--u));

  /* Navigation + instructions (both sections) */
  --nav-left: var(--side);
  --nav-size: calc(24 * var(--u));
  --nav-line: calc(50 * var(--u));
  --hint-right: var(--side);
  --hint-line: calc(31 * var(--u));

  /* Shared bottom baseline: distance from the bottom of the screen to the
     baseline of the last nav item ("Contact"). The instruction and the
     contact page's dog link sit on the same baseline.
     (Iosevka Charon: a line's baseline is line-height/2 - 0.375em above
     the bottom of its line box.) */
  --bottom-baseline: calc(55 * var(--u));
  --nav-bottom: calc(var(--bottom-baseline) - var(--nav-line) / 2 + 0.375 * var(--nav-size));
  --hint-bottom: calc(var(--bottom-baseline) - var(--hint-line) / 2 + 0.375 * var(--nav-size));

  /* Tagline (landing, all sizes) */
  --tagline-top: var(--side);
  --tagline-size: var(--nav-size);

  /* Portrait layout (viewport taller than wide); only used inside
     @media (orientation: portrait). Same arrangement as desktop, re-scaled. */
  --portrait-nav-size: clamp(17px, 1.6vw + 12px, 26px); /* nav, instruction, tagline */
  --portrait-gap: calc(24 * var(--u));      /* general clearance */
  --portrait-title-top: calc(var(--tagline-top) + 2.6 * var(--tagline-size) + var(--portrait-gap)); /* top of "Su - Jin" */
  --portrait-hands-width: 180vw;            /* width of the hand canvas (hand scale) */
  --portrait-hands-optical-y: 62%;          /* where the cupped palms sit */
  --portrait-jewel-gap: calc(3 * var(--portrait-gap)); /* min gap: "[receive - treasure]" -> top of the jewels */

  /* Screen transition (landing <-> experience).
     The visual style is chosen with data-transition on <body>. */
  --screen-duration: 700ms;
  --screen-easing: cubic-bezier(0.45, 0, 0.2, 1);

  /* Hands, placed inside the composition. Units are artwork pixels of the
     3840-wide reference composition (the 16:9 stage the title is set on).
     The hand PNGs share one 3840×2160 canvas; --hand-x/--hand-y put that
     canvas where it sits relative to the title in the reference mockup. */
  --hand-x: 18;
  --hand-y: 528;
  --hand-offset-y: 0;     /* extra vertical nudge, artwork px (+ = down) */
  --hand-content-bottom: 2140; /* lowest pearl on the hand canvas */
  --hand-palm-x: 1902;         /* centre of the cupped palms on the hand canvas */
  --hand-palm-y: 900;
  --hand-jewel-top: 167;       /* highest jewel under the title (the crystal) */
  --hand-jewel-left: 866;      /* outermost flower on the left ... */
  --hand-jewel-right: 2829;    /* ... and on the right */

  /* HAND SIZE (landscape). 1 = the size in the reference mockup.
     Both hand layers grow together around the palm point; the title keeps
     its size. */
  --hand-scale: 1.45;

  /* The hands stop growing before the flowers reach the navigation or the
     instruction: this is the gap that is always kept (cap by viewport width). */
  --hand-clear-x: calc(24 * var(--u));
  --hand-nav-width: calc(3.9 * var(--nav-size));   /* "Portfolio" */
  --hand-hint-width: calc(6.6 * var(--nav-size));  /* "scroll down for" */

  /* Title vs. revealed jewels: gap kept under "[receive - treasure]", and
     how close the title may move up towards the tagline to make room. */
  --title-clear: calc(14 * var(--u));
  --title-min-gap: calc(20 * var(--u));
  --title-bottom-share: 0.167; /* bottom of "[receive - treasure]" / stage width */

  /* Optical centring: where the cupped palms sit, as a share of the viewport
     height. Pearls may run past the bottom. If the jewels would reach the
     title, the title first moves up, then the palms move down. */
  --hands-optical-y: 60%;

  /* Landing composition block: title group + hands at the reference size.
     Its size sets the scale (contain) and so the title size; it does not
     change with --hand-scale. */
  --comp-width: 2072;   /* symmetric around the centre axis, covers jewels + title */
  --comp-top: 137;      /* top of "Su - Jin" */
  --comp-bottom: calc(var(--hand-y) + var(--hand-offset-y) + var(--hand-content-bottom));
  --comp-pad-y: 5vh;    /* breathing room above and below the block */
  --comp-reserve-x: calc(220 * var(--u)); /* keeps clear of nav and instruction */
  --landing-scale: 1;     /* multiplier on the fitted size */
  --landing-offset-y: 0px; /* nudge the whole block up (-) or down (+) */

  /* Artwork px -> screen px (derived, do not edit) */
  --comp-fit: min(
    (100vw - 2 * var(--comp-reserve-x)) / var(--comp-width),
    (100vh - 2 * var(--comp-pad-y)) / (var(--comp-bottom) - var(--comp-top))
  );
  --art-px: calc(var(--comp-fit) * var(--landing-scale));
  --stage-width: calc(3840 * var(--art-px));

  /* Hand canvas width on screen (derived, do not edit): the chosen size,
     capped so the flowers keep --hand-clear-x from the nav and the instruction */
  --hands-width: min(
    var(--hand-scale) * var(--stage-width),
    (50vw - var(--nav-left) - var(--hand-nav-width) - var(--hand-clear-x)) * 3840 / (var(--hand-palm-x) - var(--hand-jewel-left)),
    (50vw - var(--hint-right) - var(--hand-hint-width) - var(--hand-clear-x)) * 3840 / (var(--hand-jewel-right) - var(--hand-palm-x))
  );
  /* stage top -> palms, at the reference size */
  --palm-ref: calc((var(--hand-y) + var(--hand-offset-y) + var(--hand-palm-y)) * var(--art-px));
  /* stage top -> palms, needed for the jewels to clear the title */
  --palm-clear: calc(
    var(--title-bottom-share) * var(--stage-width) + var(--title-clear)
    + (var(--hand-palm-y) - var(--hand-jewel-top)) / 3840 * var(--hands-width)
  );
  /* highest the stage (title) may sit: title top stays under the tagline */
  --stage-top-min: calc(
    var(--tagline-top) + 1.3 * var(--tagline-size) + var(--title-min-gap) - var(--comp-top) * var(--art-px)
  );

  /* Hand reveal */
  --reveal-radius: calc(var(--hands-width) * 0.15); /* outer radius, follows the hand size */
  --reveal-feather: 72%;      /* share of the radius that fades out */
  --reveal-trail-scale: 0.72; /* size of the trailing blob */

  /* Title composition */
  --title-top: 4.2cqw;
  --title-center: 50.35%;
  --title-size: 11.9cqw;
  --script-size: 2.8cqw;
  --script-top: 8.1cqw;
  --script-left-end: 37.1%;  /* right edge of "designer" */
  --script-right-start: 63.35%; /* left edge of "portfolio" */
  --meaning-top: 14.95cqw;
  --meaning-left: 37.9%;
  --meaning-right: 61.7%;
  --meaning-size: 1.75cqw;
  --meaning-tracking: 0.22em;

  /* Experience */
  --exp-hint-size: calc(0.75 * var(--nav-size)); /* "hover a year to see more" (desktop) */
  --exp-title-top: calc(112 * var(--u));
  --exp-title-size: calc(127 * var(--u));
  --timeline-top: calc(326 * var(--u));
  --timeline-dot: calc(8 * var(--u));
  --timeline-dot-active: calc(16 * var(--u));
  --timeline-label-gap: calc(31 * var(--u));
  --timeline-label-size: calc(13 * var(--u));
  --detail-top: calc(438 * var(--u));
  --detail-title-size: calc(40 * var(--u));
  --detail-body-size: calc(19.25 * var(--u));
  --detail-body-line: calc(30 * var(--u));

  /* Contact page (colours inverted from the landing) */
  --contact-bg: var(--red);
  /* Contact text blue: saturated sky blue in the landing blue's hue family
     (2.54:1 against --red) */
  --contact-blue: #99ccff;
  --contact-weight: 500;          /* Medium, for small-text legibility */
  --contact-text: var(--contact-blue);
  --contact-size: var(--nav-size);
  --contact-line: var(--nav-line);
  --contact-services-line: var(--hint-line);
  --contact-strong-weight: 700;
  --contact-top: calc(331 * var(--u));       /* first text row */
  --contact-base-top: calc(517 * var(--u));  /* "Based in Toronto" row */
  --contact-dog-size: calc(24.6 * var(--u));

  /* Dog easter egg */
  --dog-size: 130px;    /* width of the photo that follows the cursor */
  --dog-swap-ms: 200;   /* ms between photos */

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ---------- Base ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

html,
body {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  background: var(--bg-experience);
  color: var(--red);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, p, ol, ul {
  margin: 0;
  padding: 0;
}

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

a:focus-visible,
button:focus-visible {
  outline: 1px solid currentColor;
  outline-offset: 4px;
}


/* Landing page on short landscape screens (phones turned sideways): the
   composition is laid out at least 1024px wide, as it always was. Only the
   landing / Experience page; the other pages adapt to any width. */
@media (orientation: landscape) {
  body[data-transition] {
    min-width: 1024px;
  }
}

/* ---------- Shared: navigation + instructions ---------- */

.side-nav {
  position: absolute;
  left: var(--nav-left);
  bottom: var(--nav-bottom);
  z-index: 5;
  display: flex;
  flex-direction: column;
  font-family: var(--font-mono);
  font-size: var(--nav-size);
  line-height: var(--nav-line);
}

.side-nav a {
  align-self: flex-start;
  transition: opacity 0.2s var(--ease);
}

.side-nav a:hover {
  opacity: 0.6;
}

/* current page */
.side-nav a[aria-current="page"] {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: currentColor;
}

.hint {
  position: absolute;
  right: var(--hint-right);
  bottom: var(--hint-bottom);
  z-index: 5;
  font-family: var(--font-mono);
  font-size: var(--nav-size);
  line-height: var(--hint-line);
  text-align: right;
}

/* Landing only: nav and instruction are vertically centred */
.landing .side-nav,
.landing .hint {
  top: 50%;
  bottom: auto;
  transform: translateY(-50%);
}

/* ---------- Screens + transitions ---------- */

/* Each section is a full-viewport screen; only one is active. */
.screen {
  position: fixed;
  inset: 0;
  overflow: hidden;
  visibility: hidden;
  z-index: 0;
}

.screen:focus {
  outline: none;
}

.screen.is-active {
  visibility: visible;
  z-index: 1;
}

.screen.is-leaving {
  visibility: visible;
  z-index: 1;
}

.screen.is-entering {
  visibility: visible;
  z-index: 2;
}

/* Transition: crossfade.
   The incoming screen fades in over the outgoing one (which stays opaque),
   so there is no dip through the page background.
   To add another style (e.g. a curtain wipe), add a [data-transition="name"]
   block that animates .is-entering / .is-leaving, and set the attribute on <body>. */
[data-transition="crossfade"] .screen.is-entering {
  animation: screen-fade-in var(--screen-duration) var(--screen-easing) both;
}

@keyframes screen-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---------- Landing ---------- */

.landing {
  background: linear-gradient(180deg, var(--bg-landing-top) 0%, var(--bg-landing-bottom) 100%);
}

/* Grain: SVG noise generated in CSS, above everything, never interactive */
.landing::after,
.contact::after,
.pf-sidebar::after,
.lightbox__backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='1 0 0 0 -.2 1 0 0 0 -.2 1 0 0 0 -.2 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: var(--grain-size) var(--grain-size);
}

/* The 16:9 reference composition the title is set on; horizontally centred.
   Everything in the title is sized in cqw (1cqw = 1% of the stage width).
   Vertically the palms sit at --hands-optical-y. When larger hands would put
   the jewels under the title, the stage (title) moves up, at most to
   --stage-top-min; past that the hands sit lower instead. */
.landing__stage {
  position: absolute;
  left: 50%;
  top: calc(
    min(
      var(--hands-optical-y) - var(--palm-ref),
      max(var(--stage-top-min), var(--hands-optical-y) - var(--palm-clear))
    )
    + var(--landing-offset-y)
  );
  width: var(--stage-width);
  aspect-ratio: 16 / 9;
  transform: translateX(-50%);
  container-type: inline-size;
}

/* Both hand images share one box: same position, size, scale, transform.
   Only the jewel layer's mask moves. The box is --hands-width wide and placed
   by its palm point: on the stage's centre axis, below the title. */
.hands {
  position: absolute;
  left: calc(
    (var(--hand-x) + var(--hand-palm-x)) * var(--art-px)
    - var(--hand-palm-x) / 3840 * var(--hands-width)
  );
  top: calc(
    max(var(--palm-ref), var(--palm-clear))
    - var(--hand-palm-y) / 3840 * var(--hands-width)
  );
  width: var(--hands-width);
  aspect-ratio: 3840 / 2160;
  z-index: 1;
}

.hands__layer {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  user-select: none;
  -webkit-user-drag: none;
}

.hands__layer--jewel {
  /* Set by js/reveal.js */
  --rx: 50%;
  --ry: 50%;
  --ra: 0;
  --tx: 50%;
  --ty: 50%;
  --ta: 0;
  --rr: var(--reveal-radius);

  --reveal-core: calc(100% - var(--reveal-feather));
  --reveal-mask:
    radial-gradient(
      circle var(--rr) at var(--rx) var(--ry),
      rgb(0 0 0 / var(--ra)) 0%,
      rgb(0 0 0 / var(--ra)) var(--reveal-core),
      rgb(0 0 0 / calc(var(--ra) * 0.72)) calc(var(--reveal-core) + var(--reveal-feather) * 0.3),
      rgb(0 0 0 / calc(var(--ra) * 0.36)) calc(var(--reveal-core) + var(--reveal-feather) * 0.58),
      rgb(0 0 0 / calc(var(--ra) * 0.1)) calc(var(--reveal-core) + var(--reveal-feather) * 0.82),
      rgb(0 0 0 / 0) 100%
    ),
    radial-gradient(
      circle calc(var(--rr) * var(--reveal-trail-scale)) at var(--tx) var(--ty),
      rgb(0 0 0 / var(--ta)) 0%,
      rgb(0 0 0 / calc(var(--ta) * 0.55)) 45%,
      rgb(0 0 0 / calc(var(--ta) * 0.15)) 78%,
      rgb(0 0 0 / 0) 100%
    );
  -webkit-mask-image: var(--reveal-mask);
  mask-image: var(--reveal-mask);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Ink peek (js/reveal.js): a small canvas over the palms, shown briefly */
.hands__ink {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  visibility: hidden;
}

.hands__ink.is-on {
  visibility: visible;
}

/* Area of the hands that triggers the reveal. On touch the finger drags the
   reveal around, so the browser must not treat it as a pan or zoom. */
.hands__hit {
  touch-action: none;
  position: absolute;
  left: 21%;
  top: 2%;
  width: 58%;
  height: 97%;
}

/* Title composition */
.title {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.title__name {
  position: absolute;
  top: var(--title-top);
  left: var(--title-center);
  transform: translateX(-50%);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--title-size);
  line-height: 1;
  white-space: nowrap;
  font-kerning: normal;
}

.title__script {
  position: absolute;
  top: var(--script-top);
  font-family: var(--font-script);
  font-weight: 400;
  font-style: normal;
  font-size: var(--script-size);
  line-height: 1;
  white-space: nowrap;
}

.title__script--left {
  right: calc(100% - var(--script-left-end));
}

.title__script--right {
  left: var(--script-right-start);
}

.title__meaning {
  position: absolute;
  top: var(--meaning-top);
  left: var(--meaning-left);
  width: calc(var(--meaning-right) - var(--meaning-left));
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  font-family: var(--font-script);
  font-weight: 400;
  font-size: var(--meaning-size);
  line-height: 1;
  letter-spacing: var(--meaning-tracking);
  white-space: nowrap;
}

/* Letter-spacing trails after each word's last glyph; pull it back so the
   dash sits centred and "]" lands on the right edge */
.title__meaning span {
  margin-right: calc(var(--meaning-tracking) * -1);
}

.title__meaning span:last-child {
  justify-self: end;
}

/* ---------- Experience ---------- */

.experience {
  background: var(--bg-experience);
}

.experience__title {
  position: absolute;
  top: var(--exp-title-top);
  left: var(--side);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--exp-title-size);
  line-height: 1;
  font-kerning: normal;
}

/* Small hint under the heading, set like the scroll instruction; the text
   depends on whether the device can hover */
.experience__hint {
  position: absolute;
  top: calc(var(--exp-title-top) + var(--exp-title-size) + 22 * var(--u));
  left: var(--side);
  font-family: var(--font-mono);
  font-size: var(--exp-hint-size);
  line-height: var(--hint-line);
}

.experience__hint--touch,
.experience__hint--scroll {
  display: none;
}

@media (hover: none) {
  .experience__hint--hover {
    display: none;
  }

  .experience__hint--touch {
    display: inline;
  }
}

/* Timeline + open space below it */
.experience__zone {
  position: absolute;
  left: var(--side);
  right: var(--side);
  /* snapped to whole pixels so the 1px line stays crisp */
  top: round(calc(var(--timeline-top) - 24 * var(--u)), 1px);
  height: calc(360 * var(--u));
}

.timeline {
  position: absolute;
  left: 0;
  right: 0;
  top: round(calc(24 * var(--u)), 1px);
  height: 0;
  list-style: none;
}

/* the line */
.timeline::before {
  content: "";
  position: absolute;
  left: calc(var(--timeline-dot) / 2);
  right: calc(var(--timeline-dot) / 2);
  top: 0;
  height: 1px;
  background: currentColor;
}

.timeline__item {
  position: absolute;
  top: 0;
  left: calc((100% - var(--timeline-dot)) * var(--pos) / 100);
  width: var(--timeline-dot);
}

.timeline__year {
  /* reset */
  appearance: none;
  border: 0;
  background: none;
  color: inherit;
  padding: 0;
  cursor: default;

  /* generous invisible hit area around the dot and label */
  position: absolute;
  top: calc(-24 * var(--u));
  left: calc(-24 * var(--u));
  width: calc(var(--timeline-dot) + 48 * var(--u));
  height: calc(72 * var(--u));
  font: inherit;
}

.timeline__dot {
  position: absolute;
  top: calc(24 * var(--u) - var(--timeline-dot) / 2);
  left: calc(24 * var(--u));
  width: var(--timeline-dot);
  height: var(--timeline-dot);
  border-radius: 50%;
  background: currentColor;
  transition: transform 0.35s var(--ease);
}

.timeline__year.is-active .timeline__dot {
  transform: scale(calc(var(--timeline-dot-active) / var(--timeline-dot)));
}

.timeline__label {
  position: absolute;
  top: calc(24 * var(--u) + var(--timeline-label-gap) - 11.4 * var(--u));
  left: calc(24 * var(--u));
  font-family: var(--font-mono);
  font-size: var(--timeline-label-size);
  line-height: 1;
  white-space: nowrap;
}

.timeline__item.is-end .timeline__label {
  left: auto;
  right: calc(24 * var(--u));
}

.experience__detail {
  position: absolute;
  left: 0;
  top: calc(var(--detail-top) - var(--timeline-top) + 24 * var(--u));
  right: 0;
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}

.experience__detail.is-visible {
  opacity: 1;
}

.experience__detail h3 {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--detail-title-size);
  line-height: 1.2;
  letter-spacing: -0.005em;
  word-spacing: -0.05em;
}

.experience__detail ul {
  margin-top: calc(12 * var(--u));
  list-style: none;
  font-size: var(--detail-body-size);
  line-height: var(--detail-body-line);
}

.experience__detail li {
  position: relative;
  padding-left: calc(30 * var(--u));
}

.experience__detail li::before {
  content: "";
  position: absolute;
  left: calc(12 * var(--u));
  top: calc(var(--detail-body-line) / 2 - 1 * var(--u));
  width: calc(5.5 * var(--u));
  height: calc(5.5 * var(--u));
  border-radius: 50%;
  background: currentColor;
}

/* Tagline, top centre of the landing */
.tagline {
  position: absolute;
  top: var(--tagline-top);
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  max-width: calc(100% - 2 * var(--side));
  font-family: var(--font-mono);
  font-size: var(--tagline-size);
  line-height: 1.3;
  text-align: center;
  text-wrap: balance;
  width: max-content;
}

/* ---------- Landing: portrait (tablet / mobile proportions) ----------
   Same arrangement as desktop. The title row is scaled so "designer" and
   "portfolio" touch the side margins; the hands are placed by their palm point
   (--portrait-hands-optical-y), kept clear of the title and the nav. */
@media (orientation: portrait) {
  :root {
    --nav-size: var(--portrait-nav-size);
    --nav-line: calc(var(--nav-size) * 2.08);
    --hint-line: calc(var(--nav-size) * 1.3);
    --bottom-baseline: calc(var(--nav-size) * 2.29);
    --reveal-radius: calc(var(--portrait-hands-width) * 0.15);

    /* derived, do not edit -------------------------------------------- */
    /* title block: its ink spans 28.07%–71.59% of its width, cap top at 3.65%,
       bottom of "[receive - treasure]" at 16.33% */
    --p-title-w: calc((100vw - 2 * var(--side)) / 0.4352);
    --p-title-bottom: calc(var(--portrait-title-top) + var(--p-title-w) * 0.1268);
    /* hand canvas: palms at y 900, jewels start at y 53, hands end at y 1474 */
    --p-hands-top-optical: calc(var(--portrait-hands-optical-y) - var(--portrait-hands-width) * 900 / 3840);
    --p-hands-top-nav: calc(100vh - var(--nav-bottom) - 3 * var(--nav-line) - var(--portrait-gap) - var(--portrait-hands-width) * 1474 / 3840);
    --p-hands-top-title: calc(var(--p-title-bottom) + var(--portrait-jewel-gap) - var(--portrait-hands-width) * 53 / 3840);
  }

  .landing__stage {
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    transform: none;
  }

  /* The desktop title block, re-scaled; its children keep their cqw layout */
  .title {
    inset: auto;
    left: calc(var(--side) - var(--p-title-w) * 0.2807);
    top: calc(var(--portrait-title-top) - var(--p-title-w) * 0.0365);
    width: var(--p-title-w);
    height: calc(var(--p-title-w) * 0.17);
    container-type: inline-size;
  }

  /* Both hand layers keep sharing this one box.
     Palms at the optical point, but never so low that the empty hands reach
     the nav, and never so high that the jewels touch the title (title wins). */
  .hands {
    left: calc(50% - var(--portrait-hands-width) * 1902 / 3840);
    top: max(
      min(var(--p-hands-top-optical), var(--p-hands-top-nav)),
      var(--p-hands-top-title)
    );
    width: var(--portrait-hands-width);
  }

  /* Nav and instruction in the bottom corners (revealed pearls may pass behind) */
  .landing .side-nav,
  .landing .hint {
    top: auto;
    transform: none;
  }

  .landing .side-nav {
    bottom: var(--nav-bottom);
  }

  .landing .hint {
    bottom: var(--hint-bottom);
  }
}

/* ---------- Contact page ---------- */

.contact {
  position: relative;
  height: 100vh;
  overflow: hidden;
  background: var(--contact-bg);
  color: var(--contact-text);
  font-family: var(--font-mono);
  font-weight: var(--contact-weight);
  font-size: var(--contact-size);
  line-height: var(--contact-line);
}

.contact__title {
  position: absolute;
  top: var(--exp-title-top);
  left: var(--side);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--exp-title-size);
  line-height: 1;
  font-kerning: normal;
}

.contact__left,
.contact__right {
  position: absolute;
  top: var(--contact-top);
}

.contact__left {
  left: var(--side);
}

.contact__right {
  right: var(--side);
  text-align: right;
}

.contact__services {
  margin-top: calc(9.5 * var(--u));
  line-height: var(--contact-services-line);
}

/* the bold row sits at a fixed height in both columns */
.contact__base {
  position: absolute;
  top: calc(var(--contact-base-top) - var(--contact-top));
  white-space: nowrap;
}

.contact__left .contact__base {
  left: 0;
}

.contact__right .contact__base {
  right: 0;
}

.contact__strong {
  font-weight: var(--contact-strong-weight);
}

/* content links (not the nav, which keeps the shared nav hover) */
.contact__left a,
.contact__dog a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: transparent;
  transition: text-decoration-color 0.2s var(--ease);
}

.contact__left a:hover,
.contact__left a:focus-visible,
.contact__dog a:hover,
.contact__dog a:focus-visible {
  text-decoration-color: currentColor;
}

/* Same line box as the last nav item (bottom, size, line-height), so its
   baseline is exactly the nav's (--bottom-baseline); the larger italic text
   sits on that baseline. */
.contact__dog {
  position: absolute;
  bottom: var(--nav-bottom);
  left: 0;
  right: 0;
  z-index: 5;
  text-align: center;
  font-style: italic;
  font-size: var(--nav-size);
  line-height: var(--nav-line);
}

.contact .dog-link {
  font-size: var(--contact-dog-size);
  line-height: 1;
  text-decoration-color: currentColor;
}

.contact .dog-link.has-dog:hover {
  cursor: none;
}

.dog-cursor {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 10;
  width: var(--dog-size);
  height: auto;
  pointer-events: none;
  visibility: hidden;
}

.dog-cursor.is-visible {
  visibility: visible;
}

/* ---------- Tablet + mobile (below 1180px) ----------
   Desktop is untouched: everything here sits in max-width queries.
   The landing composition keeps its own rules (above); the Experience screen
   and the contact page switch from absolute placement to a flowing layout
   that may scroll when an entry is long.
   Named queries:  tablet-and-mobile = (max-width: 1179.98px)
                   mobile            = (max-width: 599.98px)            */

/* tablet-and-mobile */
@media (max-width: 1179.98px) {
  :root {
    --gutter: 40px;
    --top-nav-top: 42px;    /* top of the centred nav */
    --top-nav-size: 24px;
    --top-nav-gap: 80px;    /* between Home, Portfolio, Contact */
    --touch-target: 44px;   /* minimum height of anything tappable */
  }

  /* page-level scrolling where the content can be taller than the screen */
  body.page-contact,
  html:has(body.page-contact) {
    overflow: auto;
    height: auto;
  }

  /* the swipe between landing and Experience must not pull-to-refresh */
  html:has(body[data-transition]),
  body[data-transition] {
    overscroll-behavior: none;
  }

  /* Nav centred at the top (not on the landing, whose composition is kept) */
  .experience .side-nav,
  .contact .side-nav {
    position: static;
    left: auto;
    bottom: auto;
    transform: none;
    flex-direction: row;
    justify-content: center;
    gap: var(--top-nav-gap);
    margin-top: calc(var(--top-nav-top) - (var(--touch-target) - var(--top-nav-size)) / 2);
    font-size: var(--top-nav-size);
    line-height: var(--touch-target);
  }

  .experience .side-nav a,
  .contact .side-nav a {
    align-self: auto;
  }

  /* ----- Experience ----- */
  .experience {
    --timeline-dot: 6px;
    --timeline-dot-active: 13px;
    --timeline-label-gap: 18px;
    --timeline-label-size: 12px;
    --detail-title-size: 33px;
    --detail-body-size: 17px;
    --detail-body-line: 25px;
    --exp-title-size: min(128px, 15.4vw);
    --exp-hit: 44px;          /* tap area around each year */

    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto auto 1fr;
    grid-template-areas:
      "nav     nav"
      "title   hint"
      "exphint ."
      "zone    zone";
    column-gap: 24px;
    padding: 0 var(--gutter) 48px;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  .experience .side-nav { grid-area: nav; }

  .experience__title {
    grid-area: title;
    position: static;
    margin-top: 84px;
  }

  .experience .hint {
    grid-area: hint;
    position: static;
    align-self: center;
    margin-top: 84px;
    font-size: var(--top-nav-size);
  }

  .experience__hint {
    grid-area: exphint;
    position: static;
    margin-top: 14px;
    font-size: var(--top-nav-size);
  }

  .experience__zone {
    grid-area: zone;
    position: relative;
    left: auto;
    right: auto;
    top: auto;
    height: auto;
    margin-top: 72px;
  }

  .timeline {
    position: relative;
    top: 0;
    height: calc(var(--timeline-label-gap) + var(--timeline-label-size) + 8px);
  }

  .timeline__year {
    top: calc(var(--timeline-dot) / 2 - var(--exp-hit) / 2);
    left: calc(var(--timeline-dot) / 2 - var(--exp-hit) / 2);
    width: var(--exp-hit);
    height: var(--exp-hit);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }

  .timeline__dot {
    top: calc(var(--exp-hit) / 2 - var(--timeline-dot) / 2);
    left: calc(var(--exp-hit) / 2 - var(--timeline-dot) / 2);
  }

  .timeline__label {
    top: calc(var(--exp-hit) / 2 + var(--timeline-label-gap) - 0.875 * var(--timeline-label-size));
    left: calc(var(--exp-hit) / 2 - var(--timeline-dot) / 2);
  }

  .timeline__item.is-end .timeline__label {
    right: calc(var(--exp-hit) / 2 - var(--timeline-dot) / 2);
  }

  .experience__detail {
    position: static;
    margin-top: 72px;
  }

  .experience__detail ul {
    margin-top: 14px;
  }

  .experience__detail li {
    padding-left: 24px;
  }

  .experience__detail li::before {
    left: 9px;
    top: calc(var(--detail-body-line) / 2 - 2px);
    width: 4px;
    height: 4px;
  }

  /* ----- Contact ----- */
  .contact {
    --contact-size: 20px;
    --contact-line: 50px;
    --contact-services-line: 26px;
    --exp-title-size: min(130px, 15.6vw);

    display: flex;
    flex-direction: column;
    height: auto;
    min-height: 100vh;
    min-height: 100dvh;
    padding: 0 var(--gutter);
    overflow: visible;
  }

  .contact .side-nav {
    order: -1;
    align-self: center;
  }

  .contact__title {
    position: static;
    margin-top: 84px;
  }

  .contact__left,
  .contact__right {
    position: static;
    text-align: left;
  }

  .contact__left {
    margin-top: 76px;
  }

  .contact__right {
    margin-top: 96px;
  }

  .contact__services {
    margin-top: 24px;
  }

  .contact__base {
    position: static;
    white-space: normal;
  }

  .contact__left .contact__base {
    margin-top: 22px;
  }

  .contact__right .contact__base {
    margin-top: 58px;
  }

  /* the links in the text keep a comfortable tap height */
  .contact__left a {
    display: inline-block;
    min-height: var(--touch-target);
  }

  .contact__dog {
    position: static;
    margin-top: auto;
    padding: 96px 0 128px;
    font-size: var(--contact-size);
    line-height: var(--touch-target);
  }

  .contact .dog-link {
    font-size: var(--contact-size);
    display: inline-block;
    min-height: var(--touch-target);
  }
}

/* mobile */
@media (max-width: 599.98px) {
  :root {
    --gutter: 30px;
    --top-nav-top: 32px;
    --top-nav-size: 20px;
    --top-nav-gap: 40px;
  }

  /* ----- Experience: vertical timeline on the left edge ----- */
  .experience {
    --timeline-dot: 6px;
    --timeline-dot-active: 13px;
    --timeline-label-size: 11px;
    --detail-title-size: 24px;
    --detail-body-size: 15px;
    --detail-body-line: 22px;
    --exp-title-size: min(86px, 22vw);
    --exp-col: 44px;          /* width of the timeline column */
    --exp-line-x: 20px;       /* the line, from the column's left edge */

    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto auto 1fr;
    grid-template-areas:
      "nav"
      "hint"
      "title"
      "exphint"
      "zone";
    padding-bottom: 32px;
  }

  .experience .hint {
    align-self: auto;
    margin-top: 0;
    font-size: 12px;
    line-height: 1.3;
    text-align: center;
  }

  .experience .hint br {
    display: none;
  }

  .experience__title {
    margin-top: 4px;
    text-align: center;
  }

  /* one line under the heading: "scroll up to go main page • tap a year to
     see more" (the scroll instruction above the heading is folded into it);
     on very narrow screens the text gets smaller instead of wrapping */
  .experience .hint {
    display: none;
  }

  .experience__hint {
    margin-top: 6px;
    font-size: min(12px, calc((100vw - 2 * var(--gutter)) / 23));
    line-height: 1.3;
    text-align: center;
    white-space: nowrap;
  }

  .experience__hint--scroll {
    display: inline;
  }

  .experience__zone {
    display: grid;
    grid-template-columns: var(--exp-col) minmax(0, 1fr);
    grid-template-rows: minmax(420px, 1fr);
    column-gap: 15px;
    margin-top: 22px;
  }

  .timeline {
    height: auto;
    width: var(--exp-col);
  }

  /* the line runs down the column */
  .timeline::before {
    left: var(--exp-line-x);
    right: auto;
    top: calc(var(--timeline-dot) / 2);
    bottom: calc(var(--timeline-dot) / 2);
    width: 1px;
    height: auto;
  }

  /* uneven spacing, proportional to the desktop line */
  .timeline__item {
    left: calc(var(--exp-line-x) + 0.5px - var(--timeline-dot) / 2);
    top: calc((100% - var(--timeline-dot)) * var(--pos) / 100);
    height: var(--timeline-dot);
  }

  /* years rotated 90deg, reading downwards, to the left of the line */
  .timeline__label,
  .timeline__item.is-end .timeline__label {
    top: calc(var(--exp-hit) / 2 - var(--timeline-dot) / 2);
    bottom: auto;
    left: auto;
    right: calc(var(--exp-hit) / 2 + var(--timeline-dot) / 2 + 7px);
    writing-mode: vertical-rl;
  }

  /* the last year ends at its dot */
  .timeline__item.is-end .timeline__label {
    top: auto;
    bottom: calc(var(--exp-hit) / 2 - var(--timeline-dot) / 2);
  }

  .experience__detail {
    margin-top: -6px;
  }

  .experience__detail ul {
    margin-top: 10px;
  }

  .experience__detail li {
    padding-left: 20px;
  }

  .experience__detail li::before {
    left: 7px;
  }

  /* ----- Contact ----- */
  .contact {
    --contact-size: 15px;
    --contact-line: 32px;
    --contact-services-line: 20px;
    --exp-title-size: min(86px, 22vw);
  }

  .contact__title {
    margin-top: 40px;
  }

  .contact__left {
    margin-top: 52px;
  }

  .contact__left .contact__base {
    margin-top: 3px;
  }

  .contact__right {
    margin-top: 60px;
  }

  .contact__services {
    margin-top: 18px;
  }

  /* the services wrap naturally within the margins */
  .contact__services br {
    display: none;
  }

  .contact__right .contact__base {
    margin-top: 12px;
  }

  .contact__dog {
    padding: 64px 0 40px;
  }
}

/* "Tap again!" bubble beside the dog photo (touch only, js/dog.js) */
.dog-bubble {
  position: fixed;
  z-index: 11;
  padding: 6px 11px 7px;
  border-radius: 14px;
  background: var(--contact-blue);
  color: var(--red);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 14px;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
  visibility: hidden;
  transform-origin: 0 50%;
}

/* small tail on the left, pointing at the dog */
.dog-bubble::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 50%;
  border-style: solid;
  border-width: 5px 7px 5px 0;
  border-color: transparent var(--contact-blue) transparent transparent;
  transform: translateY(-50%);
}

.dog-bubble.is-visible {
  visibility: visible;
  animation: dog-bubble-pop 150ms var(--ease) both;
}

@keyframes dog-bubble-pop {
  from { transform: scale(0.6); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .dog-bubble.is-visible {
    animation: none;
  }
}

/* ---------- Page-to-page transition ----------
   Cross-document View Transitions: same crossfade as the in-page screens
   (new page fades in over the old one, --screen-duration / --screen-easing).
   Browsers without support simply navigate instantly. */
@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation: none;
}

::view-transition-new(root) {
  animation: screen-fade-in var(--screen-duration) var(--screen-easing) both;
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }
}

/* ---------- Placeholder pages ---------- */

.placeholder {
  position: relative;
  height: 100vh;
}

.placeholder__title {
  position: absolute;
  top: var(--exp-title-top);
  left: var(--side);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--exp-title-size);
  line-height: 1;
}

.placeholder__note {
  position: absolute;
  top: calc(326 * var(--u));
  left: var(--side);
  font-family: var(--font-mono);
  font-size: var(--nav-size);
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  :root {
    --screen-duration: 0ms;
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}
