:root {
  --ink: #15151A;
  --paper: #F3EEE4;
  --indigo: #2B3A67;
  --ochre: #D98E2B;
  --rule: #C9C2B2;
  --red: #ba3c3c;
  --display: "Outfit", "Climate Crisis", "Fraunces", sans-serif;
  --body: "Inter", sans-serif;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; overflow-x: hidden; }

/* Header background bar — dark fill behind the logo/nav, which now
   stay in their light styling everywhere except over an open popup
   (see the JS). Fades in once scrolled past the hero. */
.header-bar-bg {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 90px;
  z-index: 240;
  background: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.header-bar-bg.is-visible {
  opacity: 1;
}
@media (max-width: 600px) {
  .header-bar-bg { height: 70px; }
}

/* Scroll-to-top — dark circle, paper-colored arrow, fixed
   bottom-right. Hidden until scrolled past the hero, and hidden again
   whenever any popup is open (handled in JS via the [hidden] attribute). */
.scroll-top-btn {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 250;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: none;
  background: var(--ink);
  color: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.15s ease;
}
.scroll-top-btn.is-visible {
  opacity: 1;
  pointer-events: auto;
}
.scroll-top-btn svg {
  width: 20px;
  height: 20px;
}
.scroll-top-btn:hover {
  transform: translateY(-2px);
}
.scroll-top-btn:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
@media (max-width: 600px) {
  .scroll-top-btn { bottom: 1rem; right: 1rem; width: 42px; height: 42px; }
  .scroll-top-btn svg { width: 17px; height: 17px; }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-top-btn { transition: none; }
}

/* Site logo — fixed top-left, mirrors social icons */
.site-logo {
  position: fixed;
  top: 1.25rem;
  left: 1.25rem;
  z-index: 250;
}
.site-logo a {
  display: flex;
  align-items: center;
}
.site-logo img {
  height: 34px;
  width: auto;
  display: block;
}
.site-logo a:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
@media (max-width: 600px) {
  .site-logo { top: 0.85rem; left: 0.85rem; }
  .site-logo img { height: 26px; }
}

/* Header nav — fixed top-right, persistent across scroll. Combines
   the Work/About/Contact links with the social icons as one group so
   they don't read as two different UI languages sitting side by side.
   No background chip: color alone carries contrast, switched
   light/dark based on scroll position past the hero (see JS). */
.header-nav {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 250;
  display: flex;
  align-items: center;
  gap: 1.1rem;
}
.nav-link {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--paper);
  background: transparent;
  padding: 0.35rem 0.75rem;
  text-decoration: none;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.nav-link:hover {
  background: var(--ink);
}
.nav-link.is-active {
  background: var(--ochre);
}
.nav-link:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
.header-nav.is-past-hero .nav-link {
  color: var(--ink);
}
.header-nav.is-past-hero .nav-link:hover {
  background: #FFFFFF;
}
.header-nav.is-past-hero .nav-link.is-active {
  background: var(--ochre);
}
.nav-divider {
  width: 1px;
  height: 16px;
  background: currentColor;
  color: var(--paper);
  opacity: 0.35;
  transition: color 0.2s ease;
}
.header-nav.is-past-hero .nav-divider {
  color: var(--ink);
}
.social-icons {
  display: flex;
  gap: 0.6rem;
}
.social-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--paper);
  transition: color 0.2s ease, opacity 0.2s ease;
}
.header-nav.is-past-hero .social-icon {
  color: var(--ink);
}
.social-icon svg {
  width: 18px;
  height: 18px;
}
.social-icon:hover {
  opacity: 0.65;
}
.social-icon:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
@media (max-width: 600px) {
  .header-nav { top: 0.85rem; right: 0.85rem; gap: 0.7rem; }
  .nav-link { font-size: 0.78rem; }
  .social-icons { gap: 0.4rem; }
  .social-icon { width: 34px; height: 34px; }
  .social-icon svg { width: 15px; height: 15px; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 0.9375rem;
  line-height: 1.5;
}

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

a { color: inherit; }

/* Masthead */
.masthead {
  position: sticky;
  top: 0;
  z-index: 1;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  border-bottom: 1px solid var(--rule);
  background: var(--ink); /* shows before video paints / if video fails */
}
.masthead-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.masthead-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(21,21,26,0.82) 0%, rgba(21,21,26,0.35) 55%, rgba(21,21,26,0.15) 100%);
  z-index: 1;
}
.masthead-inner {
  position: relative;
  z-index: 2;
  max-width: 780px;
  padding: 3rem 1.5rem 4rem;
  color: var(--paper);
}
.eyebrow-plain {
  font-size: 0.8rem;
  color: var(--ochre);
  margin: 0 0 0.5rem;
}
.masthead h1 {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: 900;
  font-size: clamp(3.2rem, 9vw, 6rem);
  line-height: 0.98;
  margin: 0 0 1.2rem;
  letter-spacing: -0.01em;
  color: var(--paper);
}
.role {
  font-size: 1.05rem;
  max-width: 52ch;
  margin: 0 0 2rem;
  color: var(--paper);
  line-height: 1.55;
}
.role-link {
  color: var(--ochre);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.cta {
  display: inline-block;
  padding: 0.75rem 1.4rem;
  text-decoration: none;
  font-weight: 500;
  border: 1px solid var(--paper);
}
.cta-primary { background: var(--paper); color: var(--ink); }
.cta-primary:hover { background: var(--ochre); border-color: var(--ochre); color: var(--ink); }
.cta-secondary { background: transparent; color: var(--paper); }
.cta-secondary:hover { background: var(--paper); color: var(--ink); }
.project-view-link {
  display: inline-block;
  margin: 0.25rem 0 1rem;
}
.project-view-link[hidden] {
  display: none;
}

/* Respect reduced motion: freeze on poster frame instead of autoplaying */
@media (prefers-reduced-motion: reduce) {
  .masthead-video { display: none; }
  .masthead {
    background-image: var(--poster-fallback, none);
    background-size: cover;
    background-position: center;
  }
}

/* Client logo marquee */
/* Solid, unmasked backdrop — sits behind the actual masked marquee
   below. mask-image fades an element's entire rendering, background
   included, so without this separate solid layer, the faded edges
   would let whatever's behind in the parallax stack (the sticky hero
   video) show through instead of fading to solid paper. */
.client-marquee-wrap {
  position: relative;
  z-index: 2;
  background: var(--paper);
}
.client-marquee {
  overflow: hidden;
  padding: 1.75rem 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, black 30%, black 70%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, black 30%, black 70%, transparent 100%);
}
.marquee-track {
  display: flex;
  align-items: center;
  width: max-content;
  animation: marquee-scroll 40s linear infinite;
}
.marquee-logo {
  height: 40px;
  width: auto;
  margin: 0 1.5rem;
  flex-shrink: 0;
  filter: grayscale(100%) contrast(0.9) brightness(0.85);
  opacity: 0.6;
  pointer-events: none;
  user-select: none;
}
@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Reduced motion: static wrapped row, no animation, no duplicate set needed
   visually (the JS still renders it twice, so hide the second copy). */
