@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&display=swap');
/* Urth's own "MAGAZINE" wordmark is set in Domaine — a commercial Klim
   Type Foundry face that can't be licensed/embedded here. Playfair
   Display is the closest freely-licensed match for that elegant,
   high-contrast editorial serif character, used only for the Photo
   Stories wordmark (.stories-wordmark) — not applied anywhere else on
   the site. */
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700;900&display=swap');

/* Smooth cross-page navigation, per geenie.framer.website — this is a
   static multi-page site (separate HTML files, real navigations), not a
   single-page app, so a hand-built route-transition system isn't an
   option without a much bigger rewrite. Originally tried the native CSS
   View Transitions API for this (a single @view-transition declaration),
   but cross-document view transitions are Chromium-only so far — nothing
   visible happens in Safari or Firefox. Replaced with a plain fade
   handled in header.js (fadeBody class below) that works identically in
   every browser: fade the current page out, then navigate; fade the new
   page in on load. */
body {
  opacity: 1;
  transition: opacity 0.22s ease;
}

body.fade-out {
  opacity: 0;
}

@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Hairline.otf') format('opentype');
  font-weight: 100;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-HairlineItalic.otf') format('opentype');
  font-weight: 100;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Thin.otf') format('opentype');
  font-weight: 200;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-ThinItalic.otf') format('opentype');
  font-weight: 200;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Extralight.otf') format('opentype');
  font-weight: 300;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-ExtralightItalic.otf') format('opentype');
  font-weight: 300;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Light.otf') format('opentype');
  font-weight: 350;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-LightItalic.otf') format('opentype');
  font-weight: 350;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Regular.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Italic.otf') format('opentype');
  font-weight: 400;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Medium.otf') format('opentype');
  font-weight: 500;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-MediumItalic.otf') format('opentype');
  font-weight: 500;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Bold.otf') format('opentype');
  font-weight: 700;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-BoldItalic.otf') format('opentype');
  font-weight: 700;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Extrabold.otf') format('opentype');
  font-weight: 800;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-ExtraboldItalic.otf') format('opentype');
  font-weight: 800;
  font-style: italic;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-Black.otf') format('opentype');
  font-weight: 900;
  font-style: normal;
}
@font-face {
  font-family: "Ekster";
  src: url('fonts/Ekster/Ekster-BlackItalic.otf') format('opentype');
  font-weight: 900;
  font-style: italic;
}

/* Dark only — the site used to also offer a light theme (toggled from the
   notch, persisted in localStorage, applied via a [data-theme] attribute),
   dropped along with the notch itself. These tokens are what's left of
   that system: still worth keeping as named variables rather than
   inlining every value, since --fg-rgb alone still drives virtually every
   text/icon color in this file as an rgba() at some opacity. */
:root {
  color-scheme: dark;
  --font: "Quicksand", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-title: "Ekster", "Quicksand", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --bg: #0a0a0a;
  --fg-rgb: 255, 255, 255;
  --pill-bg-rgb: 28, 28, 28;
  --pill-bg-alpha: 0.5;
  --border-rgb: 255, 255, 255;
  --indicator-bg: rgba(255, 255, 255, 0.16);
  --accent-bg-rgb: 255, 255, 255;
  --accent-text-rgb: 10, 10, 10;

  /* Opportunity board status pipeline only — see .board-status-* */
  --status-surfaced-rgb: 148, 163, 184;
  --status-interested-rgb: 96, 165, 250;
  --status-pitched-rgb: 251, 191, 36;
  --status-booked-rgb: 74, 222, 128;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  background: var(--bg);
  color: rgb(var(--fg-rgb));
  font-family: var(--font);
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  /* min-height (a floor, not a fixed box) is much less prone to the
     mobile-Safari gap a fixed height: 100vh hit — but still worth
     tracking the real viewport rather than the older, less reliable
     calculation. See .landing-hero's own comment. */
  min-height: 100vh;
  min-height: 100dvh;
  transition: background-color 0.25s ease, color 0.25s ease;
}

/* Push the footer to the bottom of the viewport when the page content
   (e.g. a filtered grid with only one or two tiles) is shorter than the
   screen, instead of leaving it floating right under the content. */
main {
  flex: 1 0 auto;
  width: 100%;
}

/* Header — see .classic-header further down for the actual header
   markup/styles (one shared pattern, used by every page) and
   classic-header.js for its hamburger-menu behavior. This is what
   replaced the old expandable "notch" — logo left, plain-text links
   right, sitting transparent on top of the page (not fixed — it scrolls
   away with the content, matching visura.framer.website's own header). */

/* Same material as the notch — dark translucent card, blur, subtle
   border, 16px outer radius with an 8px nested radius for the segments
   inside, and the same rgba(255,255,255,0.16) "active" overlay the notch
   uses for its own active Portfolio/Info link. */
.filter-tabs {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  width: fit-content;
  margin: 10px 6px 18px;
  padding: 6px;
  border-radius: 16px;
  background: rgba(var(--pill-bg-rgb), var(--pill-bg-alpha));
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(var(--border-rgb), 0.08);
  font-family: var(--font);
}

.filter-tabs.hidden {
  display: none;
}

/* Category filtering isn't essential on a small screen where the grid is
   already a single column and scrolling through everything is quick — the
   tabs mostly just ate space here. Grid still shows every project
   unfiltered; this only removes the control, not the underlying
   filtering (still there for desktop/tablet). */
@media (max-width: 640px) {
  .filter-tabs {
    display: none;
  }
}

/* Position and shape split across two elements (wrap handles position,
   inner pill handles shape) so a translate transition and the squash
   keyframe animation (.morphing) don't fight over the `transform`
   property — built and positioned by updateFilterIndicator in board.js. */
.filter-tabs-indicator-wrap {
  position: absolute;
  /* top/left stay at 0 — board.js's updateFilterIndicator already positions
     this with translate(activeTab.offsetLeft, activeTab.offsetTop), and
     offsetTop/offsetLeft are measured from .filter-tabs's own padding edge
     already, so a static top/left inset here on top of that would just
     double-count the padding and push the pill off from the label. */
  top: 0;
  left: 0;
  transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.4, 1), width 0.5s cubic-bezier(0.34, 1.4, 0.4, 1),
    height 0.5s cubic-bezier(0.34, 1.4, 0.4, 1);
  pointer-events: none;
  z-index: 0;
}

.filter-tabs-indicator {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  background: var(--indicator-bg);
  transform-origin: center;
}

.filter-tabs-indicator.morphing {
  animation: liquidSquash 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .filter-tabs-indicator.morphing {
    animation: none;
  }
}

.filter-tab {
  position: relative;
  z-index: 1;
  background: none;
  border: none;
  border-radius: 8px;
  color: rgba(var(--fg-rgb), 0.7);
  font-family: var(--font);
  font-weight: 500;
  font-size: 12px;
  padding: 8px 14px;
  cursor: pointer;
  transition: color 0.2s ease;
}

.filter-tab:hover {
  color: rgb(var(--fg-rgb));
}

.filter-tab.active {
  color: rgb(var(--fg-rgb));
}

/* Grid tile layout modeled on monologue.framer.website/works/grid,
   measured directly: 4:3 image tiles (not 3:2) with real breathing room
   between them, a small translucent category pill overlaid on the image
   itself, and an uppercase bold title paired with a muted, normal-case
   type — reference computed styles: title ~18px/weight 500/uppercase/white,
   type 16px/weight 500/white at 50% opacity, badge 12px/rgba(255,255,255,0.1)
   background/blur(30px)/5px radius/~9px 13px padding. Reused by the
   homepage's own grid (.landing-albums-grid) via this same .work-tile*
   family, not a grid of its own. */
.work-tile {
  display: block;
  text-decoration: none;
  color: inherit;
}

.work-tile-image {
  aspect-ratio: 16 / 9;
  background-size: cover;
  background-position: center;
  overflow: hidden;
  position: relative;
}

.work-tile-image::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: inherit;
  background-size: cover;
  background-position: center;
  transform: scale(1);
  transition: transform 0.6s ease;
}

.work-tile:hover .work-tile-image::after {
  transform: scale(1.06);
}

.work-tile-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 1;
  padding: 8px 12px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  color: #ffffff;
}

/* "new" tag on the most recent project — solid white/black rather than
   the category badge's translucent-on-dark treatment (it's meant to pop,
   not blend in), but otherwise the exact same typography as
   .work-tile-badge (font, size, weight, lowercase) and the same small
   rounded-rect radius as the notch and its buttons rather than a full
   stadium pill, so it still reads as part of the same design language. */
