/* ===========================================
   FONT DECLARATIONS
   =========================================== */
/* UNUSED - Commented out to improve load speed
@font-face {
  font-family: "Lemon Milk";
  src: url("../fonts/LEMONMILK-Regular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: "Lemon Milk Italic";
  src: url("../fonts/LEMONMILK-RegularItalic.otf") format("opentype");
  font-weight: 400;
  font-style: italic;
}
*/

@font-face {
  font-family: "Lemon Milk Light";
  src: url("../fonts/LEMONMILK-Light.otf") format("opentype");
  font-weight: 300;
  font-style: normal;
}

/* UNUSED - Commented out to improve load speed
@font-face {
  font-family: "Lemon Milk Light Italic";
  src: url("../fonts/LEMONMILK-LightItalic.otf") format("opentype");
  font-weight: 300;
  font-style: italic;
}
*/

@font-face {
  font-family: "Lemon Milk Medium";
  src: url("../fonts/LEMONMILK-Medium.otf") format("opentype");
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: "Lemon Milk Medium Italic";
  src: url("../fonts/LEMONMILK-MediumItalic.otf") format("opentype");
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: "Lemon Milk Bold Italic";
  src: url("../fonts/LEMONMILK-BoldItalic.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
}

/* ===========================================
   RESET & BASE STYLES
   =========================================== */
/* Border-box everywhere. Without this, any `width: 100%` element that also has
   padding is wider than its container — which is exactly how .workflow-panel
   was pushing the document 19px past the viewport in the 640-900px range. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body, h1, h2, h3, p, ul {
  margin: 0;
  padding: 0;
  border: 0;
}

html {
  scroll-behavior: smooth;
}

body {
  background-color: #3B3B40;
  font-family: Arial, sans-serif;
  background-image: 
    url("data:image/svg+xml,%3Csvg viewBox='0 0 2000 2000' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='10' numOctaves='10' stitchTiles='stitch' result='turbulence'/%3E%3CfeComponentTransfer in='turbulence'%3E%3CfeFuncR type='linear' slope='0.5' intercept='0.063'/%3E%3CfeFuncG type='linear' slope='0.3' intercept='0.725'/%3E%3CfeFuncB type='linear' slope='0.1' intercept='0.506'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.15'/%3E%3C/svg%3E");
  position: relative;
  overflow-x: hidden;
}

/* Ambient background wash.
   This used to be a 200% x 200% fixed layer rotating on a 30s infinite loop.
   Rotating a surface four times the area of the viewport forces the compositor
   to re-rasterise it continuously, at 2x on a retina display, for the entire
   life of the page — and it ran underneath every other animation on the site.
   The gradients are between 0.04 and 0.08 alpha, so holding them still costs
   nothing visually and returns that budget to the scroll animations. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background:
    radial-gradient(circle at 20% 30%, rgba(4, 108, 78, 0.08) 0%, transparent 50%),
    radial-gradient(circle at 80% 70%, rgba(16, 185, 129, 0.06) 0%, transparent 50%),
    radial-gradient(circle at 50% 50%, rgba(4, 108, 78, 0.04) 0%, transparent 60%);
  pointer-events: none;
  z-index: 0;
}

section, header {
  scroll-margin-top: 110px;
}

/* ===========================================
   NAVIGATION
   =========================================== */
nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  z-index: 9999;
  /* Shares --content-inset with every section, so the wordmark sits on the same
     left edge as the headings below it. It used to run its own
     clamp(16px, 4vw, 46px), which put the logo 26px left of "WHAT WE DO." at
     1440 — the ragged edge --content-inset exists to prevent, in the one bar
     that never got folded into that pass. */
  padding: 14px var(--content-inset);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background .45s ease, backdrop-filter .45s ease,
              border-color .45s ease, padding .45s ease,
              opacity .5s ease, transform .5s ease;
}

/* Past the hero, the bar earns a background so headings stop colliding
   with the wordmark. Toggled by main.js. */
nav.is-scrolled {
  background: rgba(18, 20, 21, 0.72);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: rgba(255, 255, 255, 0.09);
  padding-top: 8px;
  padding-bottom: 8px;
}

nav ul {
  list-style: none;
  display: flex;
  justify-content: right;
  align-items: center;
  gap: clamp(2px, 0.6vw, 12px);
  padding: 0;
  margin: 0;
}

nav a {
  font-family: "Lemon Milk Medium Italic", sans-serif;
  color: white;
  text-decoration: none;
  font-weight: 400;
  font-size: 0.9rem;
  background-color: transparent;
  padding: 6px var(--nav-link-pad-x);
  border-radius: 6px;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55);
  transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  position: relative;
  overflow: hidden;
}

/* Morphing underline effect */
nav a::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 0;
  height: 2px;
  background: linear-gradient(90deg, #046C4E, #10B981);
  transform: translateX(-50%);
  transition: width 0.4s ease;
}

nav a:hover::after {
  width: 80%;
}

nav a:hover {
  background-color: rgba(4, 108, 78, 0.3);
  transform: translateY(-3px);
  box-shadow: 0 5px 20px rgba(4, 108, 78, 0.3);
}

.brand {
  font-family: "Lemon Milk Bold Italic", sans-serif;
  font-size: clamp(1.5rem, 3.4vw, 2.4rem);
  color: white;
  font-weight: bold;
  text-decoration: none;
  flex: 0 0 auto;
  position: relative;
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.5);
  transition: font-size .45s ease;

  /* The wordmark is an <a> inside the nav, so it picks up `nav a`'s hit-area
     padding and its glyphs start 14px inside its own box. Cancelling that on the
     leading side puts the ink itself on --content-inset instead of the box, which
     is what the eye compares against the headings. The padding is left intact —
     it is still the click and focus-ring target.

     Deliberately a physical margin. margin-inline-start resolves against the
     ELEMENT's own direction, and .brand is forced direction:ltr in Arabic
     because the wordmark stays Latin — so the logical property still computed to
     margin-left under [dir=rtl] and pulled the logo 14px inward rather than
     outward. Measured 100px from the right edge against the headings' 86px. */
  margin-left: calc(var(--nav-link-pad-x) * -1);
}

/* The mirror the logical property could not give us. */
[dir="rtl"] .brand {
  margin-left: 0;
  margin-right: calc(var(--nav-link-pad-x) * -1);
}

nav.is-scrolled .brand {
  font-size: clamp(1.25rem, 2.2vw, 1.6rem);
}

/* Skip link for keyboard users */
.skip-link {
  position: absolute;
  top: -60px;
  left: 12px;
  z-index: 10001;
  background: var(--green-bright, #10B981);
  color: #06231a;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .85rem;
  padding: 10px 18px;
  border-radius: 8px;
  text-decoration: none;
  transition: top .25s ease;
}

.skip-link:focus { top: 12px; }

/* ===========================================
   TYPOGRAPHY - CINEMATIC REVEALS
   =========================================== */
h2, h3 {
  font-family: "Lemon Milk Light", sans-serif;
  font-weight: 600;
  color: white;
}

/* Headings are visible by default. Reveal states live behind html.js so the
   page still reads correctly with JavaScript off or blocked. */
h2 {
  font-size: clamp(2rem, 6vw, 4rem);
  position: relative;
  display: inline-block;
  margin-bottom: 40px;
  line-height: 1.05;
}

/* Animated writing underline — drawn when the heading enters view */
h2.reveal-heading::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -10px;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, #046C4E, #10B981, #046C4E);
  background-size: 200% 100%;
  border-radius: 2px;
  transform-origin: left center;
}

html.js h2.reveal-heading::after {
  transform: scaleX(0);
  transition: transform 1.1s cubic-bezier(.16, 1, .3, 1) .15s;
}

html.js h2.reveal-heading.is-revealed::after {
  transform: scaleX(1);
  /* Two passes, not infinite. This animates background-position, which repaints
     every frame — six of them were doing that for the whole life of the page,
     including while off screen, for an effect nobody sees after the first look. */
  animation: underline-shimmer 3s ease-in-out 1.4s 2;
}

@keyframes underline-shimmer {
  0%, 100% { background-position: 0% 0%; }
  50% { background-position: 100% 0%; }
}

/* Line-mask reveal: each line sits in an overflow-hidden wrapper and slides up.
   main.js wraps the text; without it, the plain heading shows. */
.line-mask {
  display: block;
  overflow: hidden;
  padding-bottom: 0.08em;
}

.line-inner {
  display: block;
  will-change: transform;
}

h3 {
  font-size: clamp(1.3rem, 3vw, 1.8rem);
}

p {
  font-family: "IBM Plex Sans", sans-serif;
  font-weight: 400;
  color: white;
}

/* ===========================================
   SECTIONS
   =========================================== */
/* The old blanket `opacity:0 + animation` fired on page load for every
   section at once, regardless of scroll position, and left content invisible
   if the animation never ran. Reveals are per-element and scroll-driven now. */
section {
  margin-bottom: var(--section-gap);
  padding: var(--section-pad-y) var(--content-inset);
  position: relative;
}

/* ===========================================
   HERO VIDEO
   =========================================== */
.hero {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: #0b0d0d;
  scroll-margin-top: 0;
  /* Was driven purely by the video's 16:5 aspect, which left about 450px of
     letterbox — not enough to hold a headline, a subline and two buttons.
     The hero now sets its own height and the video fills it. */
  min-height: min(78vh, 760px);
  display: flex;
}

#video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: brightness(0.95) contrast(1.05);
}

/* ===========================================
   IMPACT SECTION - DRAMATIC REVEALS
   =========================================== */
