/* ==========================================================================
   Jameson Thermitus — Portfolio
   Global styles: reset, typography, nav, footer, layout primitives
   ========================================================================== */

:root {
  --bg: #0a0a0a;
  --bg-elevated: #121212;
  --text: #f2f2f0;
  --text-dim: #a8a8a4;
  --text-faint: #6b6b68;
  --accent: #ffffff;
  --border: rgba(255, 255, 255, 0.1);
  --nav-height: 84px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --font: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* The logo wordmark (.hero-logo) is a raster PNG, not text set in a web
     font -- there's no serif actually loaded anywhere in the project (no
     @font-face, no Google Fonts link). This is the system serif stack,
     used only for the Portfolio landing tile names, kept consistent with
     the rest of the site's system-fonts-only approach (no web font
     dependency added just for this). */
  --font-serif: Georgia, "Times New Roman", Times, serif;
  /* Sampled directly from images/logo/logo-horizontal.png (rgb(26, 117, 187))
     -- a real accent color already used in the wordmark, not invented. */
  --accent-blue: #1a75bb;
}

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

html {
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-weight: 300;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

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

ul {
  list-style: none;
}

button {
  font-family: inherit;
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
}

/* ---- Layout primitives ---- */

.container {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 6vw;
}

.section {
  padding: 120px 0;
}

.eyebrow {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 16px;
}

.section-title {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 300;
  letter-spacing: -0.01em;
  margin-bottom: 24px;
}

.body-text {
  color: var(--text-dim);
  font-size: 1rem;
  max-width: 640px;
}

/* ---- Navigation ---- */

.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  height: var(--nav-height);
  display: flex;
  align-items: center;
  padding: 0 6vw;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
  transition: background-color 0.4s var(--ease), backdrop-filter 0.4s var(--ease);
}

.site-nav.is-scrolled {
  background: rgba(10, 10, 10, 0.82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.nav-inner {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav-logo {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  overflow: hidden; /* belt-and-suspenders: the img can never spill out of the nav row */
  z-index: 1001;
}

.nav-logo img {
  display: block;
  height: 40px; /* fixed — this is a wide horizontal lockup, so height (not max-width) is the dominant constraint */
  width: auto;
  max-width: 100%;
  object-fit: contain;
  /* the logo is a near-white mark; this keeps it legible in the transparent
     nav state over unpredictable photo content, same rationale as the
     text-shadow already used on .hero-mark */
  filter: drop-shadow(0 1px 6px rgba(0, 0, 0, 0.45));
}

@media (max-width: 860px) {
  .nav-logo img {
    height: 32px; /* leaves room next to the hamburger toggle */
  }
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 40px;
}

.nav-item {
  position: relative;
}

.nav-link {
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: color 0.3s var(--ease);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 0;
}

.nav-link:hover,
.nav-link.is-active {
  color: var(--text);
}

.nav-caret {
  font-size: 0.6rem;
  transition: transform 0.3s var(--ease);
}

.nav-item.has-dropdown:hover .nav-caret {
  transform: rotate(180deg);
}

.nav-dropdown {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  min-width: 180px;
  background: rgba(15, 15, 15, 0.96);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0.25s;
  backdrop-filter: blur(10px);
}

.nav-item.has-dropdown:hover .nav-dropdown,
.nav-item.has-dropdown:focus-within .nav-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

.nav-dropdown a {
  display: block;
  padding: 10px 14px;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  white-space: nowrap;
  border-radius: 3px;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}

.nav-dropdown a:hover,
.nav-dropdown a.is-active {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
}

/* ---- Nav social icons (last item in .nav-links -- top-right on desktop,
   folds to the bottom of the fullscreen mobile panel for free since it's
   just the last flex item in that column layout too) ---- */

.nav-socials {
  display: flex;
  align-items: center;
  gap: 14px;
}

.nav-social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  color: var(--text-dim);
  transition: color 0.3s var(--ease);
}

.nav-social-link:hover {
  color: var(--text);
}

.nav-social-link svg {
  width: 18px;
  height: 18px;
}

.nav-toggle {
  display: none;
  z-index: 1001;
  width: 28px;
  height: 20px;
  position: relative;
}

.nav-toggle span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--text);
  transition: transform 0.3s var(--ease), opacity 0.3s var(--ease), top 0.3s var(--ease);
}

