/* Tokens */
:root {
  --paper: #fafaf9;
  --ink: #18181b;
  --muted: #71717a;
  --line: #e4e4e7;
  --overlay: rgb(250 250 249 / 0.72);
  --tag: #ebeae7;
  --tag-hover: #18181b;
  --tag-hover-ink: #fafaf9;

  --font-display: "Bricolage Grotesque", "Arial Black", sans-serif;
  --font-text: "Geist", system-ui, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, monospace;

  --gutter: clamp(1rem, 2.5vw, 2rem);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* z-index scale */
  --z-blur: 5;
  --z-header: 10;
  --z-menu: 20;
}

/* Light is the default; the header switch sets data-theme="dark" */
:root {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #131315;
  --ink: #f4f4f5;
  --muted: #a1a1aa;
  --line: #27272a;
  --overlay: rgb(19 19 21 / 0.72);
  --tag: #26262a;
  --tag-hover: #f4f4f5;
  --tag-hover-ink: #131315;
}

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: 1rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

body.menu-open {
  overflow: hidden;
}

img {
  display: block;
  max-width: 100%;
}

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

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1;
  color: var(--muted);
  white-space: nowrap;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -4rem;
  z-index: calc(var(--z-menu) + 1);
  padding: 0.5rem 0.75rem;
  background: var(--ink);
  color: var(--paper);
}

.skip-link:focus {
  top: var(--gutter);
}

/* Header */
.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--gutter);
  pointer-events: none;
}

.site-header > * {
  pointer-events: auto;
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

.theme-toggle {
  padding: 0.5rem 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--ink);
}

.mark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.75rem;
  line-height: 1;
  letter-spacing: -0.04em;
}