.work-tile-new {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  padding: 8px 12px;
  border-radius: 5px;
  background: #ffffff;
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  color: #0a0a0a;
}

.work-tile-caption {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 16px 4px 24px;
  font-family: var(--font);
}

.work-tile-title {
  font-family: var(--font-title);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: rgb(var(--fg-rgb));
}

/* liquidSquash — the "liquid glass" squash-and-stretch keyframe reused by
   several sliding-pill indicators across the site (.filter-tabs-indicator,
   .board-sort-indicator, .pitch-mode-indicator): real Liquid Glass
   elements don't just slide, they squash toward the direction of travel
   and overshoot before settling, like a droplet. */
@keyframes liquidSquash {
  0% {
    transform: scaleX(1) scaleY(1);
  }
  28% {
    transform: scaleX(1.22) scaleY(0.8);
  }
  58% {
    transform: scaleX(0.92) scaleY(1.08);
  }
  82% {
    transform: scaleX(1.04) scaleY(0.98);
  }
  100% {
    transform: scaleX(1) scaleY(1);
  }
}

/* Homepage hero — see the "Homepage hero + Albums grid" section further
   down for .landing-hero itself. .work-slider-bg (the crossfading cover
   layers) is the one piece kept from the old Slider design, reused
   as-is by the new hero. */
.work-slider-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 0.7s ease;
}

.work-slider-bg.visible {
  opacity: 1;
}

.work-slider-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0) 45%);
}

/* Homepage hero + Albums grid — one continuously scrolling page instead
   of the old Slider/Grid toggle, modeled on visura.framer.website
   (full-bleed hero, "scroll to explore") and photographers.framer.website
   (hero gives way to a project grid on scroll). .landing-hero reuses
   .work-slider-bg as-is for the crossfading cover — same material, just
   a plain full-bleed hero rather than a wheel/touch-driven carousel:
   trapping the scroll gesture to step through covers would mean the
   grid below could never be scrolled to. */
.landing-hero {
  position: relative;
  width: 100%;
  /* 100dvh (dynamic viewport height) tracks mobile Safari's address bar
     showing/hiding live; 100vh is the fallback for browsers that don't
     support it. */
  height: 100vh;
  height: 100dvh;
  min-height: 480px;
  overflow: hidden;
  cursor: pointer;
}

/* A tighter, dedicated scrim just for the divider's own footprint —
   .work-slider-bg already carries its own broader top-of-image gradient
   (see its ::after), but that one fades out well above where the divider
   sits, so this adds a second, shorter one sitting just above the
   crossfading backgrounds (z-index 1) and below the divider text
   (z-index 2), for legibility regardless of how bright the cover is. */
.landing-hero-scrim {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  height: 160px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.65), rgba(0, 0, 0, 0) 100%);
  pointer-events: none;
}

/* Bottom divider bar — per photographers.framer.website: a thin rule
   pinned to the very bottom of the hero, with a 3-up row underneath it:
   brand name, a scroll hint, and a CTA link. */
.landing-hero-divider {
  position: absolute;
  left: 28px;
  right: 28px;
  bottom: 28px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.2);
}

.landing-hero-divider-item {
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  text-decoration: none;
  white-space: nowrap;
}

.landing-hero-divider-center {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

/* Same underline-sweep hover as .classic-nav-links a in the header —
   a bottom rule that scales in from the left via the scaleX(0)
   transform-origin flip trick, rather than a plain opacity fade. */
.landing-hero-divider-link {
  position: relative;
  color: #ffffff;
  transition: color 0.25s ease;
}

.landing-hero-divider-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.3s ease;
}

.landing-hero-divider-link:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* "Albums" — inspected directly from photographers.framer.website's own
   DOM: a position:sticky title (pinned to the top of the viewport,
   blurred), painted behind the grid — which is just a plain normal-flow
   sibling right after it in the markup, painting on top wherever the two
   overlap. No JS needed for the pin: position:sticky keeps the title
   glued to top:0 for as long as .landing-albums-section still has grid
   content left below the current scroll position, so the tiles scroll up
   from the bottom of the viewport and progressively cover the stationary
   title as the user scrolls, matching the reference's own behavior — the
   sticky box's height (100vh) plus the grid's own height is what
   .landing-albums-section's total scrollable height ends up being,
   automatically. */
.landing-albums-section {
  position: relative;
}

.landing-albums-sticky {
  position: sticky;
  top: 0;
  z-index: 0;
  height: 100vh;
  height: 100dvh;
  display: flex;
  /* Above dead-center, but not pinned right up near the top either. */
  align-items: flex-start;
  padding-top: 30vh;
  justify-content: center;
  pointer-events: none;
}

/* Same font as the reference's own title (Clash Display, loaded in
   index.html's <head> via Fontshare) — full solid white, same as
   .work-tile-title on the cards below it, rather than the reference's
   own orange glow (per the request that specifically asked to keep this
   page's text color) or a dimmed/translucent white (read as too faint
   next to the cards' own bright titles). Mixed case ("Albums"), not
   uppercase — that's how the reference actually sets it. font-size/
   weight measured directly off the reference (90px at ~1044px viewport,
   120px at 1600px, weight 400 throughout — not the 600 an earlier pass
   used, which read too bold). Its own slide+fade in is the shared
   .reveal class (see the markup and reveal.js) rather than a bespoke
   scroll-scrubbed animation — see home.js's own comment for why that
   reads far more clearly as an actual "animating in" moment. Blur is
   still scroll-driven and separate (also home.js): sharp until the grid
   starts covering it, then picks up blur from there. */
.landing-albums-title {
  font-family: "Clash Display", var(--font-title);
  font-size: clamp(48px, 8vw, 120px);
  font-weight: 400;
  letter-spacing: -0.01em;
  color: rgb(var(--fg-rgb));
  white-space: nowrap;
  will-change: filter, transform;
}

/* The grid itself: kept simple on request after an earlier hand-placed
   "collage" pass (different ratios, freeform drag-positioned tiles, its
   own landing-edit.html editor) turned out to be more than wanted — two
   equal 16:9 cards per row, plain CSS grid, no per-tile JSON/editor.
   Full-bleed on request (was capped at max-width: 1000px with side
   padding, like the reference — too much empty space on either side for
   this site). */
.landing-albums-grid {
  position: relative;
  z-index: 1;
  /* Pulled up so the cards start rising into view sooner — otherwise
     they only begin appearing once the user has scrolled through the
     sticky title's own full 100vh reserved height. */
  margin-top: -18vh;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 28px;
  width: 100%;
  padding: 0 28px;
}

.landing-albums-grid .work-tile-image {
  aspect-ratio: 16 / 9;
}

/* Arrow instead of a text type-tag next to the title, matching the
   reference's caption row exactly (title left, small arrow right) rather
   than this site's usual title+category pairing. The image hover-zoom
   itself (.work-tile-image::after) is untouched.
   An inline SVG, not the Unicode ↗ character it started as — that
   character has an emoji presentation on iOS/mobile, which rendered as a
   small colored emoji glyph instead of a plain line icon. stroke:
   currentColor so it still follows the same color transition below. */
.work-tile-arrow {
  display: inline-block;
  width: 18px;
  height: 18px;
  color: rgba(var(--fg-rgb), 0.4);
  transition: color 0.25s ease;
}

.work-tile:hover .work-tile-arrow {
  color: rgb(var(--fg-rgb));
}

@media (max-width: 640px) {
  .landing-hero-divider {
    left: 20px;
    right: 20px;
    bottom: 20px;
  }

  .landing-hero-divider-center {
    display: none;
  }

  .landing-albums-grid {
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 0 20px;
  }
}

/* Info page */

/* Editorial page layout — a two-column "eyebrow + big heading" left rail
   paired with a wider content column, repeated per section, modeled on
   visura.framer.website/about (and shared as-is by the Contact page's
   own get-in-touch section). .classic-header sits on top of it (position:
   fixed, hidden except at the very top of the page — see .is-hidden)
   with its own z-index, so .editorial-hero can safely run flush to the top of the page (0 top
   padding) and sit underneath it, the same way
   that reference's nav overlays its own hero photo. */
.editorial-page {
  display: flex;
  flex-direction: column;
}

.editorial-hero {
  width: 100%;
  height: 80vh;
  height: 80dvh;
  min-height: 420px;
  background-image: url('images/profile/martijn.jpg');
  background-size: cover;
  background-position: center;
}

.editorial-section {
  display: flex;
  gap: 64px;
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
  padding: 90px 40px;
}

/* Tighter gap between stacked sections (e.g. Who Am I -> What I Do on the
   About page) — only trims the top padding of a section that follows
   another one, so the hero's own breathing room above the first section
   (and Contact's single section) stay untouched. */