.nav-toggle span:nth-child(1) { top: 0; }
.nav-toggle span:nth-child(2) { top: 9px; }
.nav-toggle span:nth-child(3) { top: 18px; }

.nav-toggle.is-open span:nth-child(1) { top: 9px; transform: rotate(45deg); }
.nav-toggle.is-open span:nth-child(2) { opacity: 0; }
.nav-toggle.is-open span:nth-child(3) { top: 9px; transform: rotate(-45deg); }

/* ---- Mobile nav ---- */

@media (max-width: 860px) {
  .nav-toggle {
    display: block;
  }

  /* The <ul class="nav-links"> is wrapped in a bare <nav> for semantics.
     That wrapper has no styling of its own, so at this width it still sat
     in .nav-inner's flex row as a 3rd item alongside .nav-logo and
     .nav-toggle -- even though .nav-links itself goes position:fixed
     below and renders full-screen, the empty wrapper box was still there,
     making space-between distribute across 3 items instead of 2 and
     pushing .nav-toggle in from the true right edge. display:none would
     hide .nav-links too (a display:none ancestor hides descendants
     regardless of their own position), so this uses display:contents
     instead -- the wrapper generates no box of its own (removing it from
     flex layout entirely), while .nav-links still renders exactly as
     before once it's fixed-positioned to fill the viewport. */
  .nav-inner > nav {
    display: contents;
  }

  .nav-links {
    position: fixed;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    gap: 28px;
    /* Fully opaque -- was rgba(8, 8, 8, 0.98), which let page content
       (photos, play buttons) visibly bleed through at the 2% gap. No
       reason for this panel to ever be anything but solid. */
    background: #080808;
    /* No backdrop-filter here (deliberately) -- this is a full-viewport
       panel revealed by a transform slide, and blurring everything newly
       exposed behind it in real time is expensive enough on real mobile
       hardware that the browser visibly lags getting the background fully
       opaque/blurred a beat after the (compositor-cheap) transform has
       already placed the nav links on screen -- the links render first,
       the backdrop looks like it's still catching up for about a second.
       The background alone is already fully opaque, so removing the blur
       costs essentially no visible fidelity while removing the one
       expensive-to-composite thing that could lag behind the content. */
    transform: translateX(100%);
    transition: transform 0.4s var(--ease);
  }

  .nav-links.is-open {
    transform: translateX(0);
  }

  .nav-link {
    font-size: 1.1rem;
  }

  .nav-item.has-dropdown {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .nav-dropdown {
    position: static;
    transform: none;
    opacity: 1;
    visibility: visible;
    background: none;
    border: none;
    backdrop-filter: none;
    display: none;
    text-align: center;
    padding: 4px 0 0;
  }

  .nav-item.has-dropdown.is-open .nav-dropdown {
    display: block;
  }

  /* Desktop's equivalent rotate is hover-driven (:hover above); on mobile
     there's no hover, so it's tied to the same .is-open class js/nav.js
     toggles for the dropdown itself, giving the same open/closed signal. */
  .nav-item.has-dropdown.is-open .nav-caret {
    transform: rotate(180deg);
  }

  .nav-dropdown a {
    padding: 10px 0;
    font-size: 0.95rem;
  }
}

/* Mobile nav scroll lock (js/nav.js) -- plain `overflow: hidden` on body
   alone doesn't reliably stop touch-scroll on iOS Safari, so the page
   underneath could still scroll/rubber-band behind the fixed nav panel,
   showing photo/social-icon/footer content bleeding in around it. Pinning
   body itself with position: fixed (js/nav.js sets `top` to the negative
   of the scroll position it's opened at, then scrolls back to that exact
   position on close) is the standard cross-browser-safe fix. left/right:0
   plus width:100% keep the now-fixed body from collapsing to auto width.
   Only ever applied by js/nav.js's mobile menu toggle, which itself is
   only visible/reachable below the 860px breakpoint above -- desktop nav
   behavior is unaffected. */
body.nav-scroll-lock {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

/* Root cause of the reopen-after-scroll flash: .site-nav.is-scrolled sets
   backdrop-filter, and ANY non-none filter/backdrop-filter on an ancestor
   establishes a new containing block for its position: fixed descendants
   (here, .nav-links) -- so instead of sizing against the true viewport,
   .nav-links briefly sized/positioned against .site-nav's own small
   header-height box (confirmed via a real rendered rect: 390x83 instead
   of 390x844 immediately after opening with .is-scrolled active).
   Harmless on first open (no scroll yet -- .is-scrolled isn't applied),
   which is exactly why this only ever showed up after scroll + reopen.
   Scoped to the same nav-scroll-lock class js/nav.js already applies
   synchronously in openMenu(), so this takes effect on the very first
   frame the panel can be visible -- no reliance on a later scroll-driven
   correction. transition: none forces an instant cut instead of letting
   .site-nav's own backdrop-filter 0.4s transition animate through
   intermediate non-zero blur values (which would still trigger the same
   containing-block bug for the duration of that animation). Invisible
   either way once .nav-links' now-opaque panel covers .site-nav. */
body.nav-scroll-lock .site-nav {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  transition: none;
}

/* ---- Footer ---- */

.site-footer {
  border-top: 1px solid var(--border);
  padding: 48px 6vw;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  align-items: center;
  justify-content: space-between;
  color: var(--text-faint);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
}

.site-footer a {
  color: var(--text-dim);
  transition: color 0.3s var(--ease);
}

.site-footer a:hover {
  color: var(--text);
}

.footer-social {
  display: flex;
  flex-wrap: wrap; /* .site-footer itself already wraps, but this inner row of 7 links didn't -- a flex item's default min-width is its content's intrinsic width, not 0, so without this it forced the whole row (and the page) wider than the viewport instead of actually wrapping */
  gap: 24px;
}

/* ---- Page hero (non-homepage pages) ---- */

.page-header {
  padding: calc(var(--nav-height) + 64px) 6vw 48px;
  border-bottom: 1px solid var(--border);
}

.page-header h1 {
  font-size: clamp(2rem, 5vw, 3.25rem);
  font-weight: 300;
  letter-spacing: -0.01em;
}

.page-header p {
  color: var(--text-dim);
  margin-top: 12px;
  max-width: 520px;
}

/* ---- Portfolio landing grid (portfolio/index.html) ----
   Full-bleed, edge-to-edge: the grid IS the page, no .container, no
   .page-header, no side padding on the section -- first/last tiles touch
   the viewport edges exactly. 4 tiles in a single row on desktop (one row
   is the right shape for exactly 4 categories, not a 2x2 stack); 2x2 at
   tablet/mobile, same "narrower cells, not fewer columns" rule used by
   .gallery-grid and .screenings-grid elsewhere on the site.

   This page is also a no-scroll, exactly-100vh layout: nav + grid + footer
   must add up to precisely window height, with zero leftover gap below
   the footer. Scoped to body.portfolio-landing-page (not bare body/main)
   so it can't affect any other page's height behavior -- the homepage
   hero, Movies, and the gallery pages all have their own, different
   height mechanics. */

body.portfolio-landing-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh; /* min, not fixed height -- if the footer ever needs more room than the viewport has, the page grows instead of clipping content */
}

body.portfolio-landing-page main {
  /* display: flex (not just flex: 1 1 auto on its own) is required here --
     a flex ITEM's flex-grown main-axis size is real for layout purposes
     but is NOT treated as a definite size for CHILD PERCENTAGE HEIGHT
     resolution (the flexbox spec's "definite size for percentage
     children" guarantee only covers the CROSS axis / stretch, not the
     MAIN axis / grow-shrink). height: 100% on .portfolio-landing-grid
     silently fell back to content-based sizing because of exactly this --
     confirmed live: main measured a real, correct 762.8px via flex-grow,
     but the grid's height:100% still resolved to its old content-driven
     519.6px, completely ignoring it. Making main a flex column itself and
     using flex: 1 on the grid (below) sidesteps the issue entirely,
     since flex-grow participates directly in the layout algorithm rather
     than resolving a percentage against a parent size. */
  display: flex;
  flex-direction: column;
  flex: 1 1 auto; /* fills exactly the space between the fixed nav and the footer's natural height */
  min-height: 0; /* main is ALSO a flex item (of body) with its own default min-height: auto, which pulls a content-based floor from the grid's intrinsic size -- same fix as .portfolio-landing-grid's own min-height: 0 below, just one level up. Without this, main (and everything inside it) refuses to shrink past that floor regardless of how the grid itself is configured. */
}

.portfolio-landing-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* desktop: one row of 4 */
  grid-template-rows: minmax(0, 1fr); /* bare 1fr is really minmax(auto, 1fr) -- an IMPLICIT content-based (aspect-ratio-driven) minimum the track won't shrink below, which silently defeated the shrink half of flex: 1 on short viewports. minmax(0, 1fr) removes that floor so the row can go all the way down to whatever height is actually available, not just grow beyond its content size. */
  gap: 2px; /* thin dark seam between tiles, not a visible margin -- background shows through as the "border" */
  background: #000;
  padding-top: var(--nav-height); /* .site-nav is position: fixed and overlaps in-flow content -- this offset pushes the grid's first row below it, same pattern as .page-header uses elsewhere. Horizontal edges stay untouched (0 side padding) so the grid is still full-bleed. */
  flex: 1 1 auto; /* grows to fill <main> (see the comment on body.portfolio-landing-page main above for why this is flex, not height: 100%) */
  min-height: 0; /* flex items default to min-height: auto, which can refuse to shrink below the (large) aspect-ratio-driven content size -- without this, flex: 1 wouldn't be able to shrink the grid on short viewports */
}