#impact {
  text-align: center;
  margin: 0px auto;
  padding: clamp(44px, 6vw, 88px) var(--content-inset);
  position: relative;
  /* Clips the decorative sideways drift on the slides so it can never widen
     the document and create a horizontal scrollbar. */
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  
/* Darker green background with green-tinted grain */
background-color: #035c43;
background-image: 
  url("data:image/svg+xml,%3Csvg viewBox='0 0 2000 2000' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilterGray'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='3.5' numOctaves='8' stitchTiles='stitch' result='turbulence'/%3E%3CfeComponentTransfer in='turbulence'%3E%3CfeFuncR type='linear' slope='0.5' intercept='0.010'/%3E%3CfeFuncG type='linear' slope='0.5' intercept='0.270'/%3E%3CfeFuncB type='linear' slope='0.5' intercept='0.145'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilterGray)' opacity='0.45'/%3E%3C/svg%3E");

}

/* Rotating accent rings in background - white for green background */
#impact::before,
#impact::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  pointer-events: none;
}

#impact::before {
  width: 400px;
  height: 400px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  animation: ring-rotate 20s linear infinite;
}

#impact::after {
  width: 300px;
  height: 300px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  animation: ring-rotate 15s linear infinite reverse;
}

@keyframes ring-rotate {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to { transform: translate(-50%, -50%) rotate(360deg); }
}

/* The zigzag comes from the margins. Opacity and movement are GSAP's job —
   the previous version set an inline transform on every scroll event, which
   silently cancelled the class-based reveal transform. */
.impact-slide {
  font-family: "Lemon Milk Light", sans-serif;
  font-size: clamp(1.2rem, 2.5vw, 1.8rem);
  color: white;
  width: 100%;
  max-width: 800px;
  position: relative;
  z-index: 2;
  padding: 0;
  text-align: center;
  display: block;
  margin-top: 60px;
  margin-bottom: 60px;
}

html.js .impact-slide { opacity: 0; }

/* Remove the heading underline inside this section */
.impact-slide::after { display: none; }

.impact-slide:nth-child(odd)  { margin-left: 6%;   margin-right: auto; }
.impact-slide:nth-child(even) { margin-left: auto; margin-right: 6%; }
.impact-slide:nth-child(1)    { margin-top: clamp(24px, 4vw, 64px); }
.impact-slide:nth-child(4)    { margin-bottom: clamp(24px, 4vw, 64px); }

@media (max-width: 700px) {
  .impact-slide:nth-child(odd),
  .impact-slide:nth-child(even) { margin-left: auto; margin-right: auto; }
}

/* ===========================================
   CONTACT FORM - FUTURISTIC
   =========================================== */
.contact-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 600px;
  margin: 60px auto 0;
  color: white;
}

.contact-form label {
  font-family: "Raleway", sans-serif;
  font-size: 1rem;
  color: rgba(255, 255, 255, 0.7);
  transition: color 0.3s ease;
}

.contact-form input,
.contact-form textarea {
  padding: 15px;
  border: 2px solid rgba(4, 108, 78, 0.3);
  border-radius: 8px;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 1rem;
  background: rgba(59, 59, 64, 0.4);
  color: white;
  transition: all 0.4s ease;
  position: relative;
}

.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
  border-color: #10B981;
  background: rgba(59, 59, 64, 0.6);
  box-shadow: 
    0 0 20px rgba(16, 185, 129, 0.3),
    inset 0 0 20px rgba(4, 108, 78, 0.1);
  transform: translateY(-2px);
}

.contact-form input:focus + label,
.contact-form textarea:focus + label {
  color: #10B981;
}

.contact-form button {
  background: linear-gradient(135deg, #046C4E 0%, #10B981 100%);
  color: white;
  font-family: "Raleway", sans-serif;
  font-weight: bold;
  border: none;
  border-radius: 8px;
  padding: 15px;
  cursor: pointer;
  /* Scoped, and no overshoot. `transition: all` with a bouncy curve made the
     shadow and brightness overshoot and settle, which read as the button
     wobbling under the cursor. */
  transition: box-shadow .25s ease, filter .25s ease;
  position: relative;
  overflow: hidden;
}

.contact-form button::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  transition: width 0.6s ease, height 0.6s ease;
}

.contact-form button:hover::before {
  width: 0;
  height: 0;
}

/* Hover must not move the button. It previously lifted 4px and scaled 1.02,
   which walked it out from under the cursor; that dropped the hover state, which
   snapped it back, which re-hovered it — so it visibly jittered while you tried
   to click. Only the shadow and brightness change now. */
.contact-form button:hover,
.contact-form button:focus-visible {
  box-shadow:
    0 12px 34px rgba(16, 185, 129, 0.45),
    0 0 0 4px rgba(16, 185, 129, 0.22);
  filter: brightness(1.08);
}

.contact-form button:active {
  filter: brightness(0.96);
}



/* ===========================================
   GALLERY
   =========================================== */
.masonry-container {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 0 100px;
}

.masonry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-auto-rows: 200px;
  gap: 20px;
  grid-auto-flow: dense;
}

.masonry-item {
  /* Now a <button> so photos can be opened — reset the UA styling it brings */
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: inherit;
  -webkit-appearance: none;
  appearance: none;

  position: relative;
  overflow: hidden;
  contain: paint;
  border-radius: 12px;
  cursor: pointer;
  /* transform is animated by GSAP on entry, so it is left out of the
     transition — a CSS transition on the same property fights the tween. */
  transition: box-shadow 0.3s ease;
}

.masonry-item:hover {
  z-index: 10;
}

.masonry-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
  /* Was `transition: all`, which covered transform and so competed with the
     hover parallax for control of the same value. */
  transition: box-shadow 0.35s ease;
}

.masonry-item:hover img {
  box-shadow: 0 15px 60px rgba(4, 108, 78, 0.7);
}

/* ===========================================
   SERVICES SECTION - INTERACTIVE CARDS WITH IMAGE DISPLAY
   =========================================== */

/* Container for both services and image display */
.services-container {
  display: flex;
  gap: 60px;
  max-width: 1400px;
  margin: 80px auto 0;
  padding: 0 40px;
  align-items: flex-start;
}

/* Services grid - pushed to left */
.services-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 25px;
  flex: 0 0 45%;
  /* The cards' hover state uses rotateX. `perspective` only applies to an
     element's children, so having it on .service-card itself did nothing and the
     "3D tilt" rendered as a flat vertical squash. It belongs on the parent. */
  perspective: 1200px;
}

/* Image display area - on the right */
.service-image-display {
  flex: 1;
  position: sticky;
  top: 120px;
  height: 600px;
  border-radius: 20px;
  overflow: hidden;
  background: rgba(59, 59, 64, 0.4);
  border: 1px solid rgba(4, 108, 78, 0.3);
}

/* Both layers stack in the same box so one can fade over the other. */
.service-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.service-image-next { opacity: 0; }

/* The card whose preview is showing — set by scroll position as well as hover,
   so it stays meaningful on a touch screen where nothing is ever hovered. */
.service-card.is-active {
  border-color: rgba(16, 185, 129, .55);
  background: rgba(59, 59, 64, .62);
}

.service-card.is-active .service-title {
  color: #fff;
}

.service-card {
  position: relative;
  background: rgba(59, 59, 64, 0.4);
  border: 1px solid rgba(4, 108, 78, 0.3);
  border-radius: 20px;
  padding: 30px 40px;
  cursor: pointer;
  /* Named properties, and no overshoot. This was
     `transition: all .5s cubic-bezier(0.68, -0.55, 0.265, 1.55)`, which meant the
     opacity and clip-path that GSAP writes inline on every frame of the reveal
     were *also* being re-interpolated by CSS on a bouncing curve. Each frame
     started a fresh half-second transition, so the wipe arrived late, mushy and
     rubber-banding — the single biggest reason this section read as broken.
     Only the hover affordances belong here. */
  transition: border-color .4s ease, background-color .4s ease,
              box-shadow .4s ease, transform .4s ease;
  overflow: hidden;
}

/* Animated gradient border on hover */
.service-card::before {
  content: "";
  position: absolute;
  inset: -2px;
  background: linear-gradient(135deg, #046C4E, #10B981, #046C4E);
  background-size: 200% 200%;
  border-radius: 20px;
  opacity: 0;
  transition: opacity 0.5s ease;
  z-index: -1;
}

@keyframes gradient-shift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

.service-card:hover::before {
  opacity: 1;
  /* Only while hovered — this used to run on all four cards permanently,
     animating a gradient behind an element that was invisible. */
  animation: gradient-shift 3s ease infinite;
}

/* Glowing orb effect */
.service-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  background: radial-gradient(circle, rgba(16, 185, 129, 0.3) 0%, transparent 70%);
  border-radius: 50%;
  top: -150px;
  right: -150px;
  opacity: 0;
  transition: opacity 0.5s ease, transform 0.5s ease;
  pointer-events: none;
}

.service-card:hover .service-glow {
  opacity: 1;
  transform: scale(1.2);
}

/* 3D hover tilt effect */
.service-card:hover {
  transform: translateY(-10px) rotateX(2deg);
  box-shadow: 
    0 30px 60px rgba(4, 108, 78, 0.4),
    0 0 0 1px rgba(16, 185, 129, 0.5);
  border-color: rgba(16, 185, 129, 0.5);
}

/* Service title */
.service-title {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: 1.8rem;
  color: white;
  margin-bottom: 15px;
  transition: all 0.3s ease;
  position: relative;
  z-index: 1;
}

.service-card:hover .service-title {
  color: rgba(255, 255, 255, 1);
  transform: translateX(5px);
}

/* Service description - always visible, no expansion */
.service-desc {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 1rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.7);
  transition: color 0.3s ease;
  position: relative;
  z-index: 1;
  opacity: 1;
  max-height: none;
  overflow: visible;
}

.service-card:hover .service-desc {
  color: rgba(255, 255, 255, 0.9);
}