@media (prefers-reduced-motion: reduce) {
  .marquee-track {
    animation: none;
    flex-wrap: wrap;
    width: auto;
    justify-content: center;
  }
  .marquee-track img.marquee-dup {
    display: none;
  }
}

/* Section shared */
main {
  position: relative;
  z-index: 2;
  background: var(--paper);
}
main section {
  padding: 4rem 1.5rem;
  max-width: 1100px;
  margin: 0 auto;
}
/* Selected Work + More Work only — wider than the standard content
   width so the grid (and its cards) get more room, without widening
   Thud/About/Contact which share this same base rule. */
#work {
  max-width: 1450px;
}
h2 {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.1rem, 4.5vw, 3rem);
  margin: 0 0 0.35rem;
}
.title-accent {
  color: var(--red);
}

/* Work grid */
.work-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
}
/* Settle-in entrance: each card starts offset by its own --enter-y
   (varied per item for a layered "different planes" feel), then
   settles into place once #work-grid scrolls ~15% into view (see JS).
   One-shot — triggers once and stays settled regardless of scroll
   direction afterward. Scoped to #work-grid specifically so it doesn't
   affect the More Work grid. */
#work-grid .work-item {
  opacity: 0;
  transform: translateY(var(--enter-y, 50px)) scale(0.97);
  transition: opacity 0.6s ease, transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--enter-delay, 0ms);
}
#work-grid.in-view .work-item {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  #work-grid .work-item {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
}
.more-work-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  margin-top: 2rem;
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.4s ease;
}
.more-work-grid:empty {
  margin-top: 0;
}
@media (prefers-reduced-motion: reduce) {
  .more-work-grid {
    transition: none;
  }
}
.work-item.span-2 { grid-column: span 2; }
.work-item.span-1 { grid-column: span 1; }