.editorial-section + .editorial-section {
  padding-top: 30px;
}

.editorial-label {
  flex: 0 0 300px;
}

.editorial-eyebrow {
  display: block;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.45);
  margin-bottom: 10px;
}

.editorial-heading {
  font-family: var(--font-title);
  font-size: 34px;
  font-weight: 500;
  text-transform: uppercase;
  line-height: 1.15;
}

.editorial-content {
  flex: 1;
  min-width: 0;
  max-width: 560px;
}

.about-bio {
  font-size: 17px;
  line-height: 1.7;
  color: rgba(var(--fg-rgb), 0.85);
  margin-bottom: 40px;
}

.editorial-meta {
  display: flex;
  gap: 48px;
}

.editorial-meta-block {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.editorial-meta-block h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: rgba(var(--fg-rgb), 0.4);
  font-weight: 500;
  margin-bottom: 6px;
}

.editorial-meta-block a {
  position: relative;
  width: fit-content;
  color: rgba(var(--fg-rgb), 0.85);
  text-decoration: none;
  font-size: 15px;
}

.editorial-meta-block a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.3s ease;
}

.editorial-meta-block a:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

.editorial-meta-block a:hover {
  color: rgb(var(--fg-rgb));
}

.about-skill {
  margin-bottom: 32px;
}

.about-skill:last-child {
  margin-bottom: 0;
}

.about-skill h3 {
  font-family: var(--font-title);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 8px;
}

.about-skill p {
  font-size: 15px;
  line-height: 1.7;
  color: rgba(var(--fg-rgb), 0.7);
}

/* Event-genre pills on the About page — same small uppercase-tag language
   as .story-card-tag, just a touch larger since these stand alone rather
   than sitting under a title. */
.genre-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.genre-tag {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 8px 14px;
  border-radius: 6px;
  background: rgba(var(--fg-rgb), 0.08);
  color: rgba(var(--fg-rgb), 0.75);
}

@media (max-width: 640px) {
  .editorial-hero {
    height: 50vh;
    height: 50dvh;
    min-height: 320px;
  }

  .editorial-section {
    flex-direction: column;
    gap: 24px;
    padding: 56px 24px;
  }

  .editorial-section + .editorial-section {
    padding-top: 20px;
  }

  .editorial-label {
    flex-basis: auto;
  }

  .editorial-meta {
    flex-direction: column;
    gap: 20px;
  }
}

/* Scroll-reveal — reveal.js toggles .is-visible via IntersectionObserver
   once each block enters the viewport (one-time, not a toggle back out).
   Shared across every page that opts a block in via class="reveal" (the
   About and Contact pages so far). The 2nd/3rd child in an
   .editorial-content gets a small extra delay so a row doesn't fade in as
   one flat block — this covers the bio/contact pair and the three
   stacked skills on the About page, and the form fields on Contact,
   since all of them are just ordered children of that same class. */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1), transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.editorial-content > .reveal:nth-child(2) {
  transition-delay: 0.12s;
}

.editorial-content > .reveal:nth-child(3) {
  transition-delay: 0.24s;
}

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

/* Contact page — reuses the .editorial-* layout above, but this page has
   no hero photo, so its first section needs its own top clearance for
   .classic-header instead of inheriting it from .editorial-hero sitting
   above. */
.editorial-page.no-hero .editorial-section:first-child {
  padding-top: 160px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.contact-form input,
.contact-form textarea {
  width: 100%;
  font-family: var(--font);
  font-size: 15px;
  color: rgb(var(--fg-rgb));
  background: rgba(var(--fg-rgb), 0.06);
  border: 1px solid transparent;
  border-radius: 10px;
  padding: 14px 16px;
  resize: none;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: rgba(var(--fg-rgb), 0.4);
}

.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
  border-color: rgba(var(--fg-rgb), 0.3);
  background: rgba(var(--fg-rgb), 0.09);
}

.contact-submit {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgb(var(--accent-text-rgb));
  background: rgb(var(--accent-bg-rgb));
  border: none;
  border-radius: 10px;
  padding: 15px;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.contact-submit:hover {
  opacity: 0.85;
}

.contact-submit:disabled {
  opacity: 0.6;
  cursor: default;
}

.contact-status {
  font-size: 13px;
  color: rgba(var(--fg-rgb), 0.6);
  min-height: 16px;
}

@media (max-width: 640px) {
  .editorial-page.no-hero .editorial-section:first-child {
    padding-top: 130px;
  }
}

/* The site's real header, replacing the old expandable notch — Visura's
   own homepage header (logo left, plain-text links right, sitting
   directly on the hero) combined with this site's own established
   language: the exact glass-pill material the notch used to use
   (--pill-bg-rgb/--pill-bg-alpha + the same blur/saturate recipe), now
   applied to .classic-mobile-menu below rather than the resting bar
   itself, and the same underline-sweep hover as .nav-social used to
   have, on the nav-links directly rather than a filled pill behind
   them. Text is fixed white (not tied to --fg-rgb) since, like the
   reference, this sits over a photo rather than the page background,
   and needs to stay legible over it regardless of the rest of the site's
   light/dark toggle. Markup is static and identical on every page (see
   any page's own <header class="classic-header">); classic-header.js
   only handles the hamburger menu and the hide-on-scroll behavior. */
.classic-header {
  /* Fixed, not absolute: the header now hides itself entirely on any
     scroll and only slides back once the page is scrolled all the way
     back to the top (see .is-hidden and classic-header.js) — fixed is
     what lets it do that reliably regardless of where in the document
     the user currently is. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Above .classic-mobile-menu (z-index: 50) so the burger (now showing
     an X) stays clickable/visible while the menu is open, and above real
     pages' own floating chrome it now sits on top of once the real notch
     is hidden. */
  z-index: 51;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 28px 48px;
  padding-top: calc(28px + env(safe-area-inset-top));
  /* Checked the actual reference again: its top bar stays fully
     transparent in every state — hover, scroll, always — the only thing
     that ever goes solid is the full-screen mobile menu (see
     .classic-mobile-menu below). So this keeps only a light touch of the
     notch's own glass material on hover, well short of the notch's own
     0.5/0.75 alpha, specifically so the bar still reads as clearly
     see-through rather than settling into a solid band. backdrop-filter
     is genuinely absent at rest (not blur(0px)/saturate(100%)) — a
     "no-op" filter still applies its saturate() to whatever's behind the
     bar, which was very visibly tinting cover photos even with 0 blur. */
  background: rgba(var(--pill-bg-rgb), 0);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0);
  transition: background-color 0.4s ease, backdrop-filter 0.4s ease, border-color 0.4s ease, transform 0.35s ease;
}

.classic-header:hover {
  background: rgba(var(--pill-bg-rgb), 0.22);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

/* Hidden the instant the page scrolls away from the very top, and only
   slid back once scrollY returns to 0 — classic-header.js toggles this
   on scroll, re-checking window.scrollY directly rather than caching
   anything, so it can't go stale. */
.classic-header.is-hidden {
  transform: translateY(-100%);
}

@media (prefers-reduced-motion: reduce) {
  .classic-header {
    transition: background-color 0.4s ease, backdrop-filter 0.4s ease, border-color 0.4s ease;
  }
}

.classic-header-name {
  font-family: var(--font-title);
  font-size: 17px;
  font-weight: 500;
  color: #fff;
  text-decoration: none;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35);
}

.classic-nav-links {
  position: relative;
  display: flex;
  gap: 28px;
}

/* Same underline-sweep hover as .nav-social (the Instagram/email links in
   the current notch) — brighten + a bottom bar that sweeps in from the
   left via the scaleX(0)-transform-origin-flip trick, not a filled pill. */
.classic-nav-links a {
  position: relative;
  width: fit-content;
  padding: 4px 0;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.8);
  text-decoration: none;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35);
  transition: color 0.25s ease;
}

.classic-nav-links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.3s ease;
}

.classic-nav-links a:hover {
  color: #fff;
}

.classic-nav-links a:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* The current page's own link — brightened like :hover, plus a
   permanently-shown (not hover-only) underline at half strength, so it
   reads as "you are here" without needing to be pointed at. Hovering it
   still sweeps to full opacity like any other link. */
.classic-nav-links a.active {
  color: #fff;
}

.classic-nav-links a.active::after {
  transform: scaleX(1);
  transform-origin: left;
  opacity: 0.5;
}

.classic-nav-links a.active:hover::after {
  opacity: 1;
}