.menu-toggle {
  position: relative;
  width: 2.5rem;
  height: 2.5rem;
  margin: -0.5rem;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

.menu-toggle__icon,
.menu-toggle__icon::before {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1.75rem;
  height: 1.5px;
  background: currentColor;
  translate: -50% -50%;
  transition: rotate 0.5s var(--ease-out);
}

.menu-toggle__icon::before {
  content: "";
  rotate: 90deg;
}

.menu-toggle[aria-expanded="true"] .menu-toggle__icon {
  rotate: 45deg;
}

/* Menu overlay */
.menu {
  position: fixed;
  inset: 0;
  z-index: var(--z-menu);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(var(--gutter) * 4) var(--gutter) var(--gutter);
  background: var(--overlay);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
}

.menu:not([hidden]) {
  animation: fade-in 0.35s var(--ease-out);
}

.menu[hidden] {
  display: none;
}

/* Keep the close button above the overlay */
body.menu-open .site-header {
  z-index: calc(var(--z-menu) + 1);
}

.menu__list a {
  display: inline-flex;
  align-items: baseline;
  gap: 0.75rem;
  font-family: var(--font-text);
  font-weight: 500;
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
}

.menu__list a:hover {
  opacity: 0.55;
}

.menu__list .label {
  translate: 0 -0.2em;
}

.menu__email {
  align-self: flex-start;
  font-size: 1.25rem;
  border-bottom: 1px solid currentColor;
}

/* Hero */
/*
  Same structure as the reference: title at the top, cards at the bottom, a flexible
  gap in between. Tall windows: the cards sit 32px above the bottom edge. Shorter
  windows (a MacBook Pro 14"): the gap shrinks to its minimum, the cards start about
  halfway down the screen and run past the bottom edge into the blur.
*/
.hero {
  /* fixed card size at every screen size, like the reference */
  --card-w: 181px;
  --card-h: 520px;
  --card-gap: 10px;
  --card-radius: 80px;
  --hero-bottom: 32px;

  position: relative;
  display: grid;
  /* minmax(0, 1fr): the strip's long track must not widen the column */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(3.5rem, 1fr) auto;
  min-height: 100dvh;
  padding-bottom: var(--hero-bottom);
  overflow-x: clip;
}

.hero__title {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 6.5rem;
  text-align: center;
}

.wordmark {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(6rem, 17vw, 16.5rem);
  /* "yann" has no ascenders: a tight line box keeps the title compact */
  line-height: 0.8;
  letter-spacing: -0.02em;
  /* room for the y descender */
  padding-bottom: 0.14em;
}

.wordmark__letter {
  display: inline-block;
}

.wordmark--upper {
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 1.05;
  padding-bottom: 0;
}

.wordmark--space {
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  letter-spacing: -0.03em;
}

.wordmark--space.wordmark--upper {
  letter-spacing: -0.03em;
}

/* Mirrored n's need a little air so they read as reversed letters, not a smudge */
.wordmark__letter--flip {
  scale: -1 1;
  margin-inline: 0.02em;
}

/* Set like the bio: same family, weight, size and tracking */
.hero__tagline {
  margin: 0.75rem 0 0;
  padding-inline: var(--gutter);
  color: var(--ink);
  font-weight: 500;
  font-size: clamp(1rem, 1.8vw, 1.625rem);
  line-height: 1.45;
  letter-spacing: -0.03em;
}

/*
  Rolling words in the tagline, dressed like the work links in the bio: a grey
  leaf-shaped tag in the wordmark font. The tag resizes to each word (so the centred
  sentence glides), the words roll up inside it, and its rounded corners swap
  sides at every change, like the bio tags on hover. main.js drives it.
*/
.roller {
  --r: 0.45em;
  position: relative;
  display: inline-block;
  padding: 0 0.32em 0.06em;
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
  white-space: nowrap;
  background: var(--tag);
  border-radius: var(--r) 0;
}

/* Without JavaScript: a plain tag with the first word */
.roller__sizer,
.roller__word:not([data-current]) {
  display: none;
}

.js .roller {
  /* clip-path (not overflow) keeps the tag on the text baseline */
  clip-path: inset(0 round var(--r) 0);
  transition:
    width 0.6s cubic-bezier(0.65, 0, 0.35, 1),
    border-radius 0.45s var(--ease-out),
    clip-path 0.45s var(--ease-out);
}

.js .roller[data-flipped] {
  border-radius: 0 var(--r);
  clip-path: inset(0 round 0 var(--r));
}

/* invisible copy of the first word: gives the tag its baseline and starting size */
.js .roller__sizer {
  display: inline;
  visibility: hidden;
}

.js .roller__word {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  padding: 0 0.32em 0.06em;
  opacity: 0;
  translate: 0 100%;
  transition:
    translate 0.6s cubic-bezier(0.65, 0, 0.35, 1),
    opacity 0.4s ease;
}

.js .roller__word[data-current] {
  opacity: 1;
  translate: 0 0;
}

.js .roller__word[data-leaving] {
  opacity: 0;
  translate: 0 -100%;
}

@media (prefers-reduced-motion: reduce) {
  .js .roller,
  .js .roller__word {
    transition: opacity 0.6s ease;
  }

  .js .roller__word,
  .js .roller__word[data-leaving] {
    translate: none;
  }
}


.hero__side {
  position: absolute;
  bottom: calc(var(--card-h) + var(--hero-bottom) + 1.5rem);
  writing-mode: vertical-rl;
  rotate: 180deg;
}

.hero__side--left {
  left: var(--gutter);
}

.hero__side--right {
  right: var(--gutter);
}

a.hero__side:hover {
  color: var(--ink);
}

.clock {
  font-variant-numeric: tabular-nums;
}

/* Card strip */
.strip {
  grid-row: 3;
  position: relative;
  height: var(--card-h);
  /* clip sideways only, so card corners are never cut */
  overflow-x: clip;
  /* fade the left and right edges into the page background, like the reference */
  mask-image: linear-gradient(to right, transparent, #000 13%, #000 87%, transparent);
}

.strip__track {
  display: flex;
  width: max-content;
  height: 100%;
  /* constant speed per card, whatever the number of cards (main.js sets --cards) */
  animation: strip-scroll calc(var(--cards, 10) * 3.2s) linear infinite;
}

.card {
  flex: none;
  width: var(--card-w);
  height: 100%;
  /* margin instead of flex gap keeps both copies exactly 50% wide, so the loop has no jump */
  margin-right: var(--card-gap);
}

/*
  Entrance: the cards stay hidden until the ones on screen have loaded, then
  fade and rise in quickly from left to right (main.js sets --n and .is-ready).
  Only with JavaScript, so the cards always show if the script fails.
*/
.js .card {
  opacity: 0;
  translate: 0 1.25rem;
}

.js .strip.is-ready .card {
  opacity: 1;
  translate: 0 0;
  transition:
    opacity 0.45s ease-out,
    translate 0.7s var(--ease-out);
  transition-delay: calc(var(--n, 0) * 70ms);
}

@media (prefers-reduced-motion: reduce) {
  .js .card {
    translate: none;
  }

  .js .strip.is-ready .card {
    transition: opacity 0.3s ease-out;
    transition-delay: 0s;
  }
}

.card img,
.card video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--line);
}