/* work-item is now a <button> — strip default button chrome */
.work-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  position: relative;
  z-index: 0;
}
.work-item::before {
  content: '';
  position: absolute;
  inset: -0.75rem;
  background: #FFFFFF;
  z-index: -1;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.work-item:hover::before {
  opacity: 1;
}
.work-item:focus-visible {
  outline: 2px solid var(--indigo);
  outline-offset: 4px;
}
/* The [hidden] attribute alone gets overridden by the display:block
   rule above (author CSS beats user-agent CSS regardless of
   specificity) — this compound selector wins back the actual hiding. */
.work-item[hidden] {
  display: none;
}

.more-work-row {
  margin-top: 2.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}
.more-work-row .cta-secondary,
.cta.cta-dark {
  border-color: var(--ink);
  color: var(--ink);
}
.more-work-row .cta-secondary:hover,
.cta.cta-dark:hover {
  background: var(--ink);
  color: var(--paper);
}
.work-item-extra {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}
.work-item-extra.is-visible {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .work-item-extra {
    transition: none;
    opacity: 1;
    transform: none;
  }
}
/* Landscape rather than square — scoped to the "more work" extras
   only, using both classes for enough specificity to beat the plain
   .work-item.span-1 rule below. */
.work-item.span-1.work-item-extra .work-image {
  aspect-ratio: 4 / 3;
}
.work-image {
  aspect-ratio: 16 / 10;
  margin-bottom: 0.75rem;
  overflow: hidden;
}
/* span-1 is half the width of span-2 — this ratio is calculated (not
   guessed) to match span-2's actual height exactly at the content's
   1100px max-width, accounting for the 1.25rem grid gap. It'll drift
   by a percent or two at in-between viewport widths since the gap
   doesn't scale proportionally with the columns, but stays accurate
   at the width most people will actually view this at. */
.work-item.span-1 .work-image {
  aspect-ratio: 10 / 13;
}
.work-image.placeholder {
  background: repeating-linear-gradient(45deg, #E3DCCB, #E3DCCB 10px, #D8D0BB 10px, #D8D0BB 20px);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  color: var(--indigo);
}
.work-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.3s ease;
}
.work-item:hover .work-image img {
  transform: scale(1.15);
}
.work-item h3 { margin: 0 0 0.3rem; font-size: 0.9375rem; }
.work-item p { margin: 0; font-size: 0.8rem; color: #4A4A4A; }

@media (max-width: 700px) {
  .work-grid { grid-template-columns: 1fr; }
  .more-work-grid { grid-template-columns: repeat(2, 1fr); }
  .work-item.span-2, .work-item.span-1 { grid-column: span 1; }
  .work-item.span-1 .work-image,
  .work-item.span-1.work-item-extra .work-image { aspect-ratio: 16 / 10; }
}

/* Project detail overlay — slides up from the bottom, takes over the page */
.project-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(21, 21, 26, 0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.project-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}
.project-overlay-inner {
  /* position:relative (not absolute) is required here — Safari has a
     documented bug where position:sticky fails for any descendant
     when its scrolling ancestor (this element) is itself
     position:absolute with overflow:auto/scroll. Chrome and Firefox
     handle the absolute case fine; Safari doesn't. Sized with
     explicit width/height instead of inset, since relative
     positioning doesn't stretch to fill via top/left/right/bottom
     the way absolute does. */
  position: relative;
  width: 100%;
  height: 100%;
  top: 100%;
  background: var(--paper);
  transition: top 0.35s ease;
  overflow-y: auto;
}
.project-overlay.is-open .project-overlay-inner {
  top: 0;
}
@media (prefers-reduced-motion: reduce) {
  .project-overlay, .project-overlay-inner { transition: none; }
}
.project-close {
  position: sticky;
  top: 6.5rem;
  margin-left: calc(100% - 1.25rem - 44px);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--ink);
  z-index: 3;
}
.project-close svg { width: 18px; height: 18px; }
.project-close:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
.project-overlay-content {
  position: relative;
  z-index: 2;
  background: var(--paper);
  max-width: 900px;
  margin: 1.5rem auto 0;
  padding: 0 1.5rem;
}
/* Fun projects popup only — wider so 3 columns have room, without
   widening the per-project popup which shares this same base class. */
#fun-overlay .project-overlay-content {
  max-width: 1300px;
}
/* Collection popups (opened from a fun-project tile) match the same
   width as the fun-projects grid they're opened from. */