.portfolio-tile {
  position: relative;
  display: block;
  overflow: hidden; /* clips the hover-scaled img so it can't spill past the tile edge */
  /* No aspect-ratio here (deliberately -- see below). .portfolio-tile-img is
     position: absolute, so it contributes zero intrinsic size to this box;
     with no aspect-ratio either, the tile has NO preferred size of its own,
     so the grid's 1fr row/column tracks (below) can size it to exactly
     fill whatever space is actually available, growing OR shrinking.
     object-fit: cover on the photo means whatever size that ends up being
     always looks intentional, never distorted.

     aspect-ratio: 4/5 used to be set here as the tile's "preferred" shape,
     on the theory that it'd be overridden once both width and height were
     pinned by the grid tracks -- confirmed live that's wrong: CSS Grid's
     fr tracks have no real shrink mechanism (unlike flexbox's
     flex-shrink). A track's base size comes from its items' content
     contribution, and fr only ever distributes LEFTOVER positive space on
     top of that base -- minmax(0, 1fr) does NOT override it. With
     aspect-ratio still set, the tile's aspect-ratio-derived preferred
     size (e.g. 435.6px tall at a 348.5px column width) became that base
     size, so the grid could grow past it but never shrink below it --
     exactly the "stuck at the old height regardless of viewport" bug this
     was fixed for. Removing it was the actual fix, not the flex/min-height
     changes above (those were real and necessary too, just not
     sufficient on their own). */
}