/* Keyboard parity with the hover preview swap */
.service-card:focus-visible {
  outline: 2px solid var(--green-bright, #10B981);
  outline-offset: 4px;
}


/* FOOTER */
footer {
  background: #000;
  padding: 32px 16px;
  text-align: center;
  color: white;
  font-family: "Lemon Milk Light"; /*ChatGPT*/
  margin-top: 48px;
  border-top: 1px solid rgba(255,255,255,0.08);
}

.footer-content p {
  margin: 0 0 10px 0;
  font-size: 14px;
  color: #ccc;
}

.footer-links {
  display: inline-flex;
  gap: 16px;
  flex-wrap: wrap;
}

.footer-links a {
  font-family: "Lemon Milk Medium Italic"; /*ChatGPT*/
  color: #10B981;
  font-size: 14px;
}

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

/* Responsive */
@media (max-width: 968px) {
  .services-container {
    flex-direction: column;
    gap: 40px;
  }
  
  .services-grid {
    flex: 1;
    width: 100%;
    grid-template-columns: 1fr;
    gap: 25px;
    padding: 0 20px;
  }
  
  .service-image-display {
    position: relative;
    top: 0;
    width: 100%;
    height: 400px;
  }
  
  .service-card {
    padding: 40px 30px;
  }

  .service-title {
    font-size: 1.5rem;
  }
}

@media (max-width: 640px) {
  .service-card {
    padding: 35px 25px;
  }
  
  .service-image-display {
    height: 300px;
  }

  .service-title {
    font-size: 1.3rem;
  }
  
  .service-desc {
    font-size: 0.9rem;
  }
}
/* ===========================================================================
   ADDITIONS & FIXES — folding in the deck content + robust gallery
   =========================================================================== */

:root{
  /* Vertical rhythm.
     Section gaps were clamp(120px, 18vw, 260px) plus each section's own 40-60px
     of padding, which stacked into empty bands of 320-380px between the lower
     sections - about 0.4 of a screen every time, and 2,455px (20%) of the whole
     page. Worse, the top of the page ran 160-175px bands while the bottom ran
     360px, so the rhythm changed halfway down. One scale now, applied
     everywhere. */
  --section-gap: clamp(64px, 7vw, 112px);
  --section-pad-y: clamp(28px, 3.5vw, 48px);

  /* Horizontal inset for every section's content. Section headings used to
     start anywhere from 20px to 144px from the left at 1440px, because some
     sections had a centring max-width and others did not — so the eye read a
     ragged left edge down the page. This is the value #workflow already used;
     everything now shares it. */
  --content-inset: clamp(24px, 6vw, 90px);
  --content-max: 1320px;

  /* Hit-area padding on the nav links. A variable rather than a literal because
     the wordmark cancels it with a negative margin to sit on --content-inset;
     if the two ever drift apart the logo stops lining up with the headings. */
  --nav-link-pad-x: 14px;

  --ink:#3B3B40;
  --green:#046C4E;
  --green-bright:#10B981;
  --green-deep:#035c43;
}

/* Visible by default. The hidden start state lives in the html.js block near
   the end of this file, so JS being off or broken cannot hide the content.
   (.in-view is gone — GSAP drives these now, not a class toggle.) */

/* Eyebrow label that echoes the deck's vertical category tags */
.section-eyebrow{
  font-family:"Lemon Milk Medium Italic",sans-serif;
  letter-spacing:.18em;
  font-size:.8rem;
  color:var(--green-bright);
  text-transform:uppercase;
  margin-bottom:14px;
  display:block;
}

/* ============================ TRUE MASONRY GALLERY ============================ */
/* Override the brittle manual grid with CSS columns that respect aspect ratio  */
.masonry-grid{
  display:block;
  column-count:4;
  column-gap:var(--masonry-gap);
  grid-template-columns:none;
}
.masonry-grid .masonry-item{
  break-inside:avoid;
  margin:0 0 var(--masonry-gap);
  display:block;
  width:100%;
  grid-column:auto !important;
  grid-row:auto !important;
}
.masonry-grid .masonry-item img{height:auto;}
@media (max-width:1200px){ .masonry-grid{column-count:3;} }
@media (max-width:768px){ .masonry-grid{column-count:2;} }
@media (max-width:460px){ .masonry-grid{column-count:2;} }

.timeline-tag{
  display:inline-block;font-family:"Lemon Milk Medium",sans-serif;font-size:1.05rem;
  color:#fff;background:var(--green-deep);padding:8px 18px;border-radius:6px;margin-bottom:12px;letter-spacing:.04em;
}

/* ============================ WORK CARDS ============================ */
.works-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:28px;margin-top:28px;}
.work-card{
  position:relative;border-radius:20px;overflow:hidden;background:rgba(59,59,64,.45);border:1px solid rgba(4,108,78,.3);
  transition:transform .5s cubic-bezier(.16,1,.3,1),box-shadow .5s ease,border-color .5s ease;
}
.work-card:hover{transform:translateY(-10px);border-color:rgba(16,185,129,.5);
  box-shadow:0 30px 70px rgba(4,108,78,.35);}