/* Groups the links/theme toggle/burger together on the right side of the
   bar, as one flex row — lets the 640px breakpoint hide just the links
   without disturbing the toggle/burger's own spacing next to them. */
.classic-header-right {
  display: flex;
  align-items: center;
  gap: 28px;
}

/* Hamburger — hidden until the 640px breakpoint below swaps it in for
   .classic-nav-links, per visura.framer.website's own behavior at
   narrower widths. Two bars that rotate into an X (see
   html.classic-menu-open below) rather than a three-bar icon, since a
   third bar sitting exactly between the other two has nowhere clean to
   go on open — Visura's own icon is a two-bar hamburger for the same reason. */
.classic-header-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  z-index: 1;
}

.classic-header-burger span {
  display: block;
  width: 100%;
  height: 1.5px;
  background: #fff;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.35);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
}

html.classic-menu-open .classic-header-burger span:first-child {
  transform: translateY(3.75px) rotate(45deg);
}

html.classic-menu-open .classic-header-burger span:last-child {
  transform: translateY(-3.75px) rotate(-45deg);
}

/* Full-screen menu — the one place this header actually does go solid
   (confirmed on the reference: its mobile menu panel is fully opaque
   black, unlike the top bar itself, which stays transparent even on
   hover). Fixed dark, same as the top bar's own fixed-white text (see
   .classic-header-name's comment) rather than the site's light/dark
   tokens — this header is styled independently of that toggle throughout,
   since the reference it's modeled on has no light mode of its own, and
   a themed panel here would risk white-on-white if a visitor arrived
   with light mode already on from elsewhere on the site. */
.classic-mobile-menu {
  position: fixed;
  inset: 0;
  /* Above ordinary page chrome so it fully covers the page underneath
     when open, not just visually sits near the top of it; below actual
     content modals (.lightbox at 100, .board-modal-overlay at 200) since
     those taking priority in the rare
     case both were somehow open is the safer default. */
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 32px;
  background: #0a0a0a;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

html.classic-menu-open .classic-mobile-menu {
  opacity: 1;
  pointer-events: auto;
}

.classic-mobile-menu a {
  position: relative;
  font-family: var(--font-title);
  font-size: 26px;
  font-weight: 500;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
  text-decoration: none;
  transition: color 0.25s ease;
}

.classic-mobile-menu a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.3s ease;
}

.classic-mobile-menu a:hover {
  color: #fff;
}

.classic-mobile-menu a:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* The page itself shouldn't scroll behind an open full-screen menu. */
html.classic-menu-open {
  overflow: hidden;
}

@media (max-width: 640px) {
  .classic-header {
    padding: 20px 20px;
    padding-top: calc(20px + env(safe-area-inset-top));
  }

  .classic-header-name {
    font-size: 14px;
  }

  .classic-nav-links {
    display: none;
  }

  .classic-header-right {
    gap: 14px;
  }

  .classic-header-burger {
    display: flex;
  }
}

/* Gallery page */

.gallery-page {
  /* No bottom padding — .project-nav (below the grid) now owns that
     spacing itself via its own margin, so a separate 100px here was just
     stacking on top of it and doubling the gap before the footer. */
  padding: 144px 0 0;
}

@media (max-width: 640px) {
  .gallery-page {
    padding-top: 190px;
  }
}

.gallery-title {
  font-family: var(--font-title);
  font-size: clamp(40px, 8vw, 110px);
  font-weight: 700;
  text-transform: none;
  letter-spacing: -0.02em;
  text-align: center;
  color: rgb(var(--fg-rgb));
  margin: 0 24px 48px;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 4px;
  padding: 0 4px;
}

.gallery-item {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  border: none;
  padding: 0;
  border-radius: 3px;
  background-size: cover;
  background-position: center;
  overflow: hidden;
  cursor: pointer;
}

.gallery-item::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.15);
  opacity: 0;
  transition: opacity 0.35s ease;
}

.gallery-item::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: scale(1);
  transition: transform 0.5s ease;
  background-image: inherit;
  background-size: cover;
  background-position: center;
}

.gallery-item:hover::before {
  opacity: 1;
}

.gallery-item:hover::after {
  transform: scale(1.06);
}

@media (max-width: 900px) {
  .gallery-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .gallery-grid {
    grid-template-columns: 1fr;
  }
}

/* Prev/Next project switcher, per geenie.framer.website/work/wars's own
   bottom-of-project navigation — two independent pill buttons (its own
   reference has each as its own separate card, not one joined segmented
   control) rather than sharing a single outer pill. Sits in normal flow
   below the grid, not position: fixed, so it doesn't compete with the
   auto-expanding contact CTA the notch already becomes down here — same
   card material as the notch either way, so it still reads as part of
   the same floating-UI family. Switching pages
   already gets the site's standard fade transition for free (see
   header.js's click delegation), since these are just regular links to
   gallery.html. Margins below are deliberately uneven (32px/4px) so the
   VISUAL gap reads even on both sides once the footer's own 28px top
   padding is added to the bottom one (4 + 28 = 32, matching the top). */
.project-nav {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin: 32px 0 4px;
}

.project-nav-btn {
  min-width: 96px;
  text-align: center;
  padding: 12px 20px;
  border-radius: 12px;
  color: rgba(var(--fg-rgb), 0.7);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: none;
  text-decoration: none;
  background: rgba(var(--pill-bg-rgb), var(--pill-bg-alpha));
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(var(--border-rgb), 0.08);
  transition: background-color 0.25s ease, color 0.25s ease;
}

.project-nav-btn:hover {
  color: rgb(var(--fg-rgb));
  background: var(--indicator-bg);
}

/* Lightbox */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(5, 5, 5, 0.94);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.lightbox.visible {
  opacity: 1;
  pointer-events: auto;
}

.lightbox-image {
  position: absolute;
  inset: 40px 84px;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  pointer-events: none;
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.6);
  font-family: var(--font);
  cursor: pointer;
  transition: color 0.2s ease;
  z-index: 2;
}

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  color: #ffffff;
}

.lightbox-close {
  top: 20px;
  right: 24px;
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.lightbox-prev,
.lightbox-next {
  top: 50%;
  transform: translateY(-50%);
  font-size: 28px;
  padding: 12px;
}

.lightbox-prev {
  left: 12px;
}

.lightbox-next {
  right: 12px;
}

.lightbox-counter {
  position: absolute;
  left: 24px;
  bottom: 18px;
  font-family: var(--font);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.5);
}

@media (max-width: 640px) {
  .lightbox-image {
    inset: 70px 16px;
  }
}

/* Opportunity board — private page (see board.html/board.js), built from
   the same material and tokens as the rest of the site (--pill-bg-rgb,
   --border-rgb, --fg-rgb, --indicator-bg, the --accent pair) rather than
   any new color system, so it reads as part of the site and not a
   bolted-on tool, the same way edit.html deliberately does NOT. */
.board-page {
  max-width: 900px;
  margin: 0 auto;
  padding: 144px 40px 80px;
}

.board-heading {
  font-family: var(--font-title);
  font-size: clamp(32px, 5vw, 48px);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 8px;
}

.board-subtitle {
  font-size: 15px;
  color: rgba(var(--fg-rgb), 0.5);
  margin-bottom: 48px;
}

.board-goals {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 56px;
}

/* Same card material as the notch/.filter-tabs/.project-nav-btn, but with
   a visibly brighter border (0.25 vs the usual 0.08) plus a soft outer
   glow at the same opacity — these two are the whole point of the page,
   so they need to read as a clearly different tier from the opportunity
   cards below at a glance, not just via the "Goal" label text. */
@property --board-glow-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.board-goal-card {
  position: relative;
  flex: 1 1 260px;
  padding: 28px;
  border-radius: 18px;
  background:
    radial-gradient(circle at 15% 0%, rgba(var(--fg-rgb), 0.08), transparent 55%),
    rgba(var(--pill-bg-rgb), calc(var(--pill-bg-alpha) + 0.05));
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(var(--fg-rgb), 0.12);
  box-shadow: 0 0 0 1px rgba(var(--fg-rgb), 0.04);
  isolation: isolate;
}

/* A bright arc of light traveling around the card's edge, same idea as
   the Dribbble "animated border glow" reference — kept monochrome
   (--fg-rgb) instead of the reference's color to match the rest of the
   site. Standard conic-gradient-ring technique: the pseudo-element sits
   over the whole card, a padding creates a thin ring, and the mask
   XORs out everything but that ring. */
.board-goal-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(
    from var(--board-glow-angle),
    transparent 0deg,
    transparent 260deg,
    rgba(var(--fg-rgb), 0.85) 300deg,
    rgba(var(--fg-rgb), 1) 320deg,
    rgba(var(--fg-rgb), 0.85) 340deg,
    transparent 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
  animation: boardGlowRotate 7s linear infinite;
}