.portfolio-tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover; /* fills the tile completely, no letterboxing */
  transition: transform 0.6s var(--ease);
}

.portfolio-tile:hover .portfolio-tile-img {
  transform: scale(1.05); /* image only -- .portfolio-tile-name is a sibling, so it never moves */
}

.portfolio-tile-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.28); /* flat dark wash, not a top/bottom gradient -- the name sits at dead center, so legibility needs to hold uniformly across the whole tile, not just one edge */
}

.portfolio-tile-name {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-serif);
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.85), 0 6px 28px rgba(0, 0, 0, 0.6); /* two layers -- a tight dark edge plus a wide soft falloff -- so it stays legible against both bright and dark spots in the photo underneath */
  white-space: nowrap;
}

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

@media (max-width: 1024px) {
  .portfolio-landing-grid {
    grid-template-columns: repeat(2, 1fr); /* 2x2 */
    grid-template-rows: repeat(2, minmax(0, 1fr)); /* both rows share the fill-height space evenly, no implicit content-based floor -- see the base rule's comment */
  }
}

@media (max-width: 640px) {
  .portfolio-landing-grid {
    grid-template-columns: repeat(2, 1fr); /* same as tablet -- not dropping to 1 column */
    grid-template-rows: repeat(2, minmax(0, 1fr));
    gap: 2px;
  }

  .portfolio-tile-name {
    font-size: 1.3rem;
    letter-spacing: 0.12em;
  }
}