.work-media{position:relative;aspect-ratio:16/9;overflow:hidden;background:#000;}
.work-media iframe,.work-media img,.work-media video{width:100%;height:100%;object-fit:cover;display:block;border:0;}
.work-body{padding:26px 30px 30px;}
.work-kicker{font-family:"Lemon Milk Medium Italic",sans-serif;font-size:.72rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--green-bright);display:block;}
.work-title{font-family:"Lemon Milk Medium",sans-serif;font-size:1.6rem;color:#fff;margin:8px 0 16px;}
.work-list{list-style:none;padding:0;margin:0;}
.work-list li{font-family:"IBM Plex Sans",sans-serif;font-size:.95rem;color:rgba(255,255,255,.72);
  padding:5px 0 5px 20px;position:relative;}
.work-list li::before{content:"";position:absolute;left:0;top:13px;width:8px;height:2px;background:var(--green-bright);}
.work-link{display:inline-block;margin-top:18px;font-family:"Lemon Milk Medium",sans-serif;font-size:.85rem;
  color:var(--green-bright);text-decoration:none;letter-spacing:.05em;transition:gap .3s ease,color .3s ease;}
.work-link::after{content:" \2192";transition:margin-left .3s ease;}
.work-link:hover{color:#fff;}
.work-link:hover::after{margin-left:6px;}
@media (max-width:900px){ .works-grid{grid-template-columns:1fr;} }

/* ============================ ABOUT / OUR STORY ============================ */
#about{margin:0 0 var(--section-gap);padding:var(--section-pad-y) var(--content-inset);}
.about-wrap{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(48px,6vw,86px);margin-top:clamp(32px,4vw,56px);align-items:start;}
.about-story p{font-family:"IBM Plex Sans",sans-serif;font-size:clamp(1.12rem,1.45vw,1.4rem);line-height:1.75;
  color:rgba(255,255,255,.82);margin-bottom:22px;}
.team{display:flex;flex-direction:column;gap:clamp(22px,2.6vw,34px);}
.team-member{display:flex;align-items:center;gap:clamp(18px,2vw,26px);}
/* Real headshots, taken from slide 10 of the pitch deck, instead of initials.
   The source portraits are on white, so a ring keeps them from reading as
   floating cut-outs against the dark page. */
.avatar{
  flex:0 0 clamp(84px,8vw,116px);width:clamp(84px,8vw,116px);height:clamp(84px,8vw,116px);border-radius:50%;
  object-fit:cover;object-position:center top;display:block;
  background:#e9e9ea;
  border:2px solid rgba(16,185,129,.45);
  box-shadow:0 8px 24px rgba(4,108,78,.4);
  transition:border-color .4s ease, box-shadow .4s ease;
}

.team-member:hover .avatar{
  border-color:rgba(16,185,129,.9);
  box-shadow:0 12px 30px rgba(4,108,78,.55);
}
.team-name{font-family:"Lemon Milk Medium",sans-serif;font-size:clamp(1.15rem,1.5vw,1.45rem);color:#fff;}
.team-role{font-family:"IBM Plex Sans",sans-serif;font-size:clamp(.9rem,1.05vw,1.02rem);color:#fff;
  background:var(--green-deep);display:inline-block;padding:3px 10px;border-radius:4px;margin-top:5px;}
@media (max-width:820px){ .about-wrap{grid-template-columns:1fr;gap:40px;} }

/* ============================ SHOWREEL ============================ */

/* Contact details row under the form */
.contact-meta{display:flex;justify-content:center;gap:40px;flex-wrap:wrap;margin-top:40px;}
.contact-meta a{font-family:"Lemon Milk Medium Italic",sans-serif;color:var(--green-bright);
  text-decoration:none;font-size:1.05rem;letter-spacing:.03em;transition:color .3s ease;}
.contact-meta a:hover{color:#fff;}

/* ===========================================================================
   LENIS (smooth scroll) base rules
   =========================================================================== */
html.lenis, html.lenis body { height: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* ===========================================================================
   SCROLL PROGRESS
   =========================================================================== */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 10001;
  background: rgba(255, 255, 255, .07);
  pointer-events: none;
}

.scroll-progress-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, var(--green), var(--green-bright));
  transform: scaleX(0);
  transform-origin: left center;
  box-shadow: 0 0 14px rgba(16, 185, 129, .7);
}

/* ===========================================================================
   MOBILE NAV
   =========================================================================== */
.nav-toggle {
  display: none;
  position: relative;
  width: 46px;
  height: 40px;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 10px;
  background: rgba(0, 0, 0, .35);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  cursor: pointer;
  flex: 0 0 auto;
}

.nav-toggle-bar {
  position: absolute;
  left: 50%;
  width: 20px;
  height: 2px;
  background: #fff;
  border-radius: 2px;
  transition: transform .35s cubic-bezier(.16, 1, .3, 1), opacity .2s ease;
}

.nav-toggle-bar:nth-child(1) { transform: translate(-50%, -5px); }
.nav-toggle-bar:nth-child(2) { transform: translate(-50%, 4px); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translate(-50%, 0) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { transform: translate(-50%, 0) rotate(-45deg); }

.nav-panel {
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: rgba(12, 14, 14, .96);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 40px;
  padding: 90px 30px 40px;
}

.nav-panel[hidden] { display: none; }

.nav-panel ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.nav-panel a {
  font-family: "Lemon Milk Medium Italic", sans-serif;
  color: #fff;
  text-decoration: none;
  font-size: clamp(1.5rem, 7vw, 2.2rem);
  display: block;
  padding: 10px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .07);
}

.nav-panel a:active { color: var(--green-bright); }

.nav-panel-meta {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.nav-panel-meta a {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .95rem;
  color: var(--green-bright);
  text-decoration: none;
  border: 0;
  padding: 0;
}

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

@media (min-width: 901px) {
  .nav-panel { display: none !important; }
}

/* ===========================================================================
   FILMS
   =========================================================================== */
#films { padding: var(--section-pad-y) var(--content-inset); }

.section-lede {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 1.05rem;
  color: rgba(255, 255, 255, .6);
  max-width: 520px;
  margin: -18px 0 0;
}

/* Grouped by client vs personal, same split as the work overlay */
.film-group { margin-top: 56px; }

.film-group-label {
  display: block;
  font-family: "Lemon Milk Medium Italic", sans-serif;
  font-size: .78rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
}

.film-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 34px;
  margin-top: 28px;
  /* Cards in a row match height, so titles and copy line up */
  align-items: stretch;
}

.film-card { display: flex; flex-direction: column; }
.film-body { flex: 1; }

.film-card {
  background: rgba(59, 59, 64, .45);
  border: 1px solid rgba(4, 108, 78, .3);
  border-radius: 20px;
  overflow: hidden;
  transition: transform .5s cubic-bezier(.16, 1, .3, 1), border-color .4s ease, box-shadow .4s ease;
}

.film-card:hover {
  transform: translateY(-8px);
  border-color: rgba(16, 185, 129, .5);
  box-shadow: 0 30px 70px rgba(4, 108, 78, .35);
}

/* Shared poster tile — used in #films and the work overlay.
   EVERY tile is the same 16:9 box whatever the film's own aspect, so the grid
   stays even. A vertical film is centred inside that box over a blurred copy of
   its own poster rather than being cropped or letterboxed into black bars. */
.film-tile {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 0;
  background: #000;
  cursor: pointer;
  overflow: hidden;
  /* overflow:hidden alone did not contain the scaled .film-blur layer, which
     leaked far enough to give the document ~50px of horizontal scroll.
     contain:paint guarantees descendants cannot paint outside this box. */
  contain: paint;
}

/* Base sizing for ANY image inside a tile. Deliberately not scoped to
   .film-frame: an image that arrives without the class must still be boxed by
   its tile rather than rendering at its intrinsic width and widening the page. */
.film-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.film-frame {
  position: relative;
  z-index: 1;
  transition: transform .8s cubic-bezier(.16, 1, .3, 1), filter .4s ease;
}

/* Vertical: show the whole frame, let the blur fill the sides */
.film-card.is-vertical .film-frame,
.work-card.is-vertical .film-frame {
  object-fit: contain;
}

.film-blur {
  position: absolute;
  inset: 0;
  z-index: 0;
  transform: scale(1.2);
  filter: blur(18px) saturate(1.3) brightness(.5);
  pointer-events: none;
}

.film-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: rgba(0, 0, 0, .26);
  transition: background .35s ease;
  pointer-events: none;
}

.film-tile:hover .film-frame { transform: scale(1.05); }
.film-tile:hover::after { background: rgba(0, 0, 0, .12); }

.film-tile:focus-visible {
  outline: 3px solid var(--green-bright);
  outline-offset: -3px;
}

.film-play {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: 78px;
  height: 78px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: rgba(16, 16, 18, .6);
  border: 1px solid rgba(255, 255, 255, .25);
  display: grid;
  place-items: center;
  transition: transform .45s cubic-bezier(.16, 1, .3, 1), background .35s ease;
}

.film-play::before {
  content: "";
  border-style: solid;
  border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent #fff;
  margin-left: 5px;
}

.film-tile:hover .film-play {
  transform: translate(-50%, -50%) scale(1.12);
  background: var(--green-bright);
}

/* Tile whose file has not been delivered yet */
.film-soon {
  position: absolute;
  bottom: 14px;
  left: 14px;
  z-index: 2;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .68rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(0, 0, 0, .6);
  border: 1px solid rgba(255, 255, 255, .2);
  padding: 6px 12px;
  border-radius: 20px;
  /* "COMING SOON" with .12em tracking needs ~100px on one line. Absolutely
     positioned, it shrink-to-fits against the tile, so on a narrow tile it wrapped
     to two lines and the pill went 44px tall. The tile clips its overflow, so
     nowrap is the safe direction to fail in. */
  white-space: nowrap;
}

.film-tile.is-pending { cursor: default; }
.film-tile.is-pending .film-play { display: none; }
.film-tile.is-pending:hover img { transform: none; }

.film-body { padding: 26px 30px 30px; }

.film-title {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: 1.5rem;
  color: #fff;
  margin: 8px 0 12px;
}

.film-desc {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .98rem;
  line-height: 1.65;
  color: rgba(255, 255, 255, .72);
}

.film-body .work-link { margin-top: 16px; }

@media (max-width: 520px) {
  .film-grid { grid-template-columns: 1fr; gap: 24px; }
  .film-play { width: 60px; height: 60px; }
}

/* ===========================================================================
   HOW WE WORK — pinned horizontal scroll
   =========================================================================== */
#workflow {
  padding: 0;
  margin-bottom: var(--section-gap);
}

.workflow-viewport {
  position: relative;
  overflow: hidden;
  padding: 60px 0;
}

/* Added by main.js only when the pin is actually active */
.workflow-viewport.is-pinned {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.workflow-head {
  padding: 0 var(--content-inset);
  margin-bottom: 40px;
}

.workflow-head .reveal-heading { display: block; text-align: left; }

.workflow-track {
  display: flex;
  gap: 30px;
  padding: 0 var(--content-inset);
}

.workflow-panel {
  position: relative;
  flex: 0 0 auto;
  width: min(460px, 78vw);
  min-height: 340px;
  padding: 40px;
  border-radius: 22px;
  background: rgba(59, 59, 64, .42);
  border: 1px solid rgba(4, 108, 78, .32);
  overflow: hidden;
  display: flex;
  align-items: flex-end;
}

.workflow-num {
  position: absolute;
  top: -8px;
  right: 18px;
  font-family: "Lemon Milk Bold Italic", sans-serif;
  font-size: clamp(6rem, 11vw, 10rem);
  line-height: 1;
  color: rgba(16, 185, 129, .13);
  pointer-events: none;
  user-select: none;
}

.workflow-content { position: relative; z-index: 1; }

.workflow-content p {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 1.04rem;
  line-height: 1.7;
  color: rgba(255, 255, 255, .8);
  margin: 0;
}

.workflow-foot {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 44px var(--content-inset) 0;
}

.workflow-progress {
  flex: 1;
  height: 2px;
  background: rgba(255, 255, 255, .12);
  border-radius: 2px;
  overflow: hidden;
}

.workflow-progress-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, var(--green), var(--green-bright));
  transform: scaleX(0);
  transform-origin: left center;
}

.workflow-counter {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .9rem;
  letter-spacing: .14em;
  color: rgba(255, 255, 255, .45);
  flex: 0 0 auto;
}

.workflow-counter-now { color: var(--green-bright); }

/* Below the pin breakpoint the track becomes a plain vertical stack.
   Pinned horizontal scroll fights native touch scrolling on phones. */
@media (max-width: 900px) {
  .workflow-track {
    flex-direction: column;
    gap: 22px;
  }
  .workflow-panel {
    width: 100%;
    min-height: 0;
    padding: 32px 28px;
  }
  .workflow-foot { display: none; }
}

/* ===========================================================================
   WORK OVERLAY
   =========================================================================== */
.work-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #23252a;
  background-image:
    radial-gradient(circle at 18% 12%, rgba(4, 108, 78, .22) 0%, transparent 55%),
    radial-gradient(circle at 82% 78%, rgba(16, 185, 129, .14) 0%, transparent 55%);
}

.work-overlay[hidden] { display: none; }

.work-overlay-inner {
  max-width: 1500px;
  margin: 0 auto;
  padding: clamp(80px, 9vw, 120px) clamp(20px, 5vw, 60px) 120px;
}

.work-overlay-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 30px;
  margin-bottom: 34px;
}

.work-overlay-head h2 {
  font-family: "Lemon Milk Light", sans-serif;
  color: #fff;
  margin-bottom: 0;
}

.work-close {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(0, 0, 0, .3);
  color: #fff;
  font-size: 1.9rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background .3s ease, transform .35s cubic-bezier(.16, 1, .3, 1), border-color .3s ease;
}

.work-close:hover {
  background: var(--green-bright);
  border-color: var(--green-bright);
  transform: rotate(90deg);
}

.work-filters {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 50px;
}

.work-filter {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .78rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .65);
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 30px;
  padding: 10px 22px;
  cursor: pointer;
  transition: all .35s ease;
}