/*
  Mirrored pair, repeated: top-left + bottom-right rounded, then top-right +
  bottom-left. main.js keeps the count even so the pattern never breaks.
*/
.card:nth-child(odd) :is(img, video) {
  border-radius: var(--card-radius) 0;
}

.card:nth-child(even) :is(img, video) {
  border-radius: 0 var(--card-radius);
}

/*
  Progressive blur: 8 stacked layers, each blurring twice as much as the last,
  each masked to its own band. Same technique as the reference site.
*/
.pblur {
  position: absolute;
  pointer-events: none;
}

.pblur i {
  position: absolute;
  inset: 0;
  backdrop-filter: blur(var(--b));
  -webkit-backdrop-filter: blur(var(--b));
  --s: calc((var(--k) - 1) * 12.5%);
  mask-image: linear-gradient(
    var(--dir),
    transparent var(--s),
    #000 calc(var(--s) + 12.5%),
    #000 calc(var(--s) + 25%),
    transparent calc(var(--s) + 37.5%)
  );
}

.pblur i:nth-child(1) { --k: 1; --b: 0.25px; }
.pblur i:nth-child(2) { --k: 2; --b: 0.5px; }
.pblur i:nth-child(3) { --k: 3; --b: 1px; }
.pblur i:nth-child(4) { --k: 4; --b: 2px; }
.pblur i:nth-child(5) { --k: 5; --b: 4px; }
.pblur i:nth-child(6) { --k: 6; --b: 8px; }
.pblur i:nth-child(7) { --k: 7; --b: 16px; }
.pblur i:nth-child(8) { --k: 8; --b: 32px; }

.pblur--bottom {
  position: fixed;
  inset: auto 0 0;
  z-index: var(--z-blur);
  height: 20dvh;
  --dir: to bottom;
  transition: opacity 0.4s var(--ease-out);
}

/* Lift the blur off the footer at the end of the page so it stays readable */
body.at-end .pblur--bottom {
  opacity: 0;
}

.pblur--top {
  position: fixed;
  inset: 0 0 auto;
  z-index: var(--z-blur);
  height: 7rem;
  --dir: to top;
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
}

body.is-scrolled .pblur--top {
  opacity: 1;
}

/* About */
.about {
  display: grid;
  gap: 2rem;
  max-width: 72rem;
  margin: 0 auto;
  padding: clamp(6rem, 16vh, 12rem) var(--gutter);
}

.about__statement {
  margin: 0;
  max-width: 50ch;
  font-weight: 500;
  /* half the previous size (1.625rem on desktop); 1rem minimum keeps it readable on phones */
  font-size: clamp(1rem, 1.8vw, 1.625rem);
  /* room between lines for the hover underlines */
  line-height: 1.45;
  letter-spacing: -0.03em;
}

/* "yann" in the bio, set like the wordmark at the top of the page */
.name-mark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.3em;
  /* bigger than the text around it without pushing the lines apart */
  line-height: 0;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.name-mark__flip {
  display: inline-block;
  scale: -1 1;
  margin-inline: 0.02em;
}

/*
  The five things Yann makes, as links to their pages. Each is a small tag in the
  leaf shape of the homepage cards, set in the wordmark font. On hover the rounded
  corners swap sides (mirrored, like the n's in "yann") and the tag fills with an
  image from that page.
*/
.work-link {
  --r: 0.45em;
  position: relative;
  isolation: isolate;
  display: inline-block;
  padding: 0 0.32em 0.06em;
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--ink);
  background: var(--tag);
  border-radius: var(--r) 0;
  transition:
    border-radius 0.45s var(--ease-out),
    color 0.25s var(--ease-out),
    background-color 0.25s var(--ease-out),
    scale 0.2s var(--ease-out);
}