/* ---- Travel landing grid (travel/index.html) ----
   Reuses .portfolio-tile/.portfolio-tile-img/.portfolio-tile-overlay/
   .portfolio-tile-name exactly as-is (same cover photo + overlay +
   centered name + hover-zoom treatment as the Portfolio landing grid) --
   only the grid CONTAINER differs, because this list grows over time
   (one tile per Travel/ subfolder, added as trips happen) rather than
   being a fixed set of exactly 4 categories. Portfolio's grid is
   deliberately locked to fill exactly 100vh in a single row; forcing an
   open-ended, growing list into that same fixed-height/single-row shape
   would make tiles keep shrinking as more cities get added, so this is a
   normal-flow grid that just wraps to as many rows as it needs, scrolling
   like every other gallery page. */

.travel-landing-section {
  padding-bottom: 96px;
}

.travel-landing-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  background: #000;
}

/* .portfolio-tile has no aspect-ratio of its own (removed deliberately on
   the Portfolio page so its viewport-fill layout could stretch tiles) --
   scoped here instead of changed at the source, so this page's tiles get
   a sensible fixed shape without affecting the Portfolio page at all. */
.travel-landing-grid .portfolio-tile {
  aspect-ratio: 4 / 5;
}

/* Portfolio's .portfolio-tile-name is nowrap + sized for short single-word
   category names ("Editorial", "Portrait") -- city names run much longer
   ("Reykjavik, Iceland") and were visually overflowing past the tile edge
   on narrow tiles (confirmed via screenshot, not just computed values).
   Scoped here rather than changing the shared rule, so Portfolio's tiles
   are unaffected. */
.travel-landing-grid .portfolio-tile-name {
  white-space: normal;
  text-align: center;
  padding: 0 16px;
  font-size: clamp(1.1rem, 2.2vw, 2rem);
  line-height: 1.3;
}

.travel-placeholder-tile {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.travel-placeholder-tile svg {
  width: 30px;
  height: 30px;
  color: var(--text-faint);
}

.travel-placeholder-tile span {
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-faint);
}

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

@media (max-width: 640px) {
  .travel-landing-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 1px;
  }

  .travel-landing-grid .portfolio-tile-name {
    font-size: 1rem;
    letter-spacing: 0.1em;
    padding: 0 10px;
  }
}

/* ---- Press / Blog (blog/index.html, blog/{slug}.html) ----
   List page: eyebrow + big serif title/description intro, a filter-tabs +
   sort-dropdown control row (both handled client-side in js/press.js
   against the one already-fetched manifest -- no server round-trip for
   either), then the card grid. Not full-bleed like .portfolio-landing-grid
   -- press coverage reads more like an editorial index than a photo wall.
   Dark background throughout (var(--bg), inherited from body -- nothing
   page-specific to set). Article pages reuse .page-header/.container for
   shared chrome plus their own .article-* classes below. */

.press-intro {
  padding: calc(var(--nav-height) + 56px) 6vw 40px;
}

.press-eyebrow {
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 20px;
}

.press-intro-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 48px;
}

.press-intro-title {
  font-family: var(--font-serif);
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.05;
  color: var(--text);
  flex: 1 1 55%;
  overflow-wrap: break-word; /* a single unbroken long word/string still wraps instead of overflowing past the padding at narrow widths */
}

.press-intro-description {
  flex: 1 1 40%;
  max-width: 440px;
  color: var(--text-dim);
  font-size: 0.95rem;
  line-height: 1.65;
  overflow-wrap: break-word;
}

.press-controls {
  padding: 32px 6vw;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 20px;
}

.press-filter-tabs {
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}

.press-filter-tab {
  padding: 4px 0;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: var(--text-faint);
  border-bottom: 1px solid transparent;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.press-filter-tab:hover {
  color: var(--text-dim);
}

.press-filter-tab.is-active {
  color: var(--text);
  border-bottom-color: var(--text);
}

.press-sort {
  display: flex;
  align-items: center;
  gap: 10px;
}

.press-sort-label {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  color: var(--text-faint);
}

.press-sort-select {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  padding: 8px 12px;
  font-family: var(--font);
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  cursor: pointer;
}

.press-section {
  /* padding-top/-bottom only (not the padding shorthand) -- this section
     shares the .container class in the markup (padding: 0 6vw), and a
     full shorthand here would silently zero out that horizontal inset
     for every card in the grid, at every viewport width, not just
     mobile. Confirmed that was exactly what the old `padding: 56px 0
     100px;` rule did. */
  padding-top: 56px;
  padding-bottom: 100px;
}

.press-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 40px;
  /* align-items: start (not the grid default, stretch) -- thumbnails now
     render at their own real aspect ratio (see js/press.js), so cards in
     the same row are naturally different heights whenever orientations
     mix. Without this, a grid row stretches every card in it to match its
     tallest neighbor, and .press-card-content's flex-grow would absorb
     that leftover space as an awkward gap above "READ ARTICLE" instead of
     each card just being its own natural height. */
  align-items: start;
}