.work-filter:hover { color: #fff; border-color: rgba(16, 185, 129, .6); }

.work-filter.is-active {
  color: #06231a;
  background: var(--green-bright);
  border-color: var(--green-bright);
}

.work-group { margin-bottom: 70px; }
.work-group[hidden] { display: none; }

.work-media-logo { display: grid; place-items: center; padding: 6%; box-sizing: border-box; }
.work-media-logo img { width: 100%; max-width: 380px; max-height: 100%; object-fit: contain; }

/* work-media is also a button when the project has a video */
button.work-media {
  width: 100%;
  padding: 0;
  border: 0;
  cursor: pointer;
  aspect-ratio: 16 / 9;
}

/* A second cut of the same project (e.g. behind the scenes), shown as a small
   secondary tile inside the card body rather than a card of its own. */
/* Two cuts of the same project sit side by side under the deliverables */
.work-extras {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 20px;
}

@media (max-width: 420px) {
  .work-extras { grid-template-columns: 1fr; }
}

/* Client logo, sat between the project title and the deliverables. Sizing is by
   height with width:auto so wildly different logo proportions — a wide MBC
   wordmark against a near-square SI monogram — read at a similar visual weight. */
.work-client {
  display: block;
  height: clamp(34px, 3.4vw, 52px);
  width: auto;
  max-width: 190px;
  object-fit: contain;
  object-position: left center;
  margin: 2px 0 16px;
  opacity: .9;
}

.work-extra {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin-top: 0;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 10px;
  overflow: hidden;
  contain: paint;
  background: #000;
  cursor: pointer;
}

.work-extra img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .6s cubic-bezier(.16, 1, .3, 1);
}

.work-extra:hover img { transform: scale(1.06); }

.work-extra::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .35);
  transition: background .3s ease;
}

.work-extra:hover::after { background: rgba(0, 0, 0, .15); }

.work-extra .film-play {
  width: 40px;
  height: 40px;
}

.work-extra .film-play::before {
  border-width: 7px 0 7px 11px;
  margin-left: 3px;
}

.work-extra-label {
  position: absolute;
  bottom: 8px;
  left: 10px;
  z-index: 2;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 6px rgba(0, 0, 0, .8);
}

@media (max-width: 640px) {
  .work-overlay-head { flex-direction: row; }
  .work-overlay-head h2 { font-size: clamp(1.6rem, 8vw, 2.4rem); }
}

/* ===========================================================================
   VIDEO LIGHTBOX
   =========================================================================== */
.lightbox {
  width: min(1200px, 92vw);
  max-width: none;
  max-height: none;
  padding: 0;
  border: 0;
  background: transparent;
  color: #fff;
  overflow: visible;
}

.lightbox::backdrop {
  background: rgba(6, 8, 8, .88);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.lightbox-stage {
  position: relative;
  aspect-ratio: var(--stage-aspect, 16 / 9);
  width: 100%;
  background: #000;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 40px 120px rgba(0, 0, 0, .7);
}

/* A 9:16 film in a 16:9 stage would be a thin sliver between black bars.
   main.js sets .is-vertical from the tile, which narrows the whole dialog and
   flips the stage upright. Height-capped so it always fits the viewport. */
.lightbox.is-vertical {
  width: min(460px, 92vw);
}

/* Height is driven by the viewport and the width falls out of the aspect ratio,
   so the frame fits exactly with no letterbox bars down the sides. */
.lightbox.is-vertical .lightbox-stage {
  --stage-aspect: 9 / 16;
  height: min(76vh, 820px);
  width: auto;
  max-width: 100%;
  margin: 0 auto;
}

.lightbox.is-vertical .lightbox-stage video,
.lightbox.is-vertical .lightbox-stage iframe {
  object-fit: cover;
}

.lightbox.is-vertical .lightbox-caption {
  justify-content: center;
  text-align: center;
}

.lightbox-stage video,
.lightbox-stage iframe {
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  object-fit: contain;
  background: #000;
}

.lightbox-caption {
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
  padding: 18px 4px 0;
}

.lightbox-title {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: 1.15rem;
  color: #fff;
  margin: 0;
}

.lightbox-meta {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .85rem;
  color: rgba(255, 255, 255, .55);
}

.lightbox-close {
  position: absolute;
  top: -54px;
  right: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .25);
  background: rgba(0, 0, 0, .4);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background .3s ease, transform .35s cubic-bezier(.16, 1, .3, 1);
}

.lightbox-close:hover {
  background: var(--green-bright);
  transform: rotate(90deg);
}

@media (max-width: 560px) {
  .lightbox-close { top: -50px; right: 0; width: 40px; height: 40px; }
}

/* ===========================================================================
   JS-GATED REVEAL STATES
   Everything below only hides content once main.js has confirmed it is running,
   so a failed script or a blocked CDN can never leave the page blank.
   =========================================================================== */
html.js .fade-in { opacity: 0; transform: translateY(40px); }
html.js .reveal-heading .line-inner { transform: translateY(110%); }
html.js #films .film-card { opacity: 0; }
html.js .service-card { opacity: 0; }

/* Words inside the impact statements, brightened on scrub by main.js */
/* will-change lists only what actually animates. It still named `filter` after
   the per-word blur was dropped, which pinned a filter layer for every word. */
.impact-slide .iw { display: inline-block; will-change: opacity, transform; }

/* Velocity skew is applied to #main; keeping the transform origin centred stops
   it pivoting off one edge. */
#main { transform-origin: center center; }

/* ===========================================================================
   REDUCED MOTION
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }

  html.js .fade-in,
  .fade-in { opacity: 1 !important; transform: none !important; }

  html.js #films .film-card,
  html.js .service-card { opacity: 1 !important; transform: none !important; }

  .impact-slide,
  .impact-slide .iw { opacity: 1 !important; filter: none !important; transform: none !important; }

  .masonry-item { clip-path: none !important; transform: none !important; }
  #main { transform: none !important; }
  .scroll-progress { display: none; }

  html.js .reveal-heading .line-inner { transform: none !important; }
  html.js h2.reveal-heading::after { transform: scaleX(1) !important; }

  .workflow-track { flex-direction: column; gap: 22px; }
  .workflow-panel { width: 100%; min-height: 0; }
  .workflow-foot { display: none; }
  .workflow-progress-fill { transform: scaleX(1); }

  body::before { animation: none; }
}

/* ===========================================================================
   HERO OVERLAY
   The hero used to be a silent video with no text at all, so a first-time
   visitor could not tell what the studio does. This is the answer to that.
   =========================================================================== */
.hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
  padding: 0 clamp(20px, 6vw, 90px);
  pointer-events: none;   /* the video keeps the space; only controls take clicks */
}

/* Legibility over an unpredictable frame of video, without dimming it flat */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(90deg, rgba(6, 10, 9, .82) 0%, rgba(6, 10, 9, .45) 45%, transparent 75%),
    linear-gradient(0deg, rgba(6, 10, 9, .55) 0%, transparent 40%);
}

.hero-title {
  font-family: "Lemon Milk Light", sans-serif;
  font-size: clamp(2.1rem, 6.4vw, 5.4rem);
  line-height: 1.02;
  color: #fff;
  margin: 0;
  max-width: 16ch;
  text-shadow: 0 4px 30px rgba(0, 0, 0, .5);
}

.hero-sub {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: clamp(.95rem, 1.5vw, 1.18rem);
  line-height: 1.6;
  color: rgba(255, 255, 255, .84);
  max-width: 46ch;
  margin: 0;
  text-shadow: 0 2px 16px rgba(0, 0, 0, .6);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 6px;
  pointer-events: auto;
}

.hero-cta {
  /* One of these is a <button> (the showreel opens the lightbox rather than
     navigating), so the UA button styling has to be neutralised for the two
     to look identical. */
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  line-height: 1;

  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .82rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  color: #06231a;
  background: var(--green-bright);
  padding: 15px 30px;
  border-radius: 40px;
  border: 1px solid transparent;
  transition: transform .4s cubic-bezier(.16, 1, .3, 1), background .3s ease, color .3s ease;
}

.hero-cta:hover {
  transform: translateY(-3px);
  background: #fff;
}

.hero-cta-ghost {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, .35);
}

.hero-cta-ghost:hover {
  background: rgba(255, 255, 255, .1);
  color: #fff;
}

/* The hero is 16:5 on desktop, which is too letterboxed to hold this much
   text on a phone. Give it height there instead. */
@media (max-width: 900px) {
  .hero { min-height: min(88vh, 720px); }
  .hero-overlay { justify-content: flex-end; padding-bottom: 76px; gap: 16px; }
  .hero-title { max-width: 12ch; }
  .hero::after {
    background: linear-gradient(0deg, rgba(6, 10, 9, .88) 12%, rgba(6, 10, 9, .35) 55%, transparent 85%);
  }
}

@media (max-width: 420px) {
  .hero-actions { flex-direction: column; align-items: flex-start; }
  .hero-cta { width: 100%; text-align: center; }
}

/* ===========================================================================
   CONTACT — what happens after you press send
   =========================================================================== */
.contact-lede {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 1.05rem;
  line-height: 1.7;
  color: rgba(255, 255, 255, .78);
  max-width: 56ch;
  margin: 0 0 40px;
}

.contact-lede strong { color: #fff; font-weight: 600; }

.contact-steps {
  list-style: none;
  padding: 0;
  margin: 0 0 10px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 26px;
  max-width: 900px;
}

.contact-steps li {
  border-top: 1px solid rgba(255, 255, 255, .14);
  padding-top: 16px;
}

.contact-step-num {
  display: block;
  font-family: "Lemon Milk Bold Italic", sans-serif;
  font-size: .8rem;
  color: var(--green-bright);
  margin-bottom: 8px;
}

.contact-step-label {
  display: block;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: 1.02rem;
  color: #fff;
  margin-bottom: 6px;
}

.contact-step-desc {
  display: block;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .9rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, .62);
}