#collection-overlay .project-overlay-content {
  max-width: 1300px;
}
/* Project popup (opened from either Selected Work or More Work — both
   use this same overlay) matches the widened #work section on the
   main page. Also guarantees at least one viewport height of content,
   scoped to just this popup (not fun/collection, which don't have a
   sticky image and don't need the extra guaranteed height) — so the
   sticky main image always has enough room to fully release before
   the footer, regardless of how short a given project's own
   description/gallery happens to be. */
#project-overlay .project-overlay-content {
  max-width: 1450px;
  min-height: 100vh;
}
.project-client {
  font-size: 0.78rem;
  font-weight: 700;
  margin-bottom: 0.75rem;
  color: var(--ochre);
}
.project-client:empty {
  display: none;
}
.field-label {
  font-weight: 400;
}
#project-focus {
  margin-top: 0;
}
.project-overlay-content h2 {
  margin-top: 0;
  margin-bottom: 0.35rem;
}
.project-credits {
  font-size: 0.78rem;
  font-weight: 700;
  margin: 0 0 0.75rem;
  color: var(--ochre);
}
.project-credits:empty {
  display: none;
}
/* Approximates #project-overlay .project-overlay-content's box model
   (max-width minus its 3rem of horizontal padding) directly on the
   sticky element itself — not via a wrapper. A wrapper here would
   collapse position:sticky's containing block down to exactly the
   image's own height, leaving zero room to actually stick (this was
   a real, previously-shipped bug, not a hypothetical). This
   approximation is only exact once the viewport is wide enough for
   1450px to be the binding constraint; below that it can drift by a
   few pixels from the grid's own edges, which is an acceptable
   trade-off for a working sticky effect. */
.project-image {
  position: sticky;
  top: 0;
  z-index: 1;
  width: 100%;
  max-width: calc(1450px - 3rem);
  margin: 0 auto 1.5rem;
  display: block;
  overflow: hidden;
}
.project-image.placeholder {
  aspect-ratio: 21 / 9;
  background: repeating-linear-gradient(45deg, #E3DCCB, #E3DCCB 10px, #D8D0BB 10px, #D8D0BB 20px);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--indigo);
}
.project-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
#project-description {
  white-space: pre-line;
  margin-top: 0;
  max-width: 65ch;
}
#collection-description {
  white-space: pre-line;
}
#collection-description:empty {
  display: none;
}
.project-gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.6rem;
  margin: 2rem 0;
  align-items: start;
}
/* Collection popups only (ABC Comics, etc.) — 4 columns, without
   touching the 2-column galleries in individual project popups, which
   share this same base class. */
#collection-grid {
  grid-template-columns: repeat(4, 1fr);
}
.gallery-tile {
  position: relative;
  width: 100%;
}
.gallery-tile-span-2 {
  grid-column: span 2;
}
.gallery-media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #EDE7DA;
  position: relative;
}
.gallery-media img,
.gallery-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Collection popups only (ABC Comics, etc.) — square, without
   touching the 4:3 galleries in individual project popups. */
#collection-grid .gallery-media {
  aspect-ratio: 1 / 1;
}
.gallery-tile.placeholder {
  aspect-ratio: 4 / 3;
  background: repeating-linear-gradient(45deg, #E3DCCB, #E3DCCB 10px, #D8D0BB 10px, #D8D0BB 20px);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  color: var(--indigo);
}
.gallery-tile img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: transform 0.3s ease;
}
.gallery-tile video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: transform 0.3s ease;
}
.gallery-tile:hover img,
.gallery-tile:hover video {
  transform: scale(1.15);
}
.gallery-tile p {
  width: 100%;
  margin: 0.6rem 0 0;
  font-size: 0.8rem;
  color: #4A4A4A;
  text-align: center;
}
@media (max-width: 600px) {
  .project-gallery { grid-template-columns: 1fr; }
}
.project-overlay-footer {
  margin-top: 3rem;
}
.project-overlay-footer p {
  margin: 0;
  max-width: 900px;
  margin-left: auto;
  margin-right: auto;
  font-size: 0.85rem;
}