@keyframes boardGlowRotate {
  to {
    --board-glow-angle: 360deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .board-goal-card::before {
    animation: none;
  }
}

.board-goal-label {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgb(var(--fg-rgb));
  background: rgba(var(--fg-rgb), 0.1);
  padding: 5px 10px;
  border-radius: 999px;
  margin-bottom: 14px;
}

.board-goal-title {
  font-family: var(--font-title);
  font-size: 20px;
  font-weight: 500;
  margin-bottom: 8px;
}

.board-goal-description {
  font-size: 14px;
  line-height: 1.6;
  color: rgba(var(--fg-rgb), 0.7);
}

.board-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 20px;
}

.board-section-title {
  font-family: var(--font-title);
  font-size: 20px;
  font-weight: 500;
}

/* Sort control — same sliding-pill segmented control the old theme toggle
   used before it was dropped (see updateFilterIndicator's own version of
   this same mechanic in home.js, mirrored here). */
.board-sort-toggle {
  position: relative;
  display: flex;
  gap: 2px;
  padding: 4px;
  border-radius: 12px;
  background: rgba(var(--pill-bg-rgb), var(--pill-bg-alpha));
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(var(--border-rgb), 0.08);
}

.board-sort-indicator-wrap {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.4, 1), width 0.5s cubic-bezier(0.34, 1.4, 0.4, 1);
  pointer-events: none;
  z-index: 0;
}

.board-sort-indicator {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  background: var(--indicator-bg);
  transform-origin: center;
}

.board-sort-indicator.morphing {
  animation: liquidSquash 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .board-sort-indicator.morphing {
    animation: none;
  }
}

.board-sort-option {
  position: relative;
  z-index: 1;
  text-align: center;
  background: none;
  border: none;
  color: rgba(var(--fg-rgb), 0.7);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  padding: 7px 14px;
  border-radius: 8px;
  transition: color 0.3s ease;
}

.board-sort-option:hover {
  color: rgb(var(--fg-rgb));
}

.board-sort-option.active {
  color: rgb(var(--fg-rgb));
}

.board-opportunities {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
}

.board-opportunity-card {
  padding: 20px;
  border-radius: 14px;
  background: rgba(var(--pill-bg-rgb), var(--pill-bg-alpha));
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(var(--border-rgb), 0.08);
  transition: border-color 0.25s ease, transform 0.25s ease;
}

/* Same subtle lift language as .gallery-item/.project-nav-btn's own hover
   feedback, rather than introducing a new interaction pattern. */
.board-opportunity-card:hover {
  border-color: rgba(var(--border-rgb), 0.18);
  transform: translateY(-2px);
}

/* Opt-in themed card (set via op.cardTheme in opportunities.json — see
   board.js) for an opportunity with a striking visual identity of its
   own, worth carrying over rather than flattening into the same dark
   glass look as everything else on the board. "ice" is The I.C.E. St.
   Moritz 2027's own housestyle: classic cars on a frozen alpine lake,
   in partnership with a five-star hotel and sponsors like Richard Mille
   and VistaJet — snow white and glacier blue, with a thin warm-gold
   accent nodding to that luxury-sponsor roster. Every color below is
   hardcoded rather than pulled from the site's --fg-rgb/--pill-bg-rgb
   theme variables on purpose: this card should look the same, snow
   white, regardless of whether the visitor has the site's own
   light/dark toggle set to dark or light. */
.board-card-ice {
  position: relative;
  isolation: isolate;
  background: linear-gradient(165deg, #ffffff 0%, #f4f9fc 55%, #e9f2f9 100%);
  border-color: rgba(97, 153, 197, 0.35);
  box-shadow: 0 0 0 1px rgba(157, 197, 230, 0.2), 0 10px 30px -14px rgba(60, 110, 160, 0.4);
  color: #16232e;
}

.board-card-ice:hover {
  border-color: rgba(97, 153, 197, 0.55);
}

/* Glow is a goal-card thing on this board (see .board-goal-card::before
   above) — the opportunity-card instance of "ice" stays plain: just the
   white/glacier card look from .board-card-ice itself, no bar and no
   ring, so it reads as calmer than the goal above it rather than
   competing with it. Scoped to .board-goal-card specifically (rather
   than plain .board-card-ice) so this only touches the goal card. */
.board-goal-card.board-card-ice::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  /* Same travelling glow-ring technique as the other goal cards' own
     .board-goal-card::before (see --board-glow-angle/boardGlowRotate
     above), reusing that same @property/keyframes pair rather than
     redefining them — but recolored through this card's own glacier
     blue instead of the plain monochrome --fg-rgb the others use, so
     the glow carries the housestyle rather than just borrowing its
     motion. Blue only, no gold/orange — this is meant to read as ice,
     not as a luxury-gold accent. */
  background: conic-gradient(
    from var(--board-glow-angle),
    transparent 0deg,
    transparent 255deg,
    rgba(97, 165, 210, 0.85) 295deg,
    rgba(255, 255, 255, 1) 320deg,
    rgba(97, 165, 210, 0.85) 345deg,
    transparent 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
  animation: boardGlowRotate 7s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .board-goal-card.board-card-ice::before {
    animation: none;
  }
}

.board-card-ice .board-opportunity-type {
  color: rgba(22, 60, 92, 0.55);
}

.board-card-ice .board-opportunity-title {
  color: #142230;
}

.board-card-ice .board-opportunity-meta {
  color: rgba(22, 45, 65, 0.6);
}

/* This card's note runs noticeably longer than the others (event
   background plus the five-recipient hint), so it gets a touch smaller
   than the shared .board-opportunity-note size to keep it from reading
   as a wall of text against the card's own name/meta above it. */
.board-card-ice .board-opportunity-note {
  color: rgba(22, 45, 65, 0.72);
  font-size: 12px;
}

.board-card-ice .board-opportunity-accreditation {
  color: rgba(22, 45, 65, 0.72);
  font-size: 12px;
}

.board-card-ice .board-opportunity-accreditation-label {
  color: #142230;
}

.board-card-ice .board-opportunity-link {
  color: rgba(18, 70, 110, 0.85);
}

.board-card-ice .board-opportunity-link:hover {
  color: #0d3552;
}

.board-card-ice .board-btn-secondary {
  background: rgba(30, 100, 150, 0.12);
  color: #0d3a5c;
}

.board-card-ice .board-btn-secondary:hover {
  background: rgba(30, 100, 150, 0.2);
}

.board-card-ice .board-btn-ghost {
  border-color: rgba(22, 60, 92, 0.25);
  color: rgba(22, 55, 82, 0.7);
}

.board-card-ice .board-btn-ghost:hover {
  color: #142230;
  border-color: rgba(22, 60, 92, 0.45);
}

.board-card-ice .board-btn-primary {
  background: linear-gradient(135deg, #123f63 0%, #1f6a9c 100%);
  color: #ffffff;
}

.board-card-ice .board-btn-primary:hover {
  opacity: 0.9;
}

/* .board-card-ice itself and the button overrides above already apply
   to a goal card wearing this theme just as they do an opportunity card
   (see board.js) — only these three text elements are goal-specific and
   need their own dark-on-white color. (The glow ring is goal-only; see
   .board-goal-card.board-card-ice::before above.) */
.board-card-ice .board-goal-label {
  color: #0d3a5c;
  background: rgba(30, 100, 150, 0.14);
}

.board-card-ice .board-goal-title {
  color: #142230;
}

.board-card-ice .board-goal-description {
  color: rgba(22, 45, 65, 0.72);
}

.board-opportunity-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
}

.board-opportunity-type {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.4);
}

/* A native <select>, not a span — lets you change the status right on
   the card. Styled to still read as a small pill rather than a form
   control; the browser's own dropdown list handles the rest. */
.board-status {
  font: inherit;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 3px 12px 3px 6px;
  border: none;
  border-radius: 999px;
  background: rgba(var(--fg-rgb), 0.08);
  color: rgba(var(--fg-rgb), 0.6);
  cursor: pointer;
}

.board-status:hover {
  filter: brightness(1.15);
}

.board-status:focus-visible {
  outline: 2px solid rgba(var(--fg-rgb), 0.35);
  outline-offset: 2px;
}

/* Pipeline order — surfaced (Claude added it) -> interested (you flagged
   it) -> pitched (you reached out) -> booked (confirmed). Each stage gets
   its own color so the board reads at a glance; these are the only spots
   of hue anywhere on the site, deliberately, since a status pipeline is
   the one place color carries real information. */