@media (max-width: 700px) {
  .contact-steps { grid-template-columns: 1fr; gap: 18px; }
}

/* ===========================================================================
   LIGHTBOX — photo mode
   =========================================================================== */
/* Shrink-wrap the dialog to the photo. With `width: auto` the dialog stretched
   to its 92vw max, so a 468px portrait sat in a 1325px stage - pinned to the
   left, 856px off centre, and with a huge dead strip beside it that swallowed
   backdrop clicks because the target was the stage rather than the dialog. */
/* fit-content sizes to the image — but before that image has decoded there is
   nothing to size to, so the dialog collapsed to almost zero and the close and
   prev/next controls (positioned relative to it) piled up in the middle of the
   screen over a blurred page. Minimums keep a sensible frame from the first
   frame; fit-content still takes over once the photo is there. */
.lightbox.is-photo {
  width: -moz-fit-content;
  width: fit-content;
  min-width: min(52vw, 460px);
  max-width: 92vw;
}

.lightbox.is-photo .lightbox-stage {
  min-height: min(46vh, 420px);
  background: rgba(0, 0, 0, .35);
}

.lightbox.is-photo .lightbox-stage {
  aspect-ratio: auto;
  width: -moz-fit-content;
  width: fit-content;
  max-width: 92vw;
  margin-inline: auto;
  background: transparent;
  box-shadow: none;
  line-height: 0;   /* kills the inline-image descender gap under the photo */
}

/* No caption on photos. The titles came from image alt text, which is generic
   or wrong on most of the gallery, and a filename-ish caption under a
   photograph adds nothing. The position counter survives as a small pill so
   there is still a sense of place in a set of 25. */
.lightbox.is-photo .lightbox-caption { display: none; }

.lightbox-counter {
  position: absolute;
  top: -46px;
  left: 0;
  z-index: 3;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .72rem;
  letter-spacing: .12em;
  color: rgba(255, 255, 255, .75);
  background: rgba(0, 0, 0, .4);
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 20px;
  padding: 8px 16px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.lightbox:not(.is-photo) .lightbox-counter { display: none; }

@media (max-width: 560px) {
  .lightbox-counter { top: -42px; font-size: .66rem; padding: 6px 12px; }
}

.lightbox-photo {
  display: block;
  max-width: 92vw;
  max-height: 78vh;
  width: auto;
  height: auto;
  border-radius: 12px;
  box-shadow: 0 40px 120px rgba(0, 0, 0, .7);
}

.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(0, 0, 0, .45);
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background .3s ease, transform .35s cubic-bezier(.16, 1, .3, 1);
}

.lightbox-nav:hover {
  background: var(--green-bright);
  color: #06231a;
}

.lightbox-prev { left: -74px; }
.lightbox-next { right: -74px; }

/* On narrow screens there is no room outside the frame, so sit them inside */
@media (max-width: 900px) {
  .lightbox-prev { left: 8px; }
  .lightbox-next { right: 8px; }
  .lightbox-nav { width: 44px; height: 44px; font-size: 1.6rem; }
}

/* Hero headline reveal — same line-mask mechanism as the section headings,
   but keyed to its own class so it is not caught by the scroll-triggered
   .reveal-heading pass (the hero is already in view on load). */
html.js .hero-title .line-inner { transform: translateY(110%); }
html.js .hero-sub,
html.js .hero-actions { opacity: 0; }

/* ===========================================================================
   FILMS TEASER — client/personal tag + link into the archive
   =========================================================================== */
.film-tag {
  float: right;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--green-bright);
  border: 1px solid rgba(16, 185, 129, .35);
  border-radius: 20px;
  padding: 3px 10px;
}

.film-tag-personal {
  color: rgba(255, 255, 255, .5);
  border-color: rgba(255, 255, 255, .18);
}

.films-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 46px;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .82rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 40px;
  padding: 15px 32px;
  transition: background .35s ease, border-color .35s ease,
              transform .4s cubic-bezier(.16, 1, .3, 1);
}

.films-cta::after {
  content: "\2192";
  transition: transform .35s ease;
}

.films-cta:hover {
  background: var(--green-bright);
  border-color: var(--green-bright);
  color: #06231a;
  transform: translateY(-3px);
}

.films-cta:hover::after { transform: translateX(4px); }

.films-cta-count {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .78rem;
  letter-spacing: 0;
  text-transform: none;
  opacity: .6;
}

/* ===========================================================================
   PROJECT PHOTO SET — a mini gallery inside a work card
   =========================================================================== */
.work-shots-label {
  display: block;
  margin-top: 22px;
  font-family: "Lemon Milk Medium Italic", sans-serif;
  font-size: .7rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .45);
}

.work-shots {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin-top: 12px;
}

.work-shot {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 8px;
  overflow: hidden;
  contain: paint;   /* the image scales on hover; keep it inside the tile */
  background: #000;
  cursor: pointer;
  transition: border-color .3s ease, transform .4s cubic-bezier(.16, 1, .3, 1);
}

.work-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .6s cubic-bezier(.16, 1, .3, 1), opacity .3s ease;
  opacity: .75;
}

.work-shot:hover {
  border-color: rgba(16, 185, 129, .6);
  transform: translateY(-3px);
}

.work-shot:hover img { transform: scale(1.12); opacity: 1; }

.work-shot:focus-visible {
  outline: 2px solid var(--green-bright);
  outline-offset: 2px;
}

/* Five across is right on a wide card and far too small on a phone */
@media (max-width: 1100px) { .work-shots { grid-template-columns: repeat(5, 1fr); } }
@media (max-width: 700px)  { .work-shots { grid-template-columns: repeat(4, 1fr); gap: 6px; } }
@media (max-width: 420px)  { .work-shots { grid-template-columns: repeat(3, 1fr); } }

/* ===========================================================================
   WORK TILE THAT LINKS OUT
   For a project hosted elsewhere (Tokyo Postcards lives on Instagram). The
   whole tile is the link, so it behaves like the playable tiles rather than
   hiding the destination in a small text link underneath.
   =========================================================================== */
.work-media-link {
  display: block;
  cursor: pointer;
  contain: paint;
  text-decoration: none;
}

.work-media-link .film-frame {
  transition: transform .8s cubic-bezier(.16, 1, .3, 1);
}

.work-media-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(0, 0, 0, .3);
  transition: background .35s ease;
  pointer-events: none;
}

.work-media-link:hover .film-frame { transform: scale(1.05); }
.work-media-link:hover::after { background: rgba(0, 0, 0, .14); }

.work-media-link:focus-visible {
  outline: 2px solid var(--green-bright);
  outline-offset: 3px;
}

.tile-badge {
  position: absolute;
  bottom: 14px;
  right: 14px;
  z-index: 3;
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .68rem;
  letter-spacing: .06em;
  color: #fff;
  background: rgba(0, 0, 0, .58);
  border: 1px solid rgba(255, 255, 255, .16);
  padding: 8px 14px;
  border-radius: 20px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background .3s ease, color .3s ease;
}

.work-media-link:hover .tile-badge {
  background: var(--green-bright);
  border-color: var(--green-bright);
  color: #06231a;
}


/* ===========================================================================
   HERO LOCATION + CENTRED CONTACT SECTION
   =========================================================================== */
/* Contact is the one section centred all the way through, closing pitch rather
   than another catalogue entry. Everything below the heading was already
   symmetric — lede, steps, form, meta all measured equal margins at 1440 — and
   the heading was the only thing still on the shared left inset, at 86px left
   against 957px right. It is the exception here, not the rule: every other
   section keeps its heading on that inset. */
#contact .contact-lede {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

/* h2 is display:inline-block globally so the ::after underline, which is
   width:100%, spans the text instead of the whole section. Auto margins do
   nothing to an inline-level box, so centring it means going back to block —
   and width:max-content is what preserves the shrink-to-fit the underline
   depends on. max-width keeps a long heading inside the section on a phone
   rather than pushing the page wide; text-align then centres the lines it
   wraps into. */
#contact > .section-eyebrow,
#contact > h2.reveal-heading {
  display: block;
  width: max-content;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

@media (max-width: 700px) {
  #contact .contact-lede { text-align: left; }
}


/* Content caps live on the content, not the section. A max-width on the section
   centres it and drags its heading away from the shared left inset — which was
   the ragged-left problem. These keep line lengths sane without moving headings. */
#films .film-grid,
#films .section-lede,
#films .films-cta { max-width: var(--content-max); }

/* Centred on its *content*, which is the part that was wrong before.
   Giving the wrapper auto margins made its box symmetric — 86px either side at
   1440 — but the box is not what you see. The team column was a .95fr track
   561px wide holding avatars and short names that only painted 349px of it, so
   the visible block still sat 212px left of centre.
   max-content sizes that column to the avatars and names, and justify-content
   centres the resulting pair, so the ink is centred rather than the container.
   minmax(0, 620px) keeps the prose at the width it already had while still
   letting it compress before the 820px stack breakpoint.

   The grid part is scoped to min-width 821px on purpose. The stacked phone
   layout is set by `.about-wrap{grid-template-columns:1fr}` inside a
   max-width:820px query, and `#about .about-wrap` outscores a bare
   `.about-wrap` — an unconditional rule here would win inside that query too
   and un-stack the phone layout. */
#about .about-wrap {
  max-width: var(--content-max);
  margin-left: auto;
  margin-right: auto;
}

@media (min-width: 821px) {
  #about .about-wrap {
    grid-template-columns: minmax(0, 620px) max-content;
    justify-content: center;
  }
}

/* Long-form prose gets its own measure — a full-width column would run to
   ~120 characters a line at 1440. */
#about .about-story p { max-width: 62ch; }

.masonry-container { max-width: none; }