/* the preview image, with a scrim so the word stays readable on top */
.work-link--image::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* follows the tag's corners as they swap (no overflow clipping: it would break the baseline) */
  border-radius: inherit;
  background:
    linear-gradient(rgb(0 0 0 / 0.38), rgb(0 0 0 / 0.38)),
    var(--img) center / cover no-repeat;
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}

.work-link:hover,
.work-link:focus-visible {
  border-radius: 0 var(--r);
  color: var(--tag-hover-ink);
  background-color: var(--tag-hover);
}

.work-link--image:hover,
.work-link--image:focus-visible {
  color: #fafaf9;
}

.work-link--image:hover::before,
.work-link--image:focus-visible::before {
  opacity: 1;
}

.work-link:active {
  scale: 0.97;
}

@media (prefers-reduced-motion: reduce) {
  .work-link,
  .work-link--image::before {
    transition: none;
  }
}

/* Footer */
.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0 var(--gutter) var(--gutter);
}

/*
  Email button: a pill with a small dot. On hover the dot grows to fill the pill,
  the label slides out to the right and an inverted label with an arrow slides in.
*/
.cta {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  overflow: hidden;
  padding: 0.6rem 1.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}

.cta__rest {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.cta__dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: none;
  border-radius: 50%;
  background: var(--ink);
  transition: scale 0.3s var(--ease-out);
}

.cta__label {
  transition:
    translate 0.3s var(--ease-out),
    opacity 0.3s var(--ease-out);
}

.cta__hover {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  color: var(--paper);
  opacity: 0;
  translate: 3rem 0;
  transition:
    translate 0.3s var(--ease-out),
    opacity 0.3s var(--ease-out);
}

.cta__arrow {
  width: 1.1em;
  height: 1.1em;
}

.cta:hover .cta__dot,
.cta:focus-visible .cta__dot {
  scale: 60;
}

.cta:hover .cta__label,
.cta:focus-visible .cta__label {
  translate: 3rem 0;
  opacity: 0;
}

.cta:hover .cta__hover,
.cta:focus-visible .cta__hover {
  translate: 0 0;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .cta__dot,
  .cta__label,
  .cta__hover {
    transition: none;
  }
}

/* Load sequence: letters rise, then both n's flip */
@media (prefers-reduced-motion: no-preference) {
  .wordmark__letter {
    animation: letter-rise 0.9s var(--ease-out) backwards;
    animation-delay: calc(var(--i) * 70ms);
  }

  .wordmark__letter--flip {
    animation:
      letter-rise 0.9s var(--ease-out) backwards,
      letter-flip 0.8s var(--ease-out) backwards;
    animation-delay: calc(var(--i) * 70ms), calc(700ms + var(--i) * 90ms);
  }

  /* Tagline wipes in from left to right with a soft edge, after the name lands */
  .hero__tagline {
    -webkit-mask-image: linear-gradient(90deg, #000 33.3%, transparent 66.6%);
    mask-image: linear-gradient(90deg, #000 33.3%, transparent 66.6%);
    -webkit-mask-size: 300% 100%;
    mask-size: 300% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    animation: wipe-in 6.4s var(--ease-out) 0.6s both;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .strip {
    overflow-x: auto;
  }

  .strip__track {
    animation: none;
  }

  .menu-toggle__icon,
  .pblur--top,
  .pblur--bottom {
    transition: none;
  }
}

@keyframes letter-rise {
  from {
    opacity: 0;
    translate: 0 0.35em;
  }
}

@keyframes letter-flip {
  from {
    scale: 1 1;
  }
}

@keyframes wipe-in {
  from {
    -webkit-mask-position: 100% 0;
    mask-position: 100% 0;
  }
  to {
    -webkit-mask-position: 0 0;
    mask-position: 0 0;
  }
}

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

/* Right to left: the track holds two copies, so moving by one copy loops seamlessly */
@keyframes strip-scroll {
  to {
    transform: translateX(-50%);
  }
}

/* Small screens */
@media (max-width: 767px) {
  /* a taller minimum gap so the vertical side labels fit between tagline and cards */
  .hero {
    grid-template-rows: auto minmax(8rem, 1fr) auto;
  }

  .hero__title {
    padding-top: 7rem;
  }

}