.press-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}

.press-card-image {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--bg-elevated);
  overflow: hidden;
}

.press-card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s var(--ease);
}

.press-card:hover .press-card-image img {
  transform: scale(1.04);
}

.press-card-badge {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 1;
  padding: 5px 10px;
  background: rgba(10, 10, 10, 0.78);
  backdrop-filter: blur(4px);
  color: var(--text);
  font-size: 0.65rem;
  letter-spacing: 0.12em;
  border: 1px solid rgba(255, 255, 255, 0.15);
}

.press-card-content {
  padding-top: 18px;
  display: flex;
  flex-direction: column;
  flex-grow: 1;
}

.press-card-meta {
  font-size: 0.75rem;
  color: var(--text-faint);
  margin-bottom: 10px;
}

.press-card-title {
  font-family: var(--font-serif);
  font-size: 1.3rem;
  font-weight: 400;
  line-height: 1.32;
  color: var(--text);
  margin-bottom: 10px;
  overflow-wrap: break-word;
}

.press-card:hover .press-card-title {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.press-card-description {
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text-dim);
  margin: 0 0 16px;
  flex-grow: 1;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: break-word;
}

.press-card-read {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--text);
}

.press-empty-message {
  color: var(--text-faint);
  padding: 48px 0;
}

@media (max-width: 720px) {
  .press-intro-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
  }

  .press-intro-description {
    max-width: none;
  }

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

  .press-filter-tabs {
    gap: 20px;
    overflow-x: auto;
    max-width: 100%;
    padding-bottom: 2px;
  }
}

/* ---- Press article page (blog/{slug}.html) ----
   Two-column layout: ~40% media column (image + credit caption) on the
   left, ~60% info column (back link, date/publication, title, body,
   external-link button) on the right -- single dark background
   (var(--bg)) throughout, no light section. .site-nav/.site-footer are
   untouched, same shared Publish-managed components every other page
   uses; this only styles what sits between them. Below the two columns,
   a full-width share/next-article row closes out the page. */

.article-post {
  padding: calc(var(--nav-height) + 48px) 6vw 0;
}

.article-layout {
  max-width: 1240px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 40% 1fr;
  gap: 64px;
  align-items: start;
}

/* No thumbnail: one centered column instead of an empty left half. */
.article-layout.article-layout--text-only {
  grid-template-columns: 1fr;
  max-width: 760px;
}

.article-media {
  position: sticky;
  top: calc(var(--nav-height) + 24px); /* stays in view while the info column's body text scrolls past it, a common long-form-article pattern */
}

.article-image {
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: #000;
  border-radius: 2px;
}

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

/* No uploaded thumbnail, article has a resolved video instead (see
   publishPressResolveFeaturedVideo() in admin/lib/publish-press.php) --
   same .article-image container/aspect-ratio, but an absolutely-
   positioned iframe filling it instead of an <img>. */
.article-image--video {
  position: relative;
}

.article-image--video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.article-media-caption {
  margin-top: 14px;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  color: var(--text-faint);
}

.article-info {
  min-width: 0;
}

.article-back-link {
  display: inline-block;
  margin-bottom: 36px;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.article-back-link:hover {
  color: var(--text);
}

.article-meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.article-title {
  font-family: var(--font-serif);
  font-size: clamp(2rem, 3.6vw, 2.75rem);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.22;
  color: var(--text);
  margin-bottom: 36px;
  overflow-wrap: break-word;
}

/* ---- Embedded media (article.embed_url -- YouTube/Vimeo/Instagram/TikTok,
   see publishPressEmbedHtml() in admin/lib/publish-press.php) ---- */

.article-embed {
  margin: 0 0 36px;
}

.article-embed--video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
}

.article-embed--video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Instagram/TikTok posts are natively tall/narrow, not 16:9 -- their
   embeds stay a centered, capped-width column instead of stretching full
   width the way a video embed does. */
.article-embed--instagram,
.article-embed--tiktok {
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
}

.article-embed--instagram iframe {
  width: 100%;
  aspect-ratio: 3 / 4;
  border: 0;
}