/* ===========================================================================
   TOUCH TARGETS
   The footer and contact links were 19-23px tall — fine for a mouse, well under
   the ~44px a fingertip needs. Padding rather than font-size so the type scale
   is untouched; negative margins keep the visual spacing unchanged.
   =========================================================================== */
/* Keyed on width, not (pointer: coarse). The pointer query is the more correct
   signal but it cannot be verified in a desktop test harness — an iframe at
   375px still reports pointer:fine — and a rule you cannot check is a rule that
   silently rots. Width covers every real phone. */
@media (max-width: 700px) {
  .footer-links a,
  .contact-meta a,
  .nav-panel-meta a {
    display: inline-block;
    padding: 12px 4px;
    margin: -12px 0;
  }

  .footer-links { gap: 6px 20px; }

  .skip-link { padding: 14px 18px; }

  /* Sub-tiles and photo-strip thumbs are tapped, so give them a floor even
     when the grid squeezes them. */
  .work-shot { min-height: 62px; }
}

/* No max-height crop on gallery frames. Capping them changed each image's
   effective aspect ratio, which made the masonry columns look inconsistent —
   and two columns had already halved the page length on its own. */


/* ===========================================================================
   TITLE + CLIENT LOGO ON ONE LINE
   The logo previously sat on its own line under the title, which read as a
   stray image rather than a credit. Baseline-ish alignment on a single row
   keeps them associated.
   =========================================================================== */
.work-titlerow {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin: 8px 0 16px;
}

.work-titlerow .work-title { margin: 0; }

.work-titlerow .work-client {
  height: clamp(34px, 3.4vw, 52px);
  width: auto;
  max-width: 210px;
  margin: 0;
  flex: 0 0 auto;
}

@media (max-width: 520px) {
  .work-titlerow { gap: 12px; }
  .work-titlerow .work-client { height: 30px; max-width: 140px; }
}

/* Contact steps centred. The grid's three 1fr columns already fill their
   container, so justify-content does nothing on its own — the block needs a cap
   before centring means anything. Text is centred too, to match the lede above. */
#contact .contact-steps {
  max-width: 880px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

#contact .contact-steps li {
  display: flex;
  flex-direction: column;
  align-items: center;
}

@media (max-width: 700px) {
  #contact .contact-steps {
    text-align: left;
  }
  #contact .contact-steps li { align-items: flex-start; }
}

/* Team photos sat top-aligned against a taller column of prose, so they read as
   pushed down the page. Centring the two columns lines the faces up with the
   paragraphs beside them. */
#about .about-wrap { align-items: center; }

@media (max-width: 820px) {
  #about .about-wrap { align-items: start; }
}

/* ===========================================================================
   PARTICLE LANDING
   The first screen. Nothing on it but the wordmark — drawn as ~23,000 particles
   on a canvas by ParticleSlider — and a scroll chevron. See the markup comment
   in index.html and initLanding() in js/main.js.
   =========================================================================== */
.landing {
  position: relative;
  height: 100vh;
  height: 100svh;
  min-height: 520px;
  /* Deliberately no background. body already paints --ink plus the grain
     texture and the ambient green wash in body::before, and an opaque colour
     here would hide both — which is exactly what it was doing. Letting them
     through means this screen matches the rest of the site by construction
     rather than by a duplicated copy of the noise data URI. The canvas above is
     transparent except where particles are drawn, so the grain reads through
     the wordmark's gaps too. */
  overflow: hidden;
  /* The blanket `section` rule adds a bottom margin (100.8px at 1440) and inset
     padding. The margin has to go — it would leave a band of bare background
     between the particles and the hero video. The padding is harmless, since
     everything in here is absolutely positioned against the padding box. */
  margin-bottom: 0;
  /* The canvas is a full-viewport bitmap being rewritten every frame. Containing
     its paint keeps that work off the rest of the document. */
  contain: paint;
}

#particle-slider {
  position: absolute;
  inset: 0;
}

/* ParticleSlider reads .slides for its slide list but never shows it, and it
   appends an empty <p> overlay to the container for slide captions we don't
   have. Neither should take part in layout. (The three scratch canvases it also
   appends already carry inline display:none.) */
#particle-slider > .slides,
#particle-slider > p {
  display: none;
}

/* Sized in pixels by the library to match the container. Centred so that if its
   backing width ever lands a pixel short of the section, the gap is split either
   side instead of showing as a strip down one edge. */
#particle-slider > .draw {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: block;
}

/* The wordmark as real text: the no-JS and reduced-motion rendering, the
   accessible one, and the spec the particles are built from. initLanding() reads
   this element's computed font and colour and draws the particle source image
   from them, so changing the type here changes the particles with it. Colour is
   the one to reach for: white now, var(--green-bright) for green. */
.landing-wordmark {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  font-family: "Lemon Milk Bold Italic", sans-serif;
  font-size: clamp(2.6rem, 12.5vw, 11.5rem);
  line-height: 1;
  /* One flat shade, no gradient — sits between --green-bright (#10B981) and
     --green (#046C4E), so it reads as the same brand green without the accent
     colour's mintiness. Slightly lighter than the #0EA674 used while the source
     carried a gradient: that gradient lifted the top of the letters a third of
     the way to white, so removing it dropped the perceived weight and the flat
     value has to come up to meet it. Going further down the ramp doesn't work —
     the grain and the ambient wash tint the background green as well, and the
     wordmark is particles with gaps that let it show through, so a deep green
     stops separating from it. --green itself is visibly muddy here. */
  color: #12AE7D;
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
  transition: opacity .6s ease;
}

/* Set by initLanding() only once particles are actually on screen, so a failure
   to start leaves the text where it is rather than showing an empty screen.
   Stays in the accessibility tree — the canvas is aria-hidden, this is what a
   screen reader reads. */
.landing.is-particles .landing-wordmark {
  opacity: 0;
  pointer-events: none;
}

.landing-scroll {
  position: absolute;
  left: 50%;
  bottom: clamp(18px, 3.4vh, 40px);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  /* Wider than the glyph so the whole label is a tap target, and tall enough to
     clear 44px on a phone. */
  min-width: 96px;
  padding: 8px 12px 6px;
  text-decoration: none;
  color: rgba(255, 255, 255, .55);
  transition: color .3s ease;
}

/* Inherits the treatment the hero's old vertical "Scroll" used, set horizontally. */
.landing-scroll-label {
  font-family: "Lemon Milk Medium Italic", sans-serif;
  font-size: .66rem;
  letter-spacing: .3em;
  text-transform: uppercase;
  /* The tracking is applied to the last letter too, which visually shifts the
     word left of centre; the indent puts it back. */
  text-indent: .3em;
  line-height: 1;
}

