:root {
  --bg: #ffffff;
  /* matches the page background so the "o" reads as background */
  --ink: #16181c;
  /* The ring (border) of the "o" — sized to the logo's letter stroke width
     (~0.11em of the wordmark) so it matches the weight of the letters. */
  --ring: calc(var(--logo-size) * 0.1);
  --logo-size: 8rem;
  /* Footer text + links — light gray. */
  --footer-fg: #757575;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: auto;
  /* hide the scrollbar — vertical scroll still drives the carousel, but no
     visible scrollbar (Firefox) */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

/* hide the scrollbar (WebKit/Blink) while keeping scrolling functional */
html::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

body {
  margin: 0;
  background: #ffffff;
  color: var(--ink);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Stays pinned at the top while the page scrolls. */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 8;
  display: flex;
  justify-content: end;
  width: 100%;
  padding: 16px 24px;
  align-items: center;
  gap: 24px;
}

.header a {
  color: #fff;
  text-decoration: none;
  padding: 12px 32px;
}

/* email shown in the header instead of the old "About" link */
.header .email {
  color: #636363;
  font-size: 18px;
  white-space: nowrap;
}

/* Provides scroll length; the scene is fixed on top of it. */
#scroll-track {
  width: 1px;
}

/* ---------- Pinned scene ---------- */
.scene {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  perspective: 1200px;
}