.board-status-surfaced {
  background: rgba(var(--status-surfaced-rgb), 0.16);
  color: rgb(var(--status-surfaced-rgb));
}

.board-status-interested {
  background: rgba(var(--status-interested-rgb), 0.16);
  color: rgb(var(--status-interested-rgb));
}

.board-status-pitched {
  background: rgba(var(--status-pitched-rgb), 0.18);
  color: rgb(var(--status-pitched-rgb));
}

.board-status-booked {
  background: rgba(var(--status-booked-rgb), 0.18);
  color: rgb(var(--status-booked-rgb));
}

.board-opportunity-title {
  font-family: var(--font-title);
  font-size: 17px;
  font-weight: 500;
  margin-bottom: 8px;
}

.board-opportunity-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: rgba(var(--fg-rgb), 0.5);
  margin-bottom: 10px;
}

.board-opportunity-dot {
  opacity: 0.5;
}

.board-opportunity-note {
  font-size: 13px;
  line-height: 1.6;
  color: rgba(var(--fg-rgb), 0.7);
  margin-bottom: 14px;
}

/* Since you don't have an existing press pass, every card states the
   realistic route to accreditation for that specific event — distinct
   enough from .board-opportunity-note to scan separately, but no color,
   per this page's own rule (see the status pill comment below) that hue
   is reserved for the status pipeline. */
.board-opportunity-accreditation {
  font-size: 13px;
  line-height: 1.6;
  color: rgba(var(--fg-rgb), 0.7);
  margin-bottom: 14px;
}

.board-opportunity-accreditation-label {
  font-weight: 600;
  color: rgba(var(--fg-rgb), 0.9);
}

/* Same underline-on-hover treatment as .info-block a/.nav-social, rather
   than a button — this is a reference link out to the event's own site,
   not an action within the board itself. */
.board-opportunity-link {
  position: relative;
  display: inline-block;
  width: fit-content;
  font-size: 12px;
  font-weight: 500;
  color: rgba(var(--fg-rgb), 0.75);
  text-decoration: none;
  transition: color 0.25s ease;
}

.board-opportunity-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.3s ease;
}

.board-opportunity-link:hover {
  color: rgb(var(--fg-rgb));
}

.board-opportunity-link:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* Category filter reuses the main grid page's own .filter-tabs component
   (see board.js) rather than a lookalike, so it's the exact same tabs +
   sliding pill. That component hides itself under 640px on the grid page
   (a single-column mobile grid doesn't need it) — the board still wants
   it at every width, so this overrides it back on here specifically.
   (Margin is handled by .board-toolbar, which always wraps it — see
   below.) */
@media (max-width: 640px) {
  .board-page .filter-tabs {
    display: flex;
  }
}

/* Action buttons at the foot of each opportunity card. */
.board-btn {
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 9px 14px;
  border-radius: 10px;
  border: none;
  cursor: pointer;
  transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}

.board-btn-secondary {
  background: rgba(var(--fg-rgb), 0.1);
  color: rgb(var(--fg-rgb));
}

.board-btn-secondary:hover {
  background: rgba(var(--fg-rgb), 0.16);
}

.board-btn-ghost {
  background: none;
  border: 1px solid rgba(var(--fg-rgb), 0.15);
  color: rgba(var(--fg-rgb), 0.6);
}

.board-btn-ghost:hover {
  color: rgb(var(--fg-rgb));
  border-color: rgba(var(--fg-rgb), 0.3);
}

.board-btn-primary {
  background: rgb(var(--accent-bg-rgb));
  color: rgb(var(--accent-text-rgb));
}

.board-btn-primary:hover {
  opacity: 0.85;
}

.board-opportunity-actions {
  display: flex;
  gap: 8px;
  margin-top: 16px;
}

.board-opportunity-actions .board-btn {
  flex: 1;
}

.board-pitch-btn {
  display: block;
  width: 100%;
  margin-top: 8px;
  text-align: center;
}

/* Modals — used for the "not interested" confirmation and the pitch
   email template. Same glass card material as the rest of the board,
   over a dimmed backdrop like the main site's .lightbox. */
.board-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(5, 5, 5, 0.75);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.board-modal-overlay.visible {
  opacity: 1;
  pointer-events: auto;
}

.board-modal {
  width: 100%;
  max-width: 460px;
  max-height: 80vh;
  overflow-y: auto;
  padding: 28px;
  border-radius: 20px;
  background: rgba(var(--pill-bg-rgb), calc(var(--pill-bg-alpha) + 0.25));
  backdrop-filter: blur(32px) saturate(160%);
  -webkit-backdrop-filter: blur(32px) saturate(160%);
  border: 1px solid rgba(var(--border-rgb), 0.1);
  transform: translateY(8px) scale(0.98);
  transition: transform 0.25s ease;
}

.board-modal-overlay.visible .board-modal {
  transform: translateY(0) scale(1);
}

.board-modal-title {
  font-family: var(--font-title);
  font-size: 19px;
  font-weight: 500;
  margin-bottom: 10px;
}

.board-modal-body {
  font-size: 14px;
  line-height: 1.6;
  color: rgba(var(--fg-rgb), 0.7);
  margin-bottom: 20px;
}

.board-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

/* Same sliding-pill segmented control the old theme toggle used before it
   was dropped. Unlike that one (and unlike this toggle's own former
   English/Nederlands-only incarnation as .pitch-lang-toggle), this can hold more than two
   options — an opportunity with its own pitchRecipients gets one option
   per recipient (see board.js's buildPitchModeToggle) — so it wraps, and
   its indicator tracks offsetTop/height too (see .filter-tabs-indicator-
   wrap's identical reasoning), not just offsetLeft/width. */
.pitch-mode-toggle {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  width: fit-content;
  padding: 4px;
  border-radius: 12px;
  background: rgba(var(--fg-rgb), 0.06);
  margin-bottom: 16px;
}

/* A single-pathway opportunity has nothing to toggle between (see
   board.js's openPitchModal), so the toggle is hidden rather than shown
   with one inert option. Needs its own rule since [hidden]'s UA-stylesheet
   display:none loses to the class rule above without it. */
.pitch-mode-toggle[hidden] {
  display: none;
}

.pitch-mode-indicator-wrap {
  position: absolute;
  /* top/left stay at 0 — board.js's updatePitchModeIndicator already
     positions this with translate(active.offsetLeft, active.offsetTop)
     and sizes it with explicit width/height, so a static inset here
     would just double-count the toggle's own padding. */
  top: 0;
  left: 0;
  transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.4, 1), width 0.5s cubic-bezier(0.34, 1.4, 0.4, 1),
    height 0.5s cubic-bezier(0.34, 1.4, 0.4, 1);
  pointer-events: none;
  z-index: 0;
}

.pitch-mode-indicator {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  background: var(--indicator-bg);
  transform-origin: center;
}

.pitch-mode-indicator.morphing {
  animation: liquidSquash 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .pitch-mode-indicator.morphing {
    animation: none;
  }
}

.pitch-mode-option {
  position: relative;
  z-index: 1;
  text-align: center;
  background: none;
  border: none;
  color: rgba(var(--fg-rgb), 0.7);
  font-family: var(--font);
  font-size: 12px;
  letter-spacing: 0.02em;
  cursor: pointer;
  padding: 6px 14px;
  border-radius: 8px;
  transition: color 0.3s ease;
}

.pitch-mode-option:hover {
  color: rgb(var(--fg-rgb));
}

.pitch-mode-option.active {
  color: rgb(var(--fg-rgb));
}

/* "To"/"Subject" preview shown above the email body when an opportunity
   supplies its own fully pre-written pitchRecipients (see
   opportunities.json) instead of the generic EN/NL template — hidden
   for every other card, which has no one fixed recipient to show. */
.pitch-recipient-info {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(var(--fg-rgb), 0.05);
}

.pitch-recipient-info[hidden] {
  display: none;
}

.pitch-recipient-row {
  display: flex;
  gap: 8px;
  font-size: 12px;
  line-height: 1.5;
}

.pitch-recipient-label {
  flex-shrink: 0;
  width: 52px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.45);
}

.pitch-recipient-row span:last-child {
  color: rgba(var(--fg-rgb), 0.8);
  word-break: break-word;
}

.board-modal-email {
  white-space: pre-wrap;
  font-size: 13px;
  line-height: 1.65;
  color: rgba(var(--fg-rgb), 0.85);
  background: rgba(var(--fg-rgb), 0.05);
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 18px;
  max-height: 40vh;
  overflow-y: auto;
}

/* Row holding the category filter and the "+ Add card" button — same
   level, filter on the left, the button pinned to the right. */