.article-embed--tiktok iframe {
  width: 100%;
  aspect-ratio: 9 / 16;
  border: 0;
}

.article-embed-link {
  margin: 0 0 36px;
}

.article-embed-link a {
  color: var(--text-dim);
  text-decoration: underline;
  word-break: break-all;
}

.article-embed-link a:hover {
  color: var(--text);
}

.article-body {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  line-height: 1.85;
  color: var(--text-dim);
  overflow-wrap: break-word;
}

.article-body p {
  margin-bottom: 1.5em;
}

.article-external-link {
  margin-top: 44px;
}

.article-external-link a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 28px;
  border: 1px solid var(--text);
  color: var(--text);
  text-decoration: none;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  transition: background 0.2s ease, color 0.2s ease;
}

.article-external-link a:hover {
  background: var(--text);
  color: #000;
}

/* ---- Share / next-article row (full width, closes out the page) ---- */

.article-share-row {
  max-width: 1240px;
  margin: 72px auto 0;
  padding: 32px 0 56px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.article-share-left {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}

.article-share-label {
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.article-share-icons {
  display: flex;
  align-items: center;
  gap: 12px;
}

.article-share-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.article-share-icon svg {
  width: 15px;
  height: 15px;
}

.article-share-icon:hover {
  color: var(--text);
  border-color: var(--text-dim);
}

.article-share-icon.is-copied {
  color: #8fe0a8;
  border-color: #8fe0a8;
}

.article-next-link {
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.article-next-link:hover {
  color: var(--text);
}

@media (max-width: 640px) {
  /* Comfortable, fixed horizontal breathing room on phone screens --
     longhand padding-left/-right only (never the padding shorthand,
     which is exactly what zeroed .press-section's own inset -- see its
     base rule above). 1.5rem instead of the base 6vw: at a 375px
     viewport 6vw is only ~22.5px, and a fixed rem value here reads more
     predictably than a value that keeps scaling down toward the very
     narrowest phones. .press-grid/.article-layout need no padding of
     their own -- both are plain children of these sections, so they
     inherit the same inset automatically. */
  .press-intro,
  .press-controls,
  .press-section,
  .article-post {
    padding-left: 1.5rem;
    padding-right: 1.5rem;
  }

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

  .article-post {
    padding-top: calc(var(--nav-height) + 24px);
  }

  .article-layout {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .article-media {
    position: static;
  }

  .article-image {
    aspect-ratio: 4 / 3;
  }

  .article-title {
    margin-bottom: 28px;
  }

  .article-share-row {
    flex-direction: column;
    align-items: flex-start;
    margin-top: 48px;
  }
}

/* ---- Fade-in on scroll utility (reusable on later pages, e.g. About) ---- */

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
}

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

/* ---- Cookie notice banner (js/cookie-notice.js, every public page) ----
   Small corner/bottom bar, not a full-screen takeover. z-index sits below
   .site-nav (1000) and .nav-toggle (1001) on purpose -- the fullscreen
   mobile nav panel is meant to cover this whenever it's open, so there's
   never a case where both are visible/interactive at once. Reuses the
   same solid, near-opaque dark panel treatment as the mobile nav
   (rgba(8, 8, 8, 0.98)) and the same secondary-button look already used
   for .contact-submit / .client-download-all-btn, rather than
   introducing a new button style for one element. */

.cookie-notice {
  position: fixed;
  left: 24px;
  right: 24px;
  bottom: 24px;
  z-index: 950;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  max-width: 640px;
  margin: 0 auto;
  padding: 16px 20px;
  background: rgba(8, 8, 8, 0.98);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
}

.cookie-notice-text {
  color: var(--text-dim);
  font-size: 0.85rem;
  line-height: 1.5;
  margin: 0;
}

.cookie-notice-text a {
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.cookie-notice-dismiss {
  flex-shrink: 0;
  border: 1px solid var(--text);
  background: transparent;
  color: var(--text);
  padding: 10px 20px;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}

.cookie-notice-dismiss:hover {
  background: var(--text);
  color: #000;
}

@media (max-width: 640px) {
  .cookie-notice {
    left: 12px;
    right: 12px;
    bottom: 12px;
    flex-direction: column;
    align-items: stretch;
    padding: 16px;
  }

  .cookie-notice-dismiss {
    align-self: stretch;
  }
}