/* Fun projects overlay — flat grid, no per-item detail */
.fun-projects-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  margin-top: 2.5rem;
  align-items: start;
}
.fun-category-header {
  grid-column: 1 / -1;
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.3rem;
  color: var(--ink);
  margin: 2.5rem 0 0.25rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  /* So a scrolled-to header doesn't land flush behind the sticky
     close button, which stays fixed near the top of the popup. */
  scroll-margin-top: 6.5rem;
}
.fun-category-header:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.fun-submenu {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 1.25rem 0 0;
}
.fun-project-image {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: #EDE7DA;
  margin-bottom: 0.6rem;
  position: relative;
}
/* Instagram's embed widget sizes and scrolls itself — the fixed 4:3
   crop box that works for images/video would just clip it. */
.fun-project-image.fun-project-embed {
  aspect-ratio: auto;
  overflow: visible;
  background: none;
  display: flex;
  justify-content: center;
}
.fun-project-image.placeholder {
  background: repeating-linear-gradient(45deg, #E3DCCB, #E3DCCB 10px, #D8D0BB 10px, #D8D0BB 20px);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  color: var(--indigo);
}
.fun-project-image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: transform 0.3s ease;
}
.fun-project-image video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: transform 0.3s ease;
}
.fun-project-tile:hover .fun-project-image img,
.fun-project-tile:hover .fun-project-image video {
  transform: scale(1.15);
}
/* Play icon overlaid on video thumbnails — pointer-events: none so
   clicks pass through to the actual video element beneath it, which
   is what carries the .lightbox-trigger click handling. */
.play-icon {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.play-icon svg {
  width: 44px;
  height: 44px;
  color: var(--paper);
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.6));
}
.fun-project-tile {
  position: relative;
  z-index: 0;
}
.fun-project-tile::before {
  content: '';
  position: absolute;
  inset: -0.75rem;
  background: #FFFFFF;
  z-index: -1;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.fun-project-tile:hover::before {
  opacity: 1;
}
.fun-project-tile p {
  margin: 0;
  font-size: 0.8rem;
  color: #4A4A4A;
}
@media (max-width: 700px) {
  .fun-projects-grid { grid-template-columns: 1fr; }
}
@media (min-width: 701px) and (max-width: 1000px) {
  .fun-projects-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Body lock while overlay is open. html also needs to be explicitly
   locked here — once html has any explicit overflow-x value set (for
   the earlier horizontal-scrollbar fix), the browser stops letting
   body's own overflow:hidden propagate up to the page-level
   scrollbar, so without this, body locks correctly but html's own
   scrollbar keeps showing right alongside the popup's internal one. */
body.overlay-open {
  overflow: hidden;
}
html:has(body.overlay-open) {
  overflow-y: hidden;
}

/* Lightbox — click any popup image to view it full size */
.lightbox-trigger {
  cursor: zoom-in;
}
.collection-trigger {
  cursor: pointer;
}
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(21, 21, 26, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem 1.5rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.lightbox.is-open {
  opacity: 1;
  pointer-events: auto;
}
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
  cursor: default;
}
.lightbox video {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: none;
}
.lightbox iframe {
  display: none;
  width: 90vw;
  max-width: 800px;
  aspect-ratio: 16 / 9;
  height: auto;
  border: 0;
}
.lightbox iframe.is-vertical {
  aspect-ratio: 9 / 16;
  width: auto;
  height: min(85vh, 700px);
  max-width: 92vw;
}
.project-close.lightbox-close {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  margin-left: 0;
}
.lightbox-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--ink);
  z-index: 1;
}
.lightbox-nav svg { width: 20px; height: 20px; }
.lightbox-nav:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
.lightbox-prev { left: 1.25rem; }
.lightbox-next { right: 1.25rem; }
@media (max-width: 600px) {
  .lightbox-nav { width: 40px; height: 40px; }
  .lightbox-prev { left: 0.6rem; }
  .lightbox-next { right: 0.6rem; }
}
/* Project popup prev/next — same treatment as the lightbox's nav
   arrows, just scoped to a different class so both can coexist (the
   lightbox can open on top of a project popup for its images). */
.project-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--ink);
  z-index: 3;
}
.project-nav svg { width: 20px; height: 20px; }
.project-nav:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
.project-nav-prev { left: 1.25rem; }
.project-nav-next { right: 1.25rem; }
@media (max-width: 600px) {
  .project-nav { width: 40px; height: 40px; }
  .project-nav-prev { left: 0.6rem; }
  .project-nav-next { right: 0.6rem; }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox {
    transition: none;
  }
}