.board-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 24px;
}

.board-toolbar .filter-tabs {
  margin: 0;
}

/* `margin-left: auto` (rather than `justify-content: space-between` on
   the parent) keeps the button pinned to the right of whichever line it
   ends up on once the tabs wrap to a second row — space-between only
   works right when there are exactly two items sharing one line. */
.board-toolbar .board-btn {
  flex-shrink: 0;
  margin-left: auto;
}

/* Category picker inside the "Add a new opportunity" modal. */
.board-modal-categories {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

.board-modal-categories[hidden] {
  display: none;
}

.board-modal-category-btn {
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid rgba(var(--fg-rgb), 0.15);
  background: none;
  color: rgba(var(--fg-rgb), 0.75);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.board-modal-category-btn:hover {
  color: rgb(var(--fg-rgb));
  border-color: rgba(var(--fg-rgb), 0.3);
}

.board-modal-other-row {
  display: flex;
  gap: 8px;
  margin-bottom: 18px;
}

/* An explicit `display` on this class would otherwise beat the browser's
   own `[hidden] { display: none }` rule — author styles always win over
   default UA ones regardless of specificity, so `hidden` alone silently
   stops working the moment a class sets its own `display`. */
.board-modal-other-row[hidden] {
  display: none;
}

.board-modal-input {
  flex: 1;
  min-width: 0;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid rgba(var(--fg-rgb), 0.15);
  background: rgba(var(--fg-rgb), 0.05);
  color: rgb(var(--fg-rgb));
  font-family: var(--font);
  font-size: 13px;
}

.board-modal-input:focus {
  outline: none;
  border-color: rgba(var(--fg-rgb), 0.35);
}

@media (max-width: 640px) {
  .board-page {
    padding: 190px 20px 60px;
  }

  .board-goals {
    flex-direction: column;
  }

  .board-opportunities {
    grid-template-columns: 1fr;
  }
}

/* Photo Stories — a template/draft page (stories.html), not yet linked
   from public nav. Full-bleed magazine layout inspired by Urth
   Magazine's story pages, built with the site's own fonts/colors/tokens
   rather than copying Urth's — same visual language as the rest of the
   site, just a different rhythm (full-bleed photos + a centered reading
   column, instead of glass cards). */

.story {
  margin-bottom: 120px;
}

.story:last-child {
  margin-bottom: 0;
}

.story-hero {
  position: relative;
  height: 100vh;
  height: 100dvh;
  min-height: 480px;
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: flex-end;
}

.story-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(5, 5, 5, 0.85) 0%, rgba(5, 5, 5, 0.25) 45%, transparent 70%);
  pointer-events: none;
}

.story-hero-content {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 780px;
  margin: 0 auto;
  padding: 0 40px 64px;
  color: #ffffff;
}

.story-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.story-tag {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 12px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: #ffffff;
}

.story-title {
  font-family: var(--font-title);
  font-size: clamp(36px, 6vw, 64px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin-bottom: 16px;
}

.story-dek {
  font-family: var(--font);
  font-size: clamp(15px, 2vw, 18px);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.8);
  max-width: 620px;
}

.story-byline-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  max-width: 780px;
  margin: 0 auto;
  padding: 32px 40px 0;
}

.story-byline {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.4);
}

.story-back-link {
  font-size: 13px;
  font-weight: 500;
  color: rgba(var(--fg-rgb), 0.6);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.25s ease;
}

.story-back-link:hover {
  color: rgb(var(--fg-rgb));
}

.story-body {
  padding-top: 56px;
}

.story-text {
  max-width: 680px;
  margin: 0 auto 40px;
  padding: 0 40px;
  font-size: 18px;
  line-height: 1.75;
  color: rgba(var(--fg-rgb), 0.85);
}

.story-text:last-child {
  margin-bottom: 0;
}

.story-image-block {
  margin: 56px 0;
}

.story-image-block img {
  display: block;
  width: 100%;
  max-height: 88vh;
  object-fit: cover;
}

.story-image-caption {
  max-width: 680px;
  margin: 16px auto 0;
  padding: 0 40px;
  font-size: 13px;
  line-height: 1.6;
  color: rgba(var(--fg-rgb), 0.5);
  font-style: italic;
}

@media (max-width: 640px) {
  .story-hero-content {
    padding: 0 20px 40px;
  }

  .story-byline-row {
    padding: 24px 20px 0;
  }

  .story-text {
    padding: 0 20px;
    font-size: 16px;
  }

  .story-image-caption {
    padding: 0 20px;
  }
}

.story-not-found {
  max-width: 780px;
  margin: 0 auto;
  padding: 200px 40px 120px;
  text-align: center;
}

.story-not-found a {
  color: rgba(var(--fg-rgb), 0.7);
}

/* Photo Stories landing page — a masthead + featured hero in the style
   of magazine.urth.co's own homepage, then a grid of the remaining
   stories. The masthead/grid stay inside a readable max-width, same as
   the rest of the site; the featured hero deliberately breaks out to
   full viewport width, same trick as an individual story's own hero
   (see .story-hero) — .stories-index-page itself carries no horizontal
   constraint so that can happen. */
.stories-index-page {
  padding-bottom: 80px;
}

/* Featured hero — true full-screen (100vh/100dvh), same trick as an
   individual story's own hero (.story-hero) for going full-bleed under
   the floating notch. The masthead lives INSIDE it, overlaid on the
   photo, matching Urth's own homepage rather than sitting in its own
   section above the image. */
.stories-featured {
  position: relative;
  height: 100vh;
  height: 100dvh;
  min-height: 560px;
  background-size: cover;
  background-position: center;
  overflow: hidden;
  color: #ffffff;
}

.stories-featured::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(5, 5, 5, 0.85) 0%, rgba(5, 5, 5, 0.15) 55%, rgba(5, 5, 5, 0.35) 100%);
  pointer-events: none;
}

/* Covers the full hero for click-through — kept as its own element
   (rather than wrapping the whole hero in one <a>) because the masthead
   overlay has its own real link (Subscribe) inside it, and nested <a>
   tags are invalid HTML/break click behavior. Sits at the bottom of the
   stack; anything with its own higher z-index (Subscribe) still gets
   hit-tested first. */
.stories-featured-bg-link {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Fixed position at every width, on purpose — only the wordmark's own
   font-size responds to viewport width (see .stories-wordmark), not
   where anything sits. */
/* Same max-width/margin/padding as .stories-featured-content below, so
   the wordmark and the tagline/Subscribe row share the exact left edge
   "In Focus" and the story title start from, not their own independent
   centering. */
/* Full width (not capped at 1100px) so the centered wordmark below gets
   equal breathing room on both sides regardless of viewport — a
   symmetric box is what actually guarantees "same distance right as
   left", not just text-align on its own. */
/* Same max-width/margin/padding as .stories-featured-content — the
   title, the tagline, and Subscribe all share that exact column, so
   left edges line up with "In Focus" and right edges line up with each
   other, and the title's own width tracks that column's width 1:1 as
   the viewport resizes (never narrower than it, by construction, since
   they're the same box). */
/* No max-width/centering here — matching Urth's own layout (re-checked
   directly): the wordmark, the nav row, and the article title all share
   one simple, consistent page margin, not a narrower centered content
   column. Same plain padding as .stories-featured-content below, so
   left edges always match at any viewport width. */
/* --stories-edge is set by stories.js to the wordmark's own live left
   edge (it's centered, so that position shifts continuously with
   viewport width) — the fallback (64px) only matters before JS runs or
   if it's ever missing. Right edge stays a plain fixed 64px; only the
   left needs to track the title. */
/* No horizontal padding here on purpose — it would otherwise be shared
   by both children below, and one of them (.stories-wordmark) can't
   have its own centering context depend on --stories-edge, which is
   itself derived FROM the wordmark's measured position (that's
   circular: the box's width would shift where "centered" falls, which
   would change the next measurement, and so on). Each child manages its
   own horizontal padding instead. */
.stories-masthead-overlay {
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  right: 0;
  padding-top: 100px;
}

/* Fixed, symmetric padding — the wordmark's own stable centering
   context (see the comment above). */
.stories-wordmark-wrap {
  padding: 0 64px;
}

/* Tracks --stories-edge independently, as a sibling of the wordmark
   rather than inheriting a shared padding from it — the actual measured
   position of the "P" in "Photo" (see stories.js), not a guess. Right
   edge stays a plain fixed 64px, matching everywhere else. */
.stories-masthead-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 10px;
  padding-right: var(--stories-edge-right, 64px);
  padding-left: var(--stories-edge, 64px);
}