/* ---------- Logo ---------- */
/* Logo + tagline stacked; the block is what flanks the nav buttons on desktop */
.logo-block {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.logo {
  position: relative;
  z-index: 2;
  font-size: var(--logo-size);
  font-weight: 500;
  line-height: 0.92;
  letter-spacing: -0.02em;
  transform-style: preserve-3d;
  user-select: none;
}

/* Tagline below the logo */
.tagline {
  margin: 24px 0 0;
  font-size: 33px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: #6A7282;
}

.line {
  white-space: nowrap;
}

.line2 {
  display: flex;
  /* align the "o" to the baseline of "St" and "ll" (the empty o-spacer's
     baseline is its bottom edge, so it sits on the text baseline) */
  align-items: baseline;
}

/* ---------- Hero (About | logo | Journal) ---------- */
.hero {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Nav buttons (About / Journal). Base style shared by both breakpoints. */
.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: #000;
  background: transparent;
  border: none;
  border-radius: 21px;
  font-size: 24px;
  font-weight: 500;
  letter-spacing: normal;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.btn:hover {
  background: #f8f8f8;
}

/* "Explore projects" is a mobile-only CTA (revealed in the max-width:768px
   block). Hidden by default so it never shows on desktop, where .cta uses
   display:contents and the "o" click drives the expansion instead. */
.btn-explore {
  display: none;
}

/* Primary "Let's talk" button, centered below the logo */
.btn-primary {
  background: #000;
  color: #fff;
  border-radius: 16px;
  font-size: 16px;
  padding: 0 32px;
}

.btn-primary:hover {
  background: #2d2d2d;
}

/* Text that blurs and recedes into the background on scroll */
.recede {
  display: inline-block;
  transform-origin: center;
  will-change: transform, filter, opacity;
}

/* space after the first "l" of "ll" — sized in em so the "ll" group width
   tracks the font size; tuned so the last "l" ends at the end of the "n". */
.l-first {
  margin-right: 0.118em;
}

/* ---------- Mini wordmark logo (back-to-home link on sub-pages) ----------
   Used by journal.html, imprint.html, privacy.html (which link styles.css).
   about.html keeps its own inline copy because it doesn't link styles.css. */
.logo-mini {
  display: inline-block;
  font-size: 22px;
  font-weight: 500;
  line-height: 0.92;
  letter-spacing: -0.02em;
  color: currentColor;
}

.logo-mini .line {
  display: block;
  white-space: nowrap;
}

.logo-mini .line2 {
  display: flex;
  /* "o" sits on the baseline of "St"/"ll", matching the home wordmark */
  align-items: baseline;
}

/* the "o" rendered as a bordered rounded box, like the home page.
   Height = Inter's x-height (~0.546em); width keeps the 2:1 aspect ratio. */
.logo-mini .o-mini {
  display: inline-block;
  width: 1.092em;
  height: 0.546em;
  margin: 0 0.04em;
  border: 0.1em solid currentColor;
  border-radius: 0.16em;
  box-sizing: border-box;
  /* fill stays inside the border so the "o" outline is preserved at rest */
  background-clip: padding-box;
  transition: border-color 0.3s ease;
}

/* On hover — the "o" outline fades to transparent and the gradient fills the
   full box (border-box clip), so what you see is just the gradient occupying
   the whole "o" footprint. Stops are placed at -20% and 120% (outside the
   visible 0–100% range) so the visible mint never reaches full saturation —
   same balance as the Figma design's to-[120%] detail (mint endpoint extends
   past the edge). Symmetric palindrome keeps the pan loop seamless. */
.logo-mini:hover .o-mini {
  border-color: transparent;
  background-clip: border-box;
  background-image: linear-gradient(.25turn, #A6FFE4 -20%, #2478FF 50%, #A6FFE4 120%);
  background-size: 200% 100%;
  animation: o-mini-gradient .4s linear infinite;
}

/* Project pages with a dark hero flip the wordmark white. Triggered by
   .on-dark on <header>; set server-side by header.php from the page's
   darkHero field. (Originally lived inline in project.html.) */
header.on-dark .logo-mini { color: #fff; }

@keyframes o-mini-gradient {
  from {
    background-position: 0% 50%;
  }

  to {
    background-position: 200% 50%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .logo-mini:hover .o-mini {
    animation: none;
    background-position: 50% 50%;
  }
}

/* Inline placeholder that reserves the slot of the "o" in the wordmark.
   The real .o element is positioned over it and animates independently.
   Height = the x-height of Inter (~0.546em); width is stretched slightly so the
   "ll" lines up directly under the "n" of "Julien". */
.o-spacer {
  display: inline-block;
  /* tuned so the first "l" after the "o" starts exactly at the "n"'s left edge */
  width: 1.142em;
  height: 0.546em;
  margin: 0 0.04em;
}

/* ---------- The expanding "o" ----------
   Resting-state hover interactions (handled here in CSS + a few mouse handlers
   in script.js, gated by JS to only fire while the "o" hasn't started expanding):
   - Hover scales to 1.1 with an ease-out curve.
   - Mouse follow: cursor offset from the "o" center drives --o-mx / --o-my.
   - Click triggers a smooth scrollTo into the expanded state.
   `@property` registers --o-scale so transition: --o-scale … animates ONLY the
   scale, leaving the mouse-follow translate to update per-frame without lag. */
@property --o-scale { syntax: "<number>"; inherits: false; initial-value: 1; }

.o {
  position: fixed;
  top: 0;
  left: 0;
  /* Above the .o-bg-overlay (z 9) so the expanded "o" sits on top of the
     frosted-glass layer that covers the resting-state chrome. */
  z-index: 11;
  /* border: var(--ring) solid var(--ink); */
  background: var(--bg);
  overflow: hidden;
  will-change: top, left, width, height, border-radius;
  /* JS sets top/left/width/height/border-radius every frame */
  cursor: pointer;
  transform: translate(var(--o-mx, 0px), var(--o-my, 0px)) scale(var(--o-scale));
  transform-origin: center center;
  /* Ease-in: slow start, faster end — feels less aggressive than the ease-out
     snap on the resting hover. */
  transition: --o-scale 0.3s ease-in-out;
}

/* Hover scale only applies while resting. Once .is-expanding is on the body
   (set by JS as soon as scroll passes the expand trigger), the hover effect is
   suppressed so the scale-up doesn't fight the scroll-driven layout. */
body:not(.o-is-expanding) .o:hover {
  --o-scale: 1.05;
}

/* While expanding, the JS positions the "o" directly (left/top/width/height)
   and the overlay + scrim are positioned to match — none of them carry the
   hover transform. So neutralize the transform entirely here. This wins over
   the .o rule by specificity, and (unlike resetting --o-mx/--o-my) it can't be
   defeated by the leftover inline custom props the mouse-follow handler sets —
   which is what caused the "o" to sit a few px off from the scrim/overlay until
   a mouseleave snapped it back. */
body.o-is-expanding .o {
  cursor: default;
  transform: none;
}

/* ---------- Expanded-state backdrop ----------
   A frosted-glass layer that sits BEHIND the expanded "o" and its overlay,
   covering the resting-state header (email + Let's talk), About/Journal
   buttons, and scroll hint, so they don't bleed through the projects view.
   Stays UNDER the footer (z 10) and the "o" itself (z 11). Pointer-events
   are on while shown so clicks on this layer fall through to the document
   click handler in script.js, which collapses the "o" back to rest. */
.o-bg-overlay {
  position: fixed;
  inset: 0;
  z-index: 9;
  background: rgba(255, 255, 255, 0.3);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

body.o-is-expanding .o-bg-overlay {
  opacity: 1;
  pointer-events: auto;
}

.track {
  position: absolute;
  inset: 0;
  /* Carousel axis is set by JS: horizontal (row) in the resting "o", vertical
     (column) in the expanded "o". Default = row for the resting preview. */
  display: flex;
  flex-direction: row;
  will-change: transform;
}

.slide {
  position: relative;
  /* each slide fills the "o" inner area */
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  background: var(--bg);
  overflow: hidden;
}

.slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* always visible so the looping preview shows inside the resting "o" */
  opacity: 1;
}

/* ---------- Overlay (indicator + headline), aligned to expanded "o" ---------- */
.o-overlay {
  position: fixed;
  /* Above .o (z 11) so the headline + indicator sit on top of the projects view. */
  z-index: 12;
  pointer-events: none;
  opacity: 0;
  will-change: opacity, top, left, width, height;
}

/* Vertical page indicator, inside the "o" on the right */
.indicator {
  position: absolute;
  top: 50%;
  right: clamp(16px, 2.2vw, 40px);
  transform: translateY(-50%);
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 1vw, 14px);
  mix-blend-mode: difference;
}

.indicator li {
  width: 2px;
  height: clamp(14px, 1.8vw, 26px);
  background: #ffffff;
  opacity: 0.35;
  border-radius: 2px;
  transition: opacity 0.35s ease, height 0.35s ease;
}

.indicator li.active {
  opacity: 1;
  height: clamp(22px, 3vw, 44px);
}

/* Gradient scrim that WRAPS the project title and extends to the bottom of the
   "o". The title sits inside it, padded from the bottom-left. */
.headline-scrim {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
  pointer-events: none;
  display: flex;
  align-items: flex-end;
  padding: 48px 48px;
  box-sizing: border-box;
}

/* On large / 4K viewports the "o" expands much bigger, so give the headline
   scrim more breathing room to sit nicely inside it. */
@media (min-width: 1921px) {
  .headline-scrim {
    padding: 72px 72px;
  }
}

/* Project headline: two columns — title (left) + "Go to project" button (right) */
.headline {
  width: 100%;
  color: #fff;
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  line-height: 1;
}

.headline-title {
  font-size: clamp(20px, 3.4vw, 52px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  transition: opacity 0.25s ease;
  flex: 1 1 auto;
  /* Cap the headline measure (~40 characters) so the sentence wraps to a
     tighter block instead of stretching across the whole expanded "o". */
  max-width: 30ch;
}

/* "Go to project" button — frosted glass. */
.headline-cta {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 64px;
  padding: 0 36px;
  color: #fff;
  font-size: 18px;
  font-weight: 500;
  text-decoration: none;
  border-radius: 21px;
  white-space: nowrap;
  pointer-events: auto;
  /* glass base */
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.25);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  transition: background-color 0.3s ease;
}

.headline-cta:hover {
  background: rgba(255, 255, 255, 0.18);
}

/* ---------- Scroll hint ----------
   "Explore projects" + a small down-arrow icon. Spec from Figma node 231:704:
   Inter 12px, letter-spacing 1px, uppercase, 12px gap between text and icon. */
.hint {
  position: fixed;
  left: 50%;
  bottom: clamp(20px, 4vh, 48px);
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.55;
  animation: bob 2.2s ease-in-out infinite;
  will-change: opacity, transform;
  /* fade out with a slight delay once the user scrolls (JS sets opacity) */
  transition: opacity 0.6s ease 0.35s;
}

/* The icon inherits its stroke color from the .hint text color (currentColor). */
.hint svg {
  display: block;
  flex-shrink: 0;
}

@keyframes bob {

  0%,
  100% {
    transform: translate(-50%, 0);
  }

  50% {
    transform: translate(-50%, 6px);
  }
}

/* Vertical-only bob for the in-flow (mobile) hint, which is centered by its
   flex parent rather than translateX(-50%). */
@keyframes bob-y {

  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(6px);
  }
}

/* ---------- Footer ---------- */
.footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Above the .o-bg-overlay (z 9) so the © + Privacy/Imprint links stay
     visible on top of the frosted-glass layer during the expanded state. */
  z-index: 10;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: clamp(14px, 2vw, 22px) clamp(18px, 3vw, 40px);
  font-size: 14px;
  letter-spacing: 0.02em;
  color: var(--footer-fg);
  pointer-events: none;
}

.footer span,
.footer a {
  pointer-events: auto;
}

/* Static variant: for scrolling templates (project, article, legal,
   journal). Flows at the end of the document instead of pinning to the
   viewport bottom — otherwise it would overlap page content. */
.footer--static {
  position: static;
  pointer-events: auto;
}

/* Dark variant: applied via the .theme-dark class (page-head-about.php
   defines the token remap). The About page uses it so the footer sits flush
   with the dark CV section; .theme-dark on the element also lets the header
   scroll observer flip the logo to white while the footer is in view. */
.footer.theme-dark {
  background: var(--bg-primary);
  color: var(--text-primary);
}
/* Copyright span + links both use the dark-theme foreground. The explicit
   `span` selector is needed to beat the mobile `.footer span` gray rule, which
   would otherwise leave the © text gray on the dark About footer. */
.footer.theme-dark span,
.footer.theme-dark a {
  color: var(--text-primary);
}

.footer a {
  color: var(--footer-fg);
  text-decoration: none;
  opacity: 1;
  transition: opacity 0.2s ease;
}

.footer a:hover {
  opacity: 0.7;
}

.privacy-imprint {
  display: flex;
  gap: 24px;
}

.privacy-imprint a {
  text-decoration: none;
}

/* On mobile the expanded "o" fills the viewport, so the footer sits over the
   project image and needs white text. On desktop the "o" is centered (80vw) and
   the footer stays on the white page below it, so it must keep its dark ink. */
@media (max-width: 768px) {

  .footer.on-expanded,
  .footer.on-expanded span,
  .footer.on-expanded a {
    color: #787878;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hint {
    animation: none;
  }
}

/* ---------- Desktop ---------- */
@media (min-width: 769px) {

  /* Logo bigger on desktop. 13.52rem (= 10.4rem * 1.3) is the floor, held up to
     ~1920px wide (11.25vw == 13.52rem at 1920px); beyond that it scales with the
     viewport so it grows on large / 4K displays, capped at 30rem. */
  :root {
    --logo-size: clamp(13.52rem, 11.25vw, 30rem);
  }

  /* Pagination stays VERTICAL on the right (the base .indicator style) on
     desktop — no horizontal override here. */

  /* Hero is a horizontal row: About | logo | Journal, with a gap between.
     Constrained to the viewport so the buttons flex-shrink to fit. */
  .hero {
    flex-direction: row;
    gap: 48px;
    width: 100%;
    max-width: 100vw;
    padding: 0 48px;
    box-sizing: border-box;
  }

  /* logo keeps its intrinsic size; only the buttons shrink */
  .logo {
    flex: 0 0 auto;
  }

  /* The .cta + .cta-row wrappers dissolve so About/Journal become direct flex
     children of .hero, flanking the logo. About is ordered before, Journal
     after the logo. (.btn-explore stays display:none from the base rule.) */
  .cta,
  .cta-row {
    display: contents;
  }

  /* Nav buttons (About/Journal): as tall as the logo, ideally as wide as the
     logo, but allowed to flex-shrink so logo + both buttons fit the viewport in
     one centered row (they become rectangular when space is tight). */
  .btn-about,
  .btn-journal {
    height: var(--logo-h, 220px);
    flex: 0 1 var(--logo-w, 360px);
    min-width: 0;
  }

  .btn-about {
    order: -1;
  }

  .btn-journal {
    order: 1;
  }

  /* Primary "Let's talk": centered below the logo, as wide as the logo,
     doesn't disturb the flex row */
  .btn-primary {
    padding: 0;
    z-index: 3;
  }


}

/* ---------- Mobile ---------- */
@media (max-width: 768px) {

  /* Body text at 18px on mobile */
  body {
    font-size: 18px;
  }

  /* Fallback logo size. JS (measure() in script.js) overrides --logo-size on
     mobile so the wordmark's widest line spans the viewport width minus 16px
     padding per side; this clamp only applies if that measurement hasn't run
     yet (e.g. before first paint). Height-based curve, clamped so it never gets
     tiny or exceeds the desktop 8rem. */
  :root {
    --logo-size: clamp(3.5rem, calc(18vh - 34px), 8rem);
  }

  .tagline {
    font-size: clamp(18px, 3.4vh, 28px);
  }

  .scene {
    align-items: start;
  }

  /* The hero is a full-height flex COLUMN that distributes its children with
     space-between: the logo-block sits at the TOP and the CTA at the BOTTOM,
     so they get the largest possible gap and never overlap — regardless of
     viewport height (iPhone SE … 14 Pro). 100dvh + safe-area insets keep the
     layout inside the browser's dynamic chrome (top/bottom bars, notch, home
     indicator) on Safari/Chrome/Edge/Firefox/Arc. */
  .hero {
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    height: 100dvh;
    margin-top: 0;
    box-sizing: border-box;
    /* The hero fills the whole viewport with the CTA sitting ~16px above the
       bottom edge; the footer lives just BELOW the fold (see .footer below), so
       no footer height is reserved here. */
    padding: calc(env(safe-area-inset-top) + 64px) 16px calc(env(safe-area-inset-bottom) + 16px);
  }

  /* DOM order is .cta then .logo-block; order puts the logo-block above the CTA.
     flex:1 makes it fill all the space above the (bottom-pinned) CTA, and
     justify-content:center vertically centers the logo + tagline within it. */
  .logo-block {
    order: 1;
    flex: 1;
    justify-content: center;
  }

  .header .email {
    font-size: 16px;
  }

  /* CTA: filled "Explore projects" button stacked over the About/Journal row,
     pinned to the BOTTOM of the hero flex column (in flow, not fixed —
     space-between handles the spacing). 8px gap between the button and the row. */
  .cta {
    order: 2;
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
  }

  /* About + Journal side by side, 8px gutter, each taking half the width. */
  .cta-row {
    display: flex;
    flex-direction: row;
    gap: 8px;
    width: 100%;
  }

  .btn {
    height: 48px;
    font-size: 18px;
  }

  /* "Explore projects": full-width, filled gray/950, 64px tall, 16px radius.
     Tapping it scrolls into the expanded state (handled in script.js). */
  .btn-explore {
    display: flex;
    height: 64px;
    width: 100%;
    border-radius: 16px;
    background: #030712;
    color: #fff;
  }

  .btn-explore:hover {
    background: #16181c;
  }

  /* About / Journal: filled gray/100, no outline, 64px tall, 16px radius,
     splitting the row evenly. */
  .btn-about,
  .btn-journal {
    flex: 1 1 0;
    min-width: 0;
    height: 64px;
    border-radius: 16px;
    background: #f3f4f6;
    color: #030712;
    border: none;
  }

  .btn-about:hover,
  .btn-journal:hover {
    background: #e5e7eb;
  }

  /* "Let's talk" in the header: no fill, just a 1px gray/950 outline, 16px
     radius. Scoped to .header so it beats the `.header a { color:#fff }` rule's
     specificity. */
  .header .btn-primary {
    background: transparent;
    color: #030712;
    border: 1px solid #030712;
    border-radius: 16px;
    font-size: 16px;
  }

  .headline {
    flex-direction: column;
    align-items: start;
  }

  .headline-cta {
    width: 100%;
  }

  /* Title sits above the horizontal pagination bar: bottom padding clears the
     indicator (bar at ~56px from bottom) plus a gap, and the footer below it. */
  .headline-scrim {
    padding: 80px 16px 64px;
  }

  /* Project title font size on mobile */
  .headline-title {
    font-size: 2rem;
  }

  /* Pagination stays VERTICAL on the right on mobile too — same as desktop
     (the base .indicator style); no horizontal override here. */

  /* Footer stays at the BOTTOM as a single row: copyright on the left,
     Privacy/Imprint links on the right (justify-content: space-between from
     the base rule). */
  .footer {
    top: auto;
    bottom: 0;
    flex-direction: row;
    align-items: center;
    gap: 12px;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
  }

  /* Home only: the PINNED footer (no .footer--static) sits just below the fold
     at rest — translated down by its own height — and slides up into view once
     the "o" is (mostly) expanded (JS adds .on-expanded at e>0.5). Static footers
     on other templates (About, projects, journal, legal) must NOT be
     transformed, or a white gap opens above them. */
  .footer:not(.footer--static) {
    transform: translateY(100%);
    transition: transform 0.35s ease;
  }

  .footer:not(.footer--static).on-expanded {
    transform: translateY(0);
  }

  .privacy-imprint {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    text-decoration: none;
    gap: 24px;
  }

  /* The footer sits over the WHITE page at rest and over the project images
     once the "o" expands. `mix-blend-mode: difference` was unreliable here (it
     composited white-on-white as invisible). Use dark ink text — clearly
     legible on the white landing page — with a soft white glow so it also
     reads over the (often darker) project images when the "o" is expanded. */
  .footer span,
  .footer a {
    color: var(--footer-fg);
    mix-blend-mode: normal;
  }

  .footer a {
    opacity: 1;
  }

  .header {
    top: calc(16px + env(safe-area-inset-top));
    bottom: auto;
    justify-content: center;
    padding: 0 16px;
    transition: opacity 0.3s ease;
  }

  /* While the "o" is expanded, the header stays visible but renders BEHIND the
     .o-bg-overlay (z 9 > .header z 8), so the frosted-glass layer blurs it
     along with the About/Journal buttons. Pointer events are off so clicks
     on the email or "Let's talk" don't fire behind the overlay. */
  .header.on-expanded {
    pointer-events: none;
  }

  /* Logo becomes a column with its two lines horizontally centered. */
  .logo {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  /* No scroll hint on mobile — the filled "Explore projects" button is the
     entry point into the expanded state instead. (Desktop keeps the hint.) */
  .hint {
    display: none;
  }
}

/* Below the xl breakpoint (1281px, matching Figma): hide the header entirely
   once the "o" expands. JS toggles .on-expanded on the .header when the scroll
   passes ~5% of the expand range, so this fades out as expansion begins. At
   xl+ the header stays visible (the .header.on-expanded rule above only
   disables pointer events). */
@media (max-width: 1280px) {
  .header {
    transition: opacity 0.3s ease;
  }
  .header.on-expanded {
    opacity: 0;
    pointer-events: none;
  }
}