/* Thud */
.thud-intro { max-width: none; }
.panel-strip {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0.75rem;
  margin-top: 1.5rem;
}
.panel.placeholder {
  aspect-ratio: 3 / 4;
  border: 2px solid var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  background: var(--paper);
  overflow: hidden;
}
.panel img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.3s ease;
}
.panel:hover img {
  transform: scale(1.15);
}
@media (max-width: 700px) {
  .panel-strip { grid-template-columns: repeat(2, 1fr); }
}

/* About / freelance */
.about-section {
  position: relative;
  color: var(--paper);
  z-index: 2;
  padding-top: 8rem;
  padding-bottom: 8rem;
}
/* Full-bleed dark background via a pseudo-element instead of
   clip-path on the section itself — clip-path (and similarly
   transform/filter) on an ancestor is known to break how
   position:sticky calculates its scroll container for descendants,
   which is exactly what .about-lego-decoration below needs to work. */
.about-section::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  transform: translateX(-50%);
  background: var(--ink);
  z-index: -1;
}
/* Fixed at the bottom of the section — no scroll animation, just
   anchored in place. */
.about-lego-decoration {
  position: absolute;
  bottom: 1rem;
  right: 0.5rem;
  width: 220px;
  pointer-events: none;
  z-index: 1;
}
@media (max-width: 900px) {
  .about-lego-decoration { width: 150px; right: 0.25rem; }
}
@media (max-width: 700px) {
  /* Not enough room next to the stacked mobile layout — hide rather
     than have it overlap the photo or text awkwardly. */
  .about-lego-decoration { display: none; }
}
.about-grid {
  display: grid;
  grid-template-columns: 560px 1fr;
  gap: 2.5rem;
  align-items: start;
}
.about-photo {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border: 1px solid var(--rule);
  display: block;
}
.about-copy h2 { margin-top: 0; }
.availability-label {
  color: var(--ochre);
  font-weight: 700;
}
.availability-highlight {
  background: var(--ochre);
  color: var(--ink);
  padding: 0.6rem 0.9rem;
  margin: 0 0 0.6rem;
}
.highlight-label {
  color: #FFFFFF;
  font-weight: 700;
}
@media (max-width: 700px) {
  .about-grid { grid-template-columns: 1fr; }
  .about-photo { max-width: 260px; }
}
.about-section p, .freelance-section p { max-width: 65ch; }

/* Contact */
.contact-section a {
  font-family: var(--body);
  font-weight: 700;
  font-size: 1.4rem;
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* Contact */
.contact-intro { margin-bottom: 2rem; }
.contact-intro a {
  font-family: var(--display);
  font-size: 1.15rem;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.contact-form {
  max-width: 520px;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
.form-row {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.form-row label {
  font-size: 0.8rem;
  font-weight: 500;
}
.form-row input,
.form-row textarea {
  font-family: var(--body);
  font-size: 0.9rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--rule);
  background: #fff;
  color: var(--ink);
  border-radius: 0;
}
.form-row input:focus,
.form-row textarea:focus {
  outline: 2px solid var(--indigo);
  outline-offset: 1px;
}
.contact-form button {
  align-self: flex-start;
  cursor: pointer;
}
.form-status {
  margin: 0;
  font-size: 0.8rem;
  min-height: 1.2em;
}
.form-status-success { color: var(--indigo); }
.form-status-error { color: var(--red); }

/* Honeypot — hidden from sighted users and screen readers, but still
   present in the DOM/tab order removed, so bots that don't render CSS
   or that blindly fill every field will trip it. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

footer {
  position: relative;
  z-index: 2;
  padding: 2rem 1.5rem 3rem;
  font-size: 0.78rem;
  background: var(--ochre);
  color: var(--ink);
  text-align: center;
}

/* Keyboard focus visibility */
a:focus-visible, .cta:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .work-image img,
  .gallery-tile img,
  .gallery-tile video,
  .fun-project-image img,
  .fun-project-image video,
  .panel img {
    transition: none;
  }
  .work-item:hover .work-image img,
  .gallery-tile:hover img,
  .gallery-tile:hover video,
  .fun-project-tile:hover .fun-project-image img,
  .fun-project-tile:hover .fun-project-image video,
  .panel:hover img {
    transform: none;
  }
}