.stories-masthead-tagline {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
}

/* A plain colored word, not a button — same underline-on-hover language
   as .board-opportunity-link, in a warm accent matching Urth's own
   Subscribe treatment (the one deliberate spot of color on this
   template, same reasoning as the board's status pipeline). Deliberately
   not wired to anything yet — see stories.js's comment. */
.stories-subscribe-link {
  position: relative;
  z-index: 2;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #ffb266;
  text-decoration: none;
  cursor: pointer;
}

.stories-subscribe-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.3s ease;
}

.stories-subscribe-link:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* Urth's own logo never stacks or wraps at any width, down to a phone —
   it's an image, so it just keeps shrinking, staying a roughly constant
   ~85-95% of viewport width whether that's 1600px or 375px (confirmed
   by measuring it directly at both ends). A pure vw-driven font-size,
   with a floor low enough to never actually bind on a real screen, gets
   the same one-line-always behavior out of live text. */
.stories-wordmark {
  font-size: clamp(16px, 11vw, 190px);
  line-height: 0.95;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
}

/* "Photo" in the site's own display face (Ekster Bold — see the
   @font-face block at the top of this file), "Stories" in Playfair
   Display, the closest free match to Urth's own "MAGAZINE" (Domaine) —
   deliberately two different families, not just two weights of one,
   per feedback. */
.stories-wordmark-bold {
  font-family: var(--font-title);
  font-weight: 700;
}

.stories-wordmark-regular {
  font-family: "Playfair Display", serif;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.75);
}

.stories-featured-content {
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  bottom: 0;
  padding-right: 64px;
  padding-bottom: 64px;
  padding-left: var(--stories-edge, 64px);
}

.stories-featured-kicker {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.7);
  margin-bottom: 12px;
}

.stories-featured-title {
  font-family: var(--font-title);
  font-size: clamp(28px, 4.5vw, 44px);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.1;
  max-width: 720px;
  margin-bottom: 14px;
}

.stories-featured-dek {
  font-size: 15px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.8);
  max-width: 560px;
  margin-bottom: 18px;
}

/* The cursor-follow "Read feature" badge, and the OS cursor it replaces
   — both desktop/fine-pointer only (see stories.js), same (hover:hover)
   and (pointer:fine) gate used elsewhere on the site (the notch) so
   touch devices never get a stuck badge. */
@media (hover: hover) and (pointer: fine) {
  .stories-featured {
    cursor: none;
  }

  .stories-subscribe-link {
    cursor: pointer;
  }
}

.stories-featured-badge {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: rgba(245, 242, 235, 0.06);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(251, 249, 243, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #fbf9f3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease, width 0.2s ease, height 0.2s ease, background-color 0.2s ease, backdrop-filter 0.2s ease;
}

.stories-featured-badge.visible {
  opacity: 1;
}

/* Shrinks to a small dot with no label when hovering text within the
   hero (the masthead or the story's own title/dek/tags) — same idea as
   Urth's own hover badge, which steps out of the way of anything meant
   to be read rather than clicked-anywhere. */
.stories-featured-badge.small {
  width: 28px;
  height: 28px;
  background: rgba(245, 242, 235, 0.5);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* There's no cursor to follow on a phone or tablet, so rather than lean
   on the (hover: hover)/(pointer: fine) feature query above (touchscreens
   are known to report hover support inconsistently — see the notch's own
   :hover comment in header.js), the badge is hidden outright below
   desktop widths. */
@media (max-width: 1024px) {
  .stories-featured-badge {
    display: none;
  }
}

.stories-featured-badge.small span {
  opacity: 0;
}

/* No max-width/centering here either — same reasoning as the hero's own
   .stories-masthead-overlay/.stories-featured-content: one consistent
   page margin throughout, not a narrower centered column that would
   drift out of alignment with them at wide viewports. */
.stories-more {
  padding-top: 64px;
  padding-right: 64px;
  padding-left: var(--stories-edge, 64px);
}

.stories-more-heading {
  font-family: var(--font-title);
  font-size: 20px;
  font-weight: 500;
  text-align: center;
  margin-bottom: 24px;
}

.stories-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 24px;
}

.story-card {
  position: relative;
  display: block;
  overflow: hidden;
  background: rgba(var(--pill-bg-rgb), var(--pill-bg-alpha));
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid rgba(var(--border-rgb), 0.08);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.25s ease, transform 0.25s ease, opacity 0.25s ease;
}

.story-card:hover {
  border-color: rgba(var(--border-rgb), 0.18);
  transform: translateY(-2px);
}

/* Reveal (see reveal.js) needs its own explicit override here rather than
   relying on the shared .reveal rules alone — .story-card already
   animates its own transform on hover (the lift above), and two rules of
   equal specificity fighting over the same transform/transition
   properties would resolve by source order alone, which is fragile.
   Written out directly: reveal's slower entrance timing to start, then
   .story-card's own faster transform timing once visible, so the hover
   lift still feels snappy rather than inheriting reveal's 0.7s. Private
   cards (see below) never get the .reveal class in stories.js — their
   own opacity: 0.4 resting state would otherwise fight reveal's
   opacity: 0 -> 1 sequence. */
.story-card.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1), transform 0.7s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.25s ease;
}

.story-card.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1), transform 0.25s ease, border-color 0.25s ease;
}

.story-card.reveal.is-visible:hover {
  transform: translateY(-2px);
}

/* Set from the editor's Visibility toggle (see stories-edit.html) — the
   card renders as a plain, ghost-transparent <div> instead of a link (see
   stories.js), so there's nothing to click through to on the landing
   page. Overrides the plain .story-card hover lift/border-brighten below,
   since neither should suggest this card is interactive. */
.story-card.is-private {
  opacity: 0.4;
  cursor: default;
}

.story-card.is-private:hover {
  opacity: 0.4;
  transform: none;
  border-color: rgba(var(--border-rgb), 0.08);
}

.story-card-private-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 5px;
  background: #ffffff;
  color: #0a0a0a;
}

.story-card-image {
  aspect-ratio: 4 / 3;
  background-size: cover;
  background-position: center;
}

.story-card-body {
  padding: 20px;
}

.story-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}

.story-card-tag {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 5px;
  background: rgba(var(--fg-rgb), 0.08);
  color: rgba(var(--fg-rgb), 0.6);
}

.story-card-title {
  font-family: var(--font-title);
  font-size: 18px;
  font-weight: 500;
  margin-bottom: 8px;
}

.story-card-dek {
  font-size: 13px;
  line-height: 1.6;
  color: rgba(var(--fg-rgb), 0.65);
  margin-bottom: 14px;
}

.story-card-link {
  font-size: 12px;
  font-weight: 600;
  color: rgba(var(--fg-rgb), 0.75);
}

@media (max-width: 640px) {
  .stories-index-page {
    padding-bottom: 60px;
  }

  .stories-more {
    padding-top: 48px;
    padding-right: 28px;
    padding-left: var(--stories-edge, 28px);
  }

  .stories-wordmark-wrap {
    padding: 0 28px;
  }

  .stories-masthead-top {
    padding-right: var(--stories-edge-right, 28px);
    padding-left: var(--stories-edge, 28px);
  }

  .stories-featured-content {
    padding-top: 0;
    padding-right: 28px;
    padding-bottom: 32px;
    padding-left: var(--stories-edge, 28px);
  }

  .stories-wordmark {
    /* Was 40px — the story behind the shot / Subscribe row read as
       floating, disconnected from the title above it on mobile. Pulled in
       to sit right underneath it instead, per feedback. */
    margin-bottom: 16px;
  }

  .stories-grid {
    grid-template-columns: 1fr;
  }
}

/* Footer */

.site-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 16px;
  padding: 28px 28px;
  font-family: var(--font);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: rgba(var(--fg-rgb), 0.4);
  text-align: center;
}

/* The homepage's last grid card runs right up to the footer otherwise —
   more breathing room above it than the site's other, plainer footers
   need. */
.landing-page + .site-footer {
  padding-top: 64px;
}

/* Copy-to-clipboard confirmation for mailto: links (see header.js) — a
   small pill that appears just below whichever email link was clicked. */
.mailto-toast {
  position: fixed;
  transform: translateY(4px);
  /* Same fixed-contrast accent used elsewhere for anything that needs to
     read clearly regardless of what's behind it. */
  background: rgb(var(--accent-bg-rgb));
  color: rgb(var(--accent-text-rgb));
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 5px 10px;
  border-radius: 6px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 50;
  white-space: nowrap;
}

.mailto-toast.visible {
  opacity: 1;
  transform: translateY(8px);
}