.landing-scroll svg {
  width: 26px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.landing-scroll:hover,
.landing-scroll:focus-visible { color: #fff; }

/* A slow nudge down — enough to read as "there's more below", not enough to
   compete with the particles. */
@media (prefers-reduced-motion: no-preference) {
  .landing-scroll svg { animation: landing-nudge 2.4s ease-in-out infinite; }
}

@keyframes landing-nudge {
  0%, 100% { transform: translateY(0);   opacity: .7; }
  50%      { transform: translateY(6px); opacity: 1; }
}

/* While the landing owns the screen, it is the only thing on it: the nav and the
   progress bar step out, and come back the moment it is scrolled away. Keyboard
   users get the nav back on focus, since otherwise they would be tabbing through
   links they cannot see. */
.scroll-progress { transition: opacity .5s ease; }

html.landing-active #site-nav,
html.landing-active .scroll-progress {
  opacity: 0;
  pointer-events: none;
}

html.landing-active #site-nav { transform: translateY(-100%); }

html.landing-active #site-nav:focus-within {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* ===========================================================================
   CONTACT FORM STATUS
   The form posts over AJAX so nobody is sent to formsubmit.co and back; these
   are the states that replace that round trip. See initContactForm().
   =========================================================================== */
.form-status {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: .98rem;
  line-height: 1.5;
  margin: 0;
  color: rgba(255, 255, 255, .8);
}

.form-status:empty { display: none; }

.form-status a {
  color: var(--green-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Sent: the fields and the button have done their job, so the confirmation takes
   their place. The hidden config inputs are display:none already, so the flex
   column collapses to just this message. */
.contact-form.is-sent > label,
.contact-form.is-sent > input,
.contact-form.is-sent > textarea,
.contact-form.is-sent > button {
  display: none;
}

.contact-form.is-sent .form-status {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  color: #fff;
  text-align: center;
  padding: 30px 24px;
  border: 1px solid rgba(16, 185, 129, .45);
  border-radius: 12px;
  background: rgba(16, 185, 129, .08);
}

.contact-form.is-sent .form-status:focus-visible {
  outline: 2px solid var(--green-bright);
  outline-offset: 3px;
}

.contact-form.is-error .form-status {
  color: #ffd9d0;
}

/* ===========================================================================
   ARABIC + RTL
   Everything here is additive and scoped to html.ar or [dir="rtl"]. Not one
   existing declaration was changed, so the English rendering is byte-identical
   to what it was before Arabic existed.
   =========================================================================== */

/* --- the language switch ------------------------------------------------- */
.lang-switch {
  display: flex;
  align-items: stretch;
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 999px;
  overflow: hidden;
  margin-inline-start: clamp(8px, 1.4vw, 20px);
}

.lang-opt {
  font-family: "Lemon Milk Medium", sans-serif;
  font-size: .72rem;
  letter-spacing: .06em;
  line-height: 1;
  color: rgba(255, 255, 255, .72);
  text-decoration: none;
  padding: 8px 13px;
  background: transparent;
  transition: background-color .3s ease, color .3s ease;
  white-space: nowrap;
}

/* The Arabic label is set in Arabic type, in both languages — it is a sample of
   the thing you are switching to. */
.lang-opt[lang="ar"] {
  font-family: "Almarai", sans-serif;
  font-size: .82rem;
  letter-spacing: normal;
}

.lang-opt:hover { color: #fff; background: rgba(255, 255, 255, .08); }

.lang-opt.is-active {
  color: #06231a;
  background: var(--green-bright);
}

/* nav a::after underlines every nav link; the switch is not a nav link. */
.lang-opt::after { display: none; }

.lang-switch-panel {
  align-self: center;
  margin: 26px 0 0;
  margin-inline-start: 0;
}

@media (max-width: 900px) {
  /* The bar is tight at this width and the panel carries its own switch. */
  #site-nav .lang-switch { display: none; }
}

/* --- Arabic typography ---------------------------------------------------- */
/* Lemon Milk has no Arabic glyphs at all, and neither IBM Plex Sans nor Raleway
   covers Arabic, so every family in the design has to be remapped.

   Done structurally rather than by listing selectors. Sixty-three separate
   font-family declarations exist across this stylesheet, and an enumerated list
   silently missed .hero-sub, .film-desc, .film-soon and footer — Arabic in those
   fell back to whatever the OS chose. A catch-all that display faces then
   override cannot miss anything, and any future element is covered by default.

   Specificity, since it is doing real work and one detail here is a trap:
     html.ar body *                       (0,1,2)  the Arabic body face
     html.ar body :is(...)                (0,2,2)  display faces, so these win
     html.ar body :is(.brand, .wordmark)  (0,2,1)  the logo exemption

   :is() takes the specificity of its *most specific* argument, and the display
   list contains compounds like `.footer-links a` (0,1,1) rather than only bare
   classes — which makes that rule (0,2,2), not the (0,2,1) it looks like. The
   exemption below is therefore weaker and loses on specificity no matter how
   late it comes, which is why .brand is excluded from the list with :not()
   instead of merely overridden. .brand was matching it through `nav a`.
   Almarai carries the display weight; IBM Plex Sans Arabic is the direct sibling
   of the IBM Plex Sans used for English body copy, so the two languages match. */
html.ar body * {
  font-family: "IBM Plex Sans Arabic", sans-serif;
  /* Two things that quietly ruin Arabic if left alone.
     letter-spacing breaks the script: Arabic letters join, and the .18em-.3em
     tracking this design uses pulls the joins apart so words look misspelt.
     Almarai has no italic, so the italic Lemon Milk faces would be synthesised
     by slanting glyphs, which is wrong in Arabic and hurts legibility. */
  letter-spacing: normal;
  font-style: normal;
}

html.ar body :is(h1, h2, h3, .hero-title, .impact-slide, .section-eyebrow,
  .service-title, .film-title, .film-tag, .film-group-label, .film-soon,
  .work-title, .work-kicker, .work-filter, .work-shots-label, .work-extra-label,
  .workflow-label, .timeline-tag,
  .team-name, .contact-step-label, .landing-scroll-label,
  .hero-cta, .lightbox-title, .films-cta, .tile-badge,
  .skip-link, nav a, .nav-panel a, .footer-links a, .contact-form button):not(.brand) {
  font-family: "Almarai", sans-serif;
}

/* The big display headings carry Lemon Milk's weight in Latin; Almarai needs its
   heaviest cut to hold the same presence. */
html.ar body :is(h2.reveal-heading, .hero-title) { font-weight: 800; }

/* Arabic sits taller in the line than Latin at the same size. */
html.ar body :is(p, li, .service-desc, .hero-sub, .section-lede, .film-desc,
  .contact-step-desc, .about-story p, .work-list li):not(.landing-wordmark) {
  line-height: 1.85;
}

/* The logo is Latin in both languages: it is the mark, not copy. Kept LTR so the
   particle source still renders the right way round.

   Note what is NOT here: font-style. Every Lemon Milk @font-face at the top of
   this file is registered `font-style: normal` — the slant is baked into the OTF,
   it is not a style variant. Asking for `font-style: italic` therefore finds no
   italic face and makes the browser synthesise an oblique on top of glyphs that
   already lean, so the wordmark rendered double-slanted and visibly wrong. The
   canvas inherited the same bad font string, because initLanding() builds it from
   this element's computed style. The catch-all's `font-style: normal` is exactly
   right for these, and matches what English does. */
html.ar body :is(.brand, .landing-wordmark) {
  font-family: "Lemon Milk Bold Italic", sans-serif;
  direction: ltr;
}

/* Latin numerals stay in Latin type. The step numbers, the pinned counter and the
   lightbox counter are 01-05 and 1 / 20 — digits, not Arabic script, and they are
   a display element of the design rather than copy. Remapping them to Almarai
   changed the look of the How We Work cards for no reason. Kept out of the
   display list above so the catch-all is all they have to beat. */
html.ar body :is(.workflow-num, .contact-step-num) {
  font-family: "Lemon Milk Bold Italic", sans-serif;
}

/* .contact-step-num is a span inside an <li>, so it inherited the 1.85 above and
   its line box grew from normal to 23.68px. Only this one needs pinning back:
   .workflow-num carries its own line-height: 1, and setting `normal` on both
   clobbered the big ghost numeral's line box (158.4px -> normal). */
html.ar body .contact-step-num { line-height: normal; }

html.ar body :is(.workflow-counter, .workflow-counter-now, .lightbox-counter) {
  font-family: "Lemon Milk Medium", sans-serif;
}

/* --- mirrored positions --------------------------------------------------- */
/* Only the declarations that are genuinely direction-dependent. left:0/right:0
   pairs and left:50% centring are symmetric and need nothing. */
[dir="rtl"] .skip-link { left: auto; right: 12px; }
[dir="rtl"] h2.reveal-heading::after { left: auto; right: 0; }
[dir="rtl"] .service-glow { right: auto; left: -150px; }
[dir="rtl"] .film-soon { left: auto; right: 14px; }
[dir="rtl"] .workflow-num { right: auto; left: 18px; }
[dir="rtl"] .work-extra-label { left: auto; right: 10px; }
[dir="rtl"] .lightbox-close { right: auto; left: 0; }
[dir="rtl"] .lightbox-counter { left: auto; right: 0; }
[dir="rtl"] .tile-badge { right: auto; left: 14px; }
[dir="rtl"] .work-link:hover::after { margin-left: 0; margin-right: 6px; }
/* These two mirror the *mobile* English rules, which drop the contact lede and
   steps from centred to text-align:left below 700px. They were written at top
   level, so they also outscored the centring on desktop — Arabic read
   right-aligned at every width while English read centred, which is not the
   parity the rest of this file keeps. The giveaway was the .contact-steps rule
   below being an exact duplicate of the top-level one: dead unless the pair was
   always meant to be scoped. */
@media (max-width: 700px) {
  [dir="rtl"] #contact .contact-lede { text-align: right; }
  [dir="rtl"] #contact .contact-steps { text-align: right; }
}

/* The impact lines alternate sides so the section drifts across the screen.
   English is `odd { margin-left: 6%; margin-right: auto }` and the reverse for
   even, so the mirror is simply those two swapped. My first attempt invented
   0/0 and 0/auto without reading the original, which put the lines in positions
   that matched neither language.

   Scoped above 700px because the mobile rule centres both with margin:auto, and
   `[dir="rtl"] .impact-slide:nth-child(odd)` at (0,3,0) outscores the bare
   `.impact-slide:nth-child(odd)` at (0,2,0) — so an unscoped rule here would win
   inside that media query and un-centre the phone layout. */
@media (min-width: 701px) {
  [dir="rtl"] .impact-slide:nth-child(odd)  { margin-left: auto; margin-right: 6%; }
  [dir="rtl"] .impact-slide:nth-child(even) { margin-left: 6%;   margin-right: auto; }
}

/* The play triangle stays pointing right in both languages — that is the
   universal convention, and mirroring it would read as rewind. */
[dir="rtl"] .film-play::before,
[dir="rtl"] .work-extra .film-play::before {
  transform: none;
}

/* ===========================================================================
   GALLERY MASONRY — the JS-built version
   .masonry-grid keeps its column-count rules above as the no-JS fallback. Once
   initMasonry() has distributed the tiles into .masonry-col containers it adds
   .is-flex, and these rules take over. See the comment on initMasonry for why
   the browser's own column balancing was not good enough.
   =========================================================================== */
.masonry-grid.is-flex {
  display: flex;
  align-items: flex-start;
  /* Was a flat 16px, which at a 1900px viewport is under 4% of a 440px tile — the
     photos read as one continuous mass rather than as separate frames. Scaling
     with the viewport keeps the breathing room proportional to how large the
     tiles actually get. */
  /* Columns are real elements now, so column-count has to go or the browser would
     try to column-ise each .masonry-col as well.

     column-gap is deliberately NOT reset here. It looks like a multi-column
     property, but it is the same property flex and grid use for the inline axis,
     and `gap` below is shorthand for row-gap plus column-gap. Resetting it after
     the shorthand zeroed the horizontal spacing, so the tiles had 28px between
     rows and nothing at all between columns — the "stuck together" look. */
  column-count: initial;
  gap: var(--masonry-gap);
}

.masonry-col {
  /* 1 1 0 with min-width 0 so four columns share the row evenly and a wide image
     cannot push its column past its share. */
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--masonry-gap);
}

/* The fallback spaces tiles with margin-bottom; the flex columns use gap, so the
   margin would double the rhythm. */
.masonry-grid.is-flex .masonry-item {
  margin: 0;
  break-inside: auto;
}

/* One value drives the column gap, the row gap and the no-JS fallback, so they
   cannot drift apart. initMasonry reads it back off the computed style to fold
   the row gap into the heights it balances. */
:root { --masonry-gap: clamp(10px, 1.5vw, 28px); }
