/* ==========================================================================
   Floc.ai landing page styles (floc.systems)
   --------------------------------------------------------------------------
   TABLE OF CONTENTS
     1. Design tokens (colors, radii, shadows, scale) -- change the brand here
     2. Base / reset
     3. Section blending (ramp gradients)
     4. Shared (labels, headings, buttons)
     5. Navigation
     6. Hero
     7. Scrubbed film sections (sensor suite / feedback loop)
     8. Hardware stack / proof / full-bleed media / panels / feature row
     9. Partners, founders, demo, field gallery
    10. Closing, footer
    11. Reveals and reduced-motion fallbacks

   Most copy/text edits happen in index.html, not here.
   Indonesian copy lives in js/i18n.js.
   Edit this file when changing colors, spacing, or layout.

   RULES (not style preferences -- see README.md):
     - --bg-soft is ONLY for sections with no white-background imagery
     - --blue-light is large text / UI only (3.51:1)
     - White on --orange clears AA large only; button labels are bold

   UNITS: every length here is rem, so the whole page scales from one number
   (the root font size). Exceptions are deliberate and marked: hairline borders
   and outlines stay in px so they stay crisp, and media queries are px because
   they describe the device, not the design.
   ========================================================================== */


/* ============================================================
   1. TOKENS
   ============================================================ */
:root{
  --bg:#FFFFFF;
  --bg-soft:#F5F2EC;   /* ONLY in sections with no white-background visual */
  --ink:#123238;
  --ink-rgb:18,50,56;   /* RGB triplet of --ink, used for rgba() shadows */
  --ink-muted:#4A6169;
  --blue:#147D84;
  --blue-deep:#0E6068;
  --blue-light:#0097A7;
  --orange:#CE7F0D;
  --orange-hover:#B26B00;
  --orange-text:#96590A;
  --green:#17753F;        /* WhatsApp action only */
  --green-hover:#166E3C;
  --line:#E6E0D5;

  /* One dark family, two forms of it. --ink-rgb is the flat fill used on
     on-ink bands, the footer and small dark chips. --scrim-rgb is a touch
     deeper and is ONLY for a translucent wash over a photo or video, where
     the darkening has to fight the image underneath. Every scrim on the page
     (hero, closing, gallery captions, the contact backdrop) shares this one
     value now instead of four hand-tuned near-misses, so the dark moments on
     this page read as one deliberate color instead of a coincidence. */
  --ink-rgb:18,50,56;
  --scrim-rgb:8,32,38;

  /* Faint contour lines, tiled behind the cream (--bg-soft) sections only.
     Those sections never carry a photo or a video -- see the --bg-soft rule
     above -- so a texture here can never sit on top of media. Kept at ~4.5%
     stroke opacity: felt as depth, not read as a pattern. */
  --contour:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='600' viewBox='0 0 600 600'%3E%3Cg fill='none' stroke='%23147D84' stroke-opacity='0.045'%3E%3Cpath d='M-50 70C70 10 190 130 310 70S550 10 670 70'/%3E%3Cpath d='M-50 190C70 130 190 250 310 190S550 130 670 190'/%3E%3Cpath d='M-50 310C70 250 190 370 310 310S550 250 670 310'/%3E%3Cpath d='M-50 430C70 370 190 490 310 430S550 370 670 430'/%3E%3Cpath d='M-50 550C70 490 190 610 310 550S550 490 670 550'/%3E%3C/g%3E%3C/svg%3E");

  --r-md:.625rem; --r-lg:1rem; --r-pill:999px;
  --sh1:0 .0625rem .125rem rgba(18,50,56,.06);
  --sh2:0 .5rem 1.5rem rgba(18,50,56,.09);
  --sh3:0 1.125rem 2.75rem rgba(18,50,56,.12);
  --sh-btn:0 .125rem .375rem rgba(18,50,56,.14), 0 .5rem 1.25rem rgba(18,50,56,.10);
  --ease-in:cubic-bezier(.22,.61,.36,1);
  --ease-state:cubic-bezier(.4,0,.2,1);
  --container:77.5rem;

  /* One vertical rhythm for the whole page. Sections used to carry four
     different padding formulas, which is why the gaps between them never
     looked related to each other. */
  --sp-sec:clamp(2.75rem, 7vh, 5.5rem);
  --sp-sec-lg:clamp(3.75rem, 10vh, 6.875rem);
  --sp-gutter:1.5rem;
}
@media(min-width:900px){ :root{ --sp-gutter:2.5rem } }

/* ------------------------------------------------------------------
   GLOBAL SCALE
   The desktop composition was drawn oversized and then squeezed back down
   with html{zoom:.8}. That had to go. zoom is not implemented consistently
   (Firefox ignored it entirely before 126), it makes getBoundingClientRect
   report painted rather than layout pixels -- which is why the old JS had to
   divide the zoom back out by hand before it could position anything -- and
   because it scaled px and viewport units by different amounts in practice,
   half the stylesheet had to be written as calc(x / var(--z)) and the other
   half not. The two halves drifted apart at every breakpoint, and that drift
   is what read as inconsistent spacing.

   Scaling the root font size does the same job in plain CSS. Every length in
   this file is rem, so one number moves all of them, layout maths stays
   honest, and all four engines agree.

   The ramp runs across 1000-1240px rather than switching at a single pixel.
   The old media query dropped body copy from 16px to 12.8px between one pixel
   of window width and the next.

   rem on the root element resolves against the reader's default font size, so
   a reader who has set a larger default still gets it.
   ------------------------------------------------------------------ */
html{ font-size:100% }
@media(min-width:1000px){
  html{ font-size:clamp(.8rem, calc(1rem - (100vw - 1000px) * .0133333), 1rem) }
}

/* Measured contrast. Two backgrounds, and they do not behave the same.

   On --bg #FFFFFF:
     --ink 14.13 | --ink-muted 6.42 | --blue 4.89 | --blue-deep 6.71
     --blue-light 3.51  LARGE TEXT AND UI ONLY
     --orange      3.15 fills, rules and buttons. White on it clears AA large only,
                        which is why button labels are bold. Switch --orange to
                        #A66300 if you want the button AA at any size.
     --orange-text 5.63 every small orange word
     --green       5.86 with white on it, so the WhatsApp button is AA at any size

   On --bg-soft #F5F2EC:
     --ink 12.21 | --ink-muted 5.86 | --orange-text 5.04 | --blue-deep 6.50
     --blue        4.37 DROPS BELOW AA for normal text on the soft background.
                        Fine for h2 and other display type, which is all it is
                        used for there. Anything smaller on --bg-soft must use
                        --blue-deep. This is why .quote uses --blue-deep. */


/* ============================================================
   2. BASE
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html{
  scroll-behavior:smooth;
  -webkit-text-size-adjust:100%;
  /* Anchor links used to drop their target underneath the fixed header.
     --nav-h is measured in js/main.js. */
  scroll-padding-top:calc(var(--nav-h, 4rem) + 1rem);
  /* clip (not hidden): overflow-x:hidden makes overflow-y compute to auto,
     which breaks position:sticky and kills the scrubbed film sections. */
  overflow-x:clip;
}
body{margin:0;background:var(--bg);color:var(--ink);
  font:400 1rem/1.6 "IBM Plex Sans",system-ui,sans-serif;-webkit-font-smoothing:antialiased;
  overflow-x:clip}
h1,h2,h3{font-family:"Montserrat",system-ui,sans-serif;line-height:1.1;letter-spacing:-.02em;margin:0;color:var(--blue)}
h1{font-weight:800;font-size:clamp(1.875rem,4.2vw,3.125rem)}
h2{font-weight:800;font-size:clamp(1.5625rem,3vw,2.375rem)}
h3{font-weight:700;font-size:1.1875rem;letter-spacing:-.01em}
p{margin:0}
img,video,canvas{max-width:100%;height:auto;display:block}
a{color:var(--blue-deep)}
:focus-visible{outline:3px solid var(--orange);outline-offset:3px}
em{font-style:normal}

/* Visually hidden until focused — first Tab lands here before the sticky nav. */
.skip-link{
  position:absolute;left:var(--sp-gutter, 1.25rem);top:.75rem;z-index:100;
  padding:.625rem 1rem;background:var(--ink);color:#fff;
  border-radius:var(--r-md);font-weight:600;text-decoration:none;
  transform:translateY(-200%);
  transition:transform .15s var(--ease-state);
}
.skip-link:focus,.skip-link:focus-visible{transform:translateY(0);color:#fff}

.wrap,.wide{max-width:var(--container);margin:0 auto;padding:0 var(--sp-gutter)}
section{padding:var(--sp-sec) 0}
.soft{background:var(--bg-soft)}
/* Dark teal bands break the long white scroll (see demo for the reference
   treatment). White type on --ink clears AAA. Blue tags step up to --blue so
   they stay distinct from the band. */
.on-ink{background:var(--ink);color:#fff}
/* Headings in the band itself go white. Type inside white cards (.founder,
   .inst) keeps its own ink colours — do not blanket-reset h3. */
.on-ink h1,.on-ink h2,.on-ink .head h3{color:#fff}
.on-ink > .wrap > .lede,
.on-ink .head .lede,
.on-ink .bleed-sub,
.on-ink .bleed-sub p{color:rgba(255,255,255,.78)}
.on-ink .tag.blue{background:var(--blue)}
.on-ink .ticks li{color:rgba(255,255,255,.78)}
.on-ink .quote{color:rgba(255,255,255,.9)}
.on-ink .quote b{color:#F0B761}
/* Only use .on-ink on text/card sections — never behind product stills or
   diagram clips that are authored to dissolve into white. */
.soft.ramp-soft,.soft.ramp-soft-top{background-color:transparent}
/* .ramp-* set the background shorthand, which already clears background-color.
   This rule only guards against .soft winning on specificity if order changes. */

/* Body scroll lock while the phone menu is open. Without it the page scrolls
   away behind an open menu, which on a phone reads as the menu being broken. */
body.menu-open{overflow:hidden}


/* ------------------------------------------------------------------
   3. SECTION BLENDING
   Real background gradients rather than a translucent overlay on a flat fill.
   Stacking alpha over a solid colour bands visibly; a continuous colour ramp
   has nothing to band.

   The ramp length is capped against the section's own height. A short section
   used to get two 150px ramps that overlapped in the middle, so it never
   reached the soft colour at all and just looked like a grey wash.
   ------------------------------------------------------------------ */
.ramp-soft{background:var(--contour) repeat, linear-gradient(180deg,
  var(--bg) 0, var(--bg-soft) min(9.375rem, 22%),
  var(--bg-soft) calc(100% - min(9.375rem, 22%)), var(--bg) 100%)}
.ramp-soft-top{background:var(--contour) repeat, linear-gradient(180deg,var(--bg) 0,var(--bg-soft) min(9.375rem, 22%))}

/* A couple of the light-to-dark handoffs get an actual wave instead of a
   straight line -- water rather than a rectangle, and it doubles as the
   visual cue that a color shift is intentional rather than incidental. Each
   divider sits between two sections in normal flow: its own background
   matches the section above, and the path fill matches the section below. */
/* margin-bottom:-1px pulls the next (solid-color) section up over the wave's
   bottom edge by a hair. Without it, sub-pixel rounding on the scaled SVG
   sometimes leaves a hairline of the divider's own background showing
   between the painted wave and the band below -- a white seam where there
   should be a clean water-to-wall edge. The overlap is invisible because the
   colours already match; it just erases the rounding error. */
.wave-divider{display:block;width:100%;line-height:0;margin-bottom:-1px;overflow:hidden}
/* Each wave's markup is one curve drawn twice back to back, at double width
   (viewBox 2880 wide instead of 1440), with the join built so the curve
   starts and ends at the same y -- the two copies tile with no visible seam.
   Sliding the whole thing left by exactly half its width (one period) and
   snapping back is therefore invisible, which is what makes the loop read as
   continuous drifting water instead of a repeat. It's a single transform on
   an SVG, so it costs nothing to speak of even on a low-end phone. */
.wave-divider svg{display:block;width:200%;height:clamp(1.25rem,3.6vw,3rem);
  animation:wave-drift 42s linear infinite}
@keyframes wave-drift{to{transform:translateX(-50%)}}
.wave-to-ink{background:var(--bg)}
.wave-to-ink svg path{fill:var(--ink)}
.wave-to-dark{background:var(--bg-soft)}
.wave-to-dark svg path{fill:var(--ink)}
/* The reverse handoffs -- an ink band waving back out into a lighter section
   below it -- so a dark band gets a wave on both edges instead of a wave in
   and a hard line out. */
.wave-ink-to-bg{background:var(--ink)}
.wave-ink-to-bg svg path{fill:var(--bg)}
.wave-ink-to-soft{background:var(--ink)}
.wave-ink-to-soft svg path{fill:var(--bg-soft)}

@media(max-width:700px){
  .ramp-soft{background:var(--contour) repeat, linear-gradient(180deg,
    var(--bg) 0, var(--bg-soft) min(5.625rem, 16%),
    var(--bg-soft) calc(100% - min(5.625rem, 16%)), var(--bg) 100%)}
  .ramp-soft-top{background:var(--contour) repeat, linear-gradient(180deg,var(--bg) 0,var(--bg-soft) min(5.625rem, 16%))}
}


/* ============================================================
   4. SHARED. Headings and subheads left aligned throughout.
   ============================================================ */
/* Section labels. Filled pills (not outline) so they read against white and
   soft backgrounds instead of blending into the page. Orange / blue variants
   match the section they introduce. Not interactive — pointer-events none
   keeps them from feeling like buttons. */
.tag{display:inline-flex;align-items:center;justify-content:center;
  font:700 .75rem/1 "Montserrat",system-ui,sans-serif;letter-spacing:.08em;
  text-transform:uppercase;color:#fff;background:var(--orange);
  padding:.5rem .875rem;border-radius:var(--r-pill);margin-bottom:.875rem;
  pointer-events:none;width:fit-content}
.tag.blue{background:var(--blue-deep);color:#fff}

.lede{font-size:clamp(1rem,1.3vw,1.125rem);color:var(--ink-muted);max-width:62ch;margin-top:1rem}
.head{max-width:45rem;margin-bottom:clamp(1.375rem,3.4vh,2.75rem);text-align:left}
.head h2{text-wrap:balance}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5625rem;
  font:600 1rem/1 "Montserrat",system-ui,sans-serif;padding:.875rem 1.625rem;border-radius:var(--r-pill);
  text-decoration:none;border:2px solid transparent;cursor:pointer;
  min-height:2.875rem;box-shadow:var(--sh-btn);
  transition:transform .16s var(--ease-state),box-shadow .16s var(--ease-state),background .16s var(--ease-state),border-color .16s var(--ease-state),color .16s var(--ease-state)}
.btn:hover{transform:translateY(-2px);box-shadow:var(--sh3)}
/* A brief ring on click rather than a flat state change -- water, on the one
   element that is on every screen of the page. It lives on ::after so the
   button's own box-shadow (painted on .btn itself) is never clipped: the
   pseudo-element gets its own overflow:hidden and border-radius, .btn keeps
   its shadow untouched. js/main.js only ever sets --rip-x/--rip-y and toggles
   .is-rippling; the animation and its clipping are entirely CSS. */
.btn{position:relative}
.btn::after{content:"";position:absolute;inset:0;border-radius:inherit;overflow:hidden;pointer-events:none;
  background:radial-gradient(circle,rgba(255,255,255,.5) 0%,rgba(255,255,255,.16) 45%,transparent 70%);
  opacity:0;transform:scale(0);transform-origin:var(--rip-x,50%) var(--rip-y,50%)}
.btn.is-rippling::after{animation:btn-ripple .6s var(--ease-state)}
@keyframes btn-ripple{
  0%{opacity:.8;transform:scale(0)}
  55%{opacity:.5}
  100%{opacity:0;transform:scale(2.6)}
}
@media(prefers-reduced-motion:reduce){.btn::after{display:none}}
.btn-primary{background:var(--orange);color:#fff;font-weight:700}
.btn-primary:hover{background:var(--orange-hover)}
.btn-green{background:var(--green);color:#fff;font-weight:700}
.btn-green:hover{background:var(--green-hover)}
.btn-green svg{width:1.25rem;height:1.25rem;fill:currentColor;flex:none}
.btn-ondark{background:rgba(255,255,255,.14);color:#fff;border-color:rgba(255,255,255,.5);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.btn-ondark:hover{background:rgba(255,255,255,.24)}
.btn-ghost{background:rgba(255,255,255,.14);color:#fff;border-color:rgba(255,255,255,.45);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:none}
.btn-ghost:hover{background:rgba(255,255,255,.24);color:#fff}
.nav.stuck .btn-ghost,.nav.open .btn-ghost{background:transparent;color:var(--ink-muted);border-color:var(--line)}
.nav.stuck .btn-ghost:hover,.nav.open .btn-ghost:hover{color:var(--blue);border-color:var(--blue);background:rgba(20,125,132,.06)}


/* ============================================================
   5. NAV. Language selector sits right of Farm login.
   ============================================================ */
.nav{position:fixed;inset:0 0 auto 0;z-index:60;border-bottom:1px solid transparent;
  transition:background .3s var(--ease-state),box-shadow .3s,border-color .3s}
.nav.stuck{background:rgba(255,255,255,.78);border-bottom-color:rgba(230,224,213,.9);box-shadow:var(--sh1);
  backdrop-filter:blur(18px) saturate(1.2);-webkit-backdrop-filter:blur(18px) saturate(1.2)}
.nav-in{max-width:var(--container);margin:0 auto;padding:.8125rem var(--sp-gutter);
  display:flex;align-items:center;gap:1.125rem}
@media(min-width:900px){.nav-in{gap:1.5rem}}
.brand{display:flex;align-items:center;gap:.625rem;text-decoration:none;
  font:800 1.25rem/1 "Montserrat";color:#fff;letter-spacing:-.01em;transition:color .3s}
.nav.stuck .brand{color:var(--blue)}
.brand img{width:2rem;height:2rem;border-radius:50%}
.brand em{font-style:normal;color:#7FD4DE}
.nav.stuck .brand em{color:var(--blue-light)}
.nav-links{margin-left:auto;display:none;gap:1.375rem}
.nav-links a{color:rgba(255,255,255,.92);text-decoration:none;font:600 1rem/1 "Montserrat",system-ui,sans-serif;transition:color .2s}
.nav.stuck .nav-links a{color:var(--ink-muted)}
.nav.stuck .nav-links a:hover{color:var(--blue)}
.nav .btn{padding:.6875rem 1.25rem;font-size:1.0625rem;min-height:0;white-space:nowrap}
.nav .btn-ghost{padding:.625rem 1.125rem}

/* ------------------------------------------------------------------
   NAV, small screens
   Seven links plus a pill button plus two flags will not fit a phone header.
   Below 1040px everything except the wordmark and the burger moves into a
   panel, which stops the login button wrapping onto two lines.
   ------------------------------------------------------------------ */
.burger{display:none;width:2.625rem;height:2.625rem;flex:none;padding:0;border:0;
  background:transparent;cursor:pointer;border-radius:var(--r-md);
  align-items:center;justify-content:center;-webkit-tap-highlight-color:transparent}
.burger span{display:block;position:relative;width:1.375rem;height:2px;border-radius:2px;background:#fff;
  transition:background .2s var(--ease-state),transform .25s var(--ease-state)}
.burger span::before,.burger span::after{content:"";position:absolute;left:0;width:1.375rem;height:2px;
  border-radius:2px;background:inherit;transition:transform .25s var(--ease-state),opacity .2s}
.burger span::before{top:-.4375rem} .burger span::after{top:.4375rem}
.nav.stuck .burger span,.nav.open .burger span{background:var(--ink)}
.nav.open .burger span{background:transparent}
.nav.open .burger span::before{background:var(--ink);transform:translateY(.4375rem) rotate(45deg)}
.nav.open .burger span::after{background:var(--ink);transform:translateY(-.4375rem) rotate(-45deg)}

.nav-panel{display:none}

@media(min-width:1040px){
  .nav-links{display:flex}
  .nav .btn{margin-left:0}
}
@media(max-width:1039px){
  .nav-in{gap:.75rem}
  .nav .btn{display:none}               /* login + contact live in the panel */
  .nav > .nav-in > .lang{display:none}  /* flags move into the panel too */
  .burger{display:flex;margin-left:auto}
  .nav-panel{display:block;position:absolute;left:0;right:0;top:100%;
    background:rgba(255,255,255,.98);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
    border-bottom:0 solid var(--line);box-shadow:none;
    padding:0 var(--sp-gutter);max-height:0;overflow:hidden;
    transition:max-height .32s var(--ease-state),padding .32s var(--ease-state)}
  /* Border and shadow exist only while open. A border paints even at
     max-height:0, which drew a stray rule across the header on every page. */
  .nav.open .nav-panel{max-height:calc(100dvh - var(--nav-h, 4rem));
    padding:.625rem var(--sp-gutter) 1.375rem;overflow-y:auto;
    border-bottom-width:1px;box-shadow:var(--sh2)}
  .nav-panel a:not(.btn){display:block;padding:.8125rem .125rem;font:600 1.125rem/1 "Montserrat",system-ui,sans-serif;
    color:var(--ink);text-decoration:none;border-bottom:1px solid var(--line)}
  /* Never inherit the panel's ink colour onto an orange fill. White only. */
  .nav-panel .btn{display:inline-flex;margin-top:1rem;width:100%}
  .nav-panel .btn-primary{color:#fff}
  .nav-panel .btn-ghost{color:var(--ink);border-color:var(--line);background:transparent}
  .nav-panel .lang{display:flex;margin-top:1.125rem;justify-content:center;gap:.875rem}
  .nav-panel .lang button{width:2.75rem;height:2.75rem;border-color:var(--line)}
}
.lang{display:flex;gap:.375rem;flex:none}
.lang button{width:2.375rem;height:2.375rem;padding:0;border:2px solid rgba(255,255,255,.45);
  border-radius:50%;background:transparent;cursor:pointer;overflow:hidden;
  box-shadow:var(--sh1);-webkit-tap-highlight-color:transparent;
  transition:border-color .16s var(--ease-state),transform .16s var(--ease-state),opacity .16s}
.nav.stuck .lang button{border-color:var(--line)}
.lang button img{width:100%;height:100%;object-fit:cover;border-radius:50%}
.lang button:hover{transform:translateY(-2px)}
.lang button[aria-pressed="false"]{opacity:.5}
.lang button[aria-pressed="true"]{border-color:var(--orange);opacity:1}


/* ============================================================
   6. HERO
   ============================================================ */
/* svh, not dvh. On a phone dvh changes as the browser toolbar hides, so a dvh
   hero resizes underneath the reader on the first scroll and shoves the whole
   page up. svh is the toolbar-visible height and never changes. */
.hero{position:relative;min-height:86svh;display:flex;align-items:flex-end;overflow:hidden;padding:0}
/* Stage is the containing block for the photo, scrim, copy and fade.
   js/main.js measures #heroBg against this parent, not the viewport. */
.hero-stage{position:relative;width:100%;min-height:86svh;display:flex;align-items:flex-end}
/* Anchored to the bottom of the frame so the near tanks stay in shot. The
   overscan is what the parallax translates into; js/main.js reads --hero-over
   so it can never translate further than there is image, which used to expose
   a bare edge under the photograph on short viewports. */
.hero-bg{--hero-over:18%;position:absolute;inset:0 0 calc(-1 * var(--hero-over)) 0;
  height:calc(100% + var(--hero-over));z-index:0;will-change:transform}
.hero-bg img{width:100%;height:100%;object-fit:cover;object-position:center bottom}
.hero-scrim{position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg,rgba(var(--scrim-rgb),.58) 0%,rgba(var(--scrim-rgb),.28) 34%,rgba(var(--scrim-rgb),.78) 100%)}
.hero-in{position:relative;z-index:3;width:100%;max-width:var(--container);margin:0 auto;
  padding:7.5rem var(--sp-gutter) 3.5rem;color:#fff}
@media(min-width:900px){.hero-in{padding:9.375rem var(--sp-gutter) 5.5rem}}
.hero h1{color:#fff;max-width:22ch;text-shadow:0 2px 6px rgba(4,20,25,.55),0 10px 46px rgba(4,20,25,.6),0 0 90px rgba(4,20,25,.45)}
.hero h1 em{color:#7FD4DE}
.hero-sub{color:#fff;font:600 clamp(1rem,1.4vw,1.1875rem)/1.6 "Montserrat",system-ui,sans-serif;
  max-width:42ch;margin-top:1.25rem;
  text-shadow:0 2px 5px rgba(4,20,25,.62),0 8px 34px rgba(4,20,25,.55),0 0 70px rgba(4,20,25,.4)}
.hero-cta{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:2rem}
/* Same idea as .closing-fade-bottom: the wave below is a flat --ink fill, so
   this short ramp turns the photo into that colour before the curve starts. */
.hero-fade-bottom{position:absolute;bottom:0;left:0;right:0;z-index:2;
  height:clamp(3rem,9vw,5.5rem);pointer-events:none;
  background:linear-gradient(to top, var(--ink) 0%, rgba(var(--ink-rgb),0) 100%)}


/* ============================================================
   7. SCRUBBED FILM SECTIONS
   The sensor suite and the feedback loop. Scroll position drives a frame
   sequence drawn to a canvas (js/main.js). See tools/encode-frames.py for why
   this is not a video any more.
   ============================================================ */
.film{position:relative;background:var(--bg)}
/* Slide 13 band shade: --bg-soft only in the empty side margins so white
   product / diagram frames keep dissolving into --bg. Seams stay --bg so
   the wave dividers still meet a matching fill. */
.film,.hw{background:
  linear-gradient(180deg,var(--bg) 0,transparent 6.25rem,transparent calc(100% - 6.25rem),var(--bg) 100%),
  radial-gradient(ellipse 78% 72% at 50% 48%,var(--bg) 38%,var(--bg-soft) 100%)}
.film-track{height:240vh}
/* svh again: a dvh sticky pane resizes mid-scroll on iOS, and because the pane
   is what the scrub progress is measured against, the animation jumped every
   time the toolbar moved. */
.film-stick{position:sticky;top:0;height:100svh;
  padding:calc(var(--nav-h, 4rem) + 2vh) var(--sp-gutter) 2vh;
  display:grid;grid-template-rows:auto minmax(0,1fr) auto;
  justify-items:center;align-content:center;gap:clamp(.875rem,2.4vh,1.75rem)}
.film-head{width:min(86vw,1180px);text-align:center}
.film-head h2{font-size:clamp(1.25rem,min(3vw,4.2vh),2.375rem);line-height:1.12;text-wrap:balance}
.film-hint{margin-top:.9vh;font:500 clamp(.75rem,1.6vh,.9375rem)/1 "IBM Plex Sans";color:var(--ink-muted);
  opacity:.7;transition:opacity .4s linear}

.film-stage{position:relative;width:100%;max-width:min(86vw,1180px);min-height:0;height:100%;
  display:grid;place-items:center}
.film-stage canvas,.film-stage .film-still{width:auto;max-width:100%;max-height:100%;
  object-fit:contain}
/* The canvas replaces the still once frames start arriving. Both occupy the
   same grid cell so there is no reflow at the swap. */
.film-stage > *{grid-area:1/1}
.film-stage .film-still[hidden]{display:none}

/* Capped so a long caption can never squeeze the film to nothing, and so the
   longer Indonesian copy cannot overflow a short phone screen. */
.film-cap{width:min(51.25rem,94vw);text-align:center;max-height:34vh;overflow:hidden;
  opacity:0;transform:translateY(.625rem);
  transition:opacity .5s var(--ease-in),transform .5s var(--ease-in)}
.film-cap.on{opacity:1;transform:none}
.film-cap h3{font-size:clamp(.9375rem,min(2vw,2.4vh),1.4375rem);margin-bottom:.7vh;line-height:1.2}
.film-cap p{color:var(--ink-muted);font-size:clamp(.78125rem,min(1.35vw,1.85vh),1rem);line-height:1.45}

@media(max-width:700px){
  /* Pack head + visual + caption tight. The previous auto/1fr/auto rows left a
     tall empty band above and below the diagram (the image is only ~220px tall
     on a phone while the middle row ate the rest of the viewport). */
  .film-track{height:200vh}
  .film-stick{padding:calc(var(--nav-h, 4rem) + .35vh) .875rem .75vh;
    gap:.45rem;grid-template-rows:auto auto auto;align-content:center}
  .film-hint{margin-top:.25vh}
  .film-stage{height:auto;max-height:46vh;width:100%}
  .film-stage canvas,.film-stage .film-still{width:100%;max-height:46vh;height:auto}
  .film-cap{max-height:34vh}
  .film-cap h3{margin-bottom:.35vh}
}
/* A short landscape window (a laptop at 1440x760, or a phone turned sideways)
   is the case where three stacked rows genuinely do not fit. Drop the caption
   rather than clip the film. */
@media(max-height:520px){
  .film-cap{display:none}
  .film-hint{display:none}
}


/* ============================================================
   8. HANDHELD STACK. Unit large and vertical left, copy and spec sheet right.
   ============================================================ */
/* Three grid items rather than two nested columns, so phone order can differ
   from desktop. On a phone the copy comes first, otherwise the unit fills the
   screen with no heading anywhere in sight. */
/* Background is the shared .film,.hw shade above — do not reset to flat --bg. */
.hw-grid{display:grid;gap:1.625rem;grid-template-columns:1fr;
  grid-template-areas:"copy" "unit" "sheet"}
@media(min-width:900px){
  .hw{min-height:125vh;position:relative;padding:clamp(1.5rem,3.5vh,2.5rem) 0}
  .hw .wide{position:sticky;top:clamp(4.5rem,calc(50vh - 16.5rem),8rem)}
  .hw-grid{grid-template-columns:minmax(0,1.15fr) minmax(20rem,1.15fr);
    gap:0 clamp(1.5rem,3vw,3.5rem);align-items:center;row-gap:.875rem;
    grid-template-areas:"unit copy" "unit sheet"}
  .hw-copy{align-self:end}
  .hw-sheet{align-self:start;width:100%;max-width:44rem;justify-self:start}
  .hw-visual{align-self:center}
}
.hw-copy{grid-area:copy;text-align:left}
.hw-copy h2{margin:0 0 .4rem}
.hw-copy .lede{max-width:54ch;margin:0 0 .5rem}
.hw-visual{grid-area:unit;display:grid;place-items:center;position:relative;width:100%;
  min-height:min(78vh,680px)}
.hw-unit-view{position:absolute;inset:0;display:grid;place-items:center;
  opacity:1;transform:scale(1);
  transition:opacity .6s cubic-bezier(.2,.8,.2,1),transform .6s cubic-bezier(.2,.8,.2,1);z-index:2}
.hw-dock-view{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);
  gap:clamp(.625rem,1.15vw,.95rem);width:100%;max-width:40rem;position:absolute;inset:0;
  align-content:center;opacity:0;pointer-events:none;transform:scale(.96);
  transition:opacity .6s cubic-bezier(.2,.8,.2,1),transform .6s cubic-bezier(.2,.8,.2,1);z-index:1}
.hw-visual.show-dock .hw-unit-view{opacity:0;pointer-events:none;transform:scale(.96);z-index:1}
.hw-visual.show-dock .hw-dock-view{opacity:1;pointer-events:auto;transform:scale(1);z-index:2}

/* Picture is the containing block for the contact shadow. isolation keeps the
   pseudo behind the still but above the section fill. */
.hw-unit-view picture{position:relative;display:block;width:fit-content;max-width:100%;
  line-height:0;isolation:isolate}
/* Cap below a full viewport so the heading stays in frame, but keep the unit
   large enough that the product reads as the section hero. */
.hw-unit-view img{position:relative;z-index:1;width:auto;max-width:100%;
  max-height:min(78vh,680px);object-fit:contain;
  filter:drop-shadow(-18px 14px 28px rgba(20,20,25,.38)) drop-shadow(-4px 4px 12px rgba(20,20,25,.2))}
/* Slide 12/17: ambient lift behind the unit */
.hw-unit-view picture::after{content:"";position:absolute;z-index:0;pointer-events:none;
  inset:0;
  background:radial-gradient(ellipse 70% 55% at 47% 38%,
    rgba(20,20,22,.22) 0%,
    rgba(20,20,22,.12) 35%,
    rgba(20,20,22,.04) 60%,
    rgba(20,20,22,0)  78%);
  filter:blur(18px)}
.hw-sheet{grid-area:sheet;border:1px solid var(--line);border-radius:var(--r-lg);
  background:#fff;overflow:hidden}
/* Real HTML table (was a raster spec-sheet image that shrank unreadably on
   phones). Mirrors the old sheet: bordered panel, teal headers, gold rule,
   zebra rows, dotted separators, numbered italic labels, bold teal terms. */
.spec-table{width:100%;border-collapse:collapse;
  font:400 clamp(.75rem,.55rem + .3vw,.8125rem)/1.35 "IBM Plex Sans",system-ui,sans-serif;color:var(--ink-muted)}
.spec-table thead th{font:700 clamp(.6875rem,.5rem + .25vw,.78rem)/1.2 "Montserrat",system-ui,sans-serif;
  color:var(--blue-deep);text-align:left;padding:clamp(.32rem,.7vh,.55rem) clamp(.5rem,.75vw,.85rem) clamp(.25rem,.5vh,.45rem);
  border-bottom:2px solid var(--orange-text);background:#fff}
.spec-table tbody{counter-reset:spec}
.spec-table tbody th,.spec-table tbody td{padding:clamp(.24rem,.55vh,.42rem) clamp(.45rem,.75vw,.85rem);vertical-align:top;
  border-bottom:1px dotted var(--line);font-weight:400}
.spec-table tbody tr:last-child th,.spec-table tbody tr:last-child td{border-bottom:0}
.spec-table tbody tr:nth-child(even) th,.spec-table tbody tr:nth-child(even) td{background:#F7F5F1}
.spec-table tbody th{width:28%;max-width:11rem;
  font:italic 400 clamp(.72rem,.52rem + .28vw,.78rem)/1.35 "IBM Plex Sans",system-ui,sans-serif;color:var(--blue);
  counter-increment:spec}
.spec-table tbody th::before{content:counter(spec) " ";font-style:italic}
.spec-table tbody td b{font-weight:700;color:var(--blue-deep)}
.spec-table tbody td i{font-style:italic}

/* Dock reference photographs, matching Kemal slide 19.
   Each render carries its own pale studio backdrop, so the cell adds only a hairline and a radius
   and does not introduce a fill colour of its own. */
.hw-dock-cell{margin:0;border:1px solid var(--line);border-radius:var(--r-md);
  overflow:hidden;background:var(--bg);line-height:0}
.hw-dock-cell picture{display:block}
/* Fixed aspect box + object-fit means the six frames align even though the
   crops behind them are not identical. */
.hw-dock-cell img{display:block;width:100%;height:auto;aspect-ratio:800/618;
  object-fit:cover}
@media(max-width:899px){
  .hw{min-height:200vh;position:relative;padding:3rem 0 2rem}
  .hw .wide{position:sticky;top:5.5rem}
  .hw-grid{gap:1.5rem}
  .hw-copy{margin-top:1.25rem}
  .hw-copy h2{margin:0 0 .4rem}
  .hw-copy .lede{margin:0 0 .75rem}
  .hw-visual{display:grid;place-items:center;position:relative;width:100%;
    min-height:min(88vh, 560px);margin:0 0 1.5rem;isolation:isolate}
  .hw-unit-view{position:absolute;inset:0;display:grid;place-items:center;
    opacity:1;transform:scale(1);z-index:2;
    transition:opacity .5s cubic-bezier(.2,.8,.2,1),transform .5s cubic-bezier(.2,.8,.2,1)}
  .hw-dock-view{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);
    gap:.75rem;width:100%;max-width:36rem;position:absolute;inset:0;
    align-content:center;opacity:0;pointer-events:none;transform:scale(.96);z-index:1;
    transition:opacity .5s cubic-bezier(.2,.8,.2,1),transform .5s cubic-bezier(.2,.8,.2,1)}
  .hw-visual.show-dock .hw-unit-view{opacity:0;pointer-events:none;transform:scale(.96);z-index:1}
  .hw-visual.show-dock .hw-dock-view{opacity:1;pointer-events:auto;transform:scale(1);z-index:2}
  .hw-unit-view img{max-height:min(75vh, 480px);margin:0 auto}
  .hw-dock-cell img{aspect-ratio:800/618;object-fit:cover;border-radius:var(--r-md)}
  .hw-sheet{max-width:none;margin-top:3.5rem}
  .spec-table{font-size:.875rem}
  .spec-table thead th,.spec-table tbody th,.spec-table tbody td{padding:.6rem .75rem}
}
/* Screen-reader-only caption for the spec table. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

.slide-l{opacity:0;transform:translateX(-2.5rem) scale(.98);
  transition:opacity .5s var(--ease-in),transform .5s var(--ease-in)}
.slide-r{opacity:0;transform:translateX(2rem);
  transition:opacity .5s var(--ease-in) .08s,transform .5s var(--ease-in) .08s}
.slide-l.in,.slide-r.in{opacity:1;transform:none}
/* On a phone the columns are stacked, so sliding sideways has nothing to say
   and a horizontal offset can push a scrollbar. Fade upward instead. */
@media(max-width:899px){
  .slide-l,.slide-r{transform:translateY(1.25rem);transition-delay:0s}
}


/* ============================================================
   9. PROOF BAND
   ============================================================ */
/* Light rather than inverted, so it belongs to the same palette as the rest of
   the page instead of interrupting it. The mission-card at the top is a quiet
   ink/blue bridge (kicker only) so the dark close of the page is foreshadowed
   without making this the only h2 that lives inside a colored plaque. */
.proof{color:var(--ink);padding:clamp(2rem,5vh,3.5rem) 0 var(--sp-sec)}
.proof .mission-card{margin-bottom:clamp(1.25rem,3vh,2rem)}
.proof h2{color:var(--blue);max-width:24ch}
.proof .sub{color:var(--ink-muted);font-size:1.0625rem;max-width:54ch;margin-top:.875rem}
.proof-grid{display:grid;gap:0;margin-top:clamp(1.375rem,3.5vh,2.25rem)}
@media(min-width:860px){.proof-grid{grid-template-columns:1fr 1fr;gap:3rem}}
.proof-cell{padding:1.625rem 0;border-top:2px solid var(--orange)}
.proof-cell + .proof-cell{border-top:2px solid var(--orange)}
@media(min-width:860px){
  .proof-cell + .proof-cell{border-top:2px solid var(--orange);border-left:1px solid var(--line);padding-left:3rem}
}
.proof-claim{font:800 clamp(1.5rem,3.1vw,2.375rem)/1.14 "Montserrat";color:var(--orange-text);
  letter-spacing:-.02em}
.proof-claim b{font-weight:800;color:var(--blue)}
.proof-l{font-size:1.0625rem;color:var(--ink-muted);line-height:1.55;margin-top:1rem}
.proof-l b{color:var(--blue-deep);font-weight:700}
@media(max-width:700px){.proof-grid{margin-top:1.25rem}.proof-cell{padding:1.125rem 0}}


/* ============================================================
   10. FULL-BLEED MEDIA. Always on white.
   ============================================================ */
.bleed{background:var(--bg);padding:var(--sp-sec) 0}
.bleed.on-ink{background:var(--ink)}
.bleed-head{max-width:var(--container);margin:0 auto clamp(1.125rem,3vh,2.125rem);
  padding:0 var(--sp-gutter);text-align:left}
.bleed-head h2{max-width:20ch}
.bleed-media{max-width:var(--container);margin:0 auto;padding:0 var(--sp-gutter)}
.bleed-media video,.bleed-media img{width:100%;border-radius:var(--r-lg);
  max-height:74vh;object-fit:contain}
.bleed-media.plain video,.bleed-media.plain img{box-shadow:none;background:#fff}
.bleed-media.plain img{width:auto;max-width:100%;margin:0 auto}
/* Architecture diagram. Capped so the section heading, the diagram and the
   explanation underneath all sit in one screen. */
.bleed-media.fit video,.bleed-media.fit img{max-height:56vh;width:auto;margin:0;object-fit:contain}
/* Subhead under the media, sharing the same left edge as the media. */
.bleed-sub{max-width:var(--container);margin:1.5rem auto 0;padding:0 var(--sp-gutter);
  color:var(--ink-muted);font-size:1.0625rem;line-height:1.6}
.bleed-sub p{max-width:74ch}
.on-ink .bleed-sub,.on-ink .bleed-sub p{color:rgba(255,255,255,.78)}
.bleed-cta{max-width:var(--container);margin:1.75rem auto 0;padding:0 var(--sp-gutter);
  display:flex;flex-wrap:wrap;justify-content:center;gap:.75rem}


/* ============================================================
   11. TWO-UP PANELS
   ============================================================ */
.two{display:grid;gap:1.375rem}
@media(min-width:900px){.two{grid-template-columns:1fr 1fr;gap:2rem}}
.panel{background:#fff;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--line);display:flex;flex-direction:column}
.panel .vidwrap,.panel video,.panel img{width:100%;background:#fff}
.panel-body{padding:1.5rem 1.625rem 1.875rem}
.panel-body h3{margin-bottom:.625rem}
.panel-body p{color:var(--ink-muted);font-size:1rem}


/* ============================================================
   12. FEATURE ROW
   ============================================================ */
.row{display:grid;gap:1.625rem;align-items:center}
@media(min-width:900px){.row{grid-template-columns:1.05fr .95fr;gap:3.25rem}.row.flip .row-media{order:2}}
.row-media img,.row-media video{width:100%;border-radius:var(--r-lg);box-shadow:var(--sh2)}
.row-media.plain img,.row-media.plain video{box-shadow:none;background:#fff}
/* Numbered by CSS counter rather than markup, so the count stays correct in
   both languages without a second place to edit, and the left margin gets a
   small, quiet index mark instead of a plain dot. */
.ticks{list-style:none;margin:1.375rem 0 0;padding:0;display:grid;gap:.75rem;counter-reset:tick}
.ticks li{position:relative;padding-left:2rem;color:var(--ink-muted);font-size:1rem;counter-increment:tick}
.ticks li::before{content:counter(tick,decimal-leading-zero);position:absolute;left:0;top:0;
  font:700 .75rem/1.6 "IBM Plex Sans",system-ui,sans-serif;letter-spacing:.06em;color:var(--orange-text)}


/* ============================================================
   13. VIDEO PLAYBACK AFFORDANCE
   Every autoplaying clip on the page sits in a .vidwrap. When the browser
   refuses to autoplay -- iOS Low Power Mode and Android data saver both do,
   even for a muted clip -- js/main.js reveals the button so the reader can
   start it. Previously a refused autoplay left a video element that had
   metadata but no decoded frame, which paints as nothing at all, so the
   section went blank and looked broken.

   .is-armed / .is-live: js/main.js copies the poster onto the wrapper as a
   background, then hides the <video> until a playing frame is painted. That
   covers the gap where the browser has already dropped the native poster and
   is showing the blank first frame of a diagram build.
   ============================================================ */
.vidwrap{position:relative;display:block;width:100%;
  background-position:center;background-size:contain;background-repeat:no-repeat}
.vidwrap > video{display:block;width:100%}
/* Armed = poster underlay is in place. Hide the element until a painted
   playing frame flips .is-live, so metadata-driven poster-drop never shows. */
.vidwrap.is-armed > video{opacity:0;transition:opacity .2s linear}
.vidwrap.is-live > video{opacity:1}
/* The wrapper is layout-transparent: it takes the width the clip would have
   had on its own, so every sizing rule elsewhere in this file can keep
   targeting the media element rather than needing a wrapper variant. */
.bleed-media.fit .vidwrap{width:-moz-fit-content;width:fit-content;max-width:100%}
.vidplay{position:absolute;inset:auto auto 1rem 1rem;z-index:2;
  display:none;align-items:center;gap:.5rem;
  padding:.625rem 1rem .625rem .75rem;border:0;border-radius:var(--r-pill);
  background:rgba(var(--ink-rgb),.78);color:#fff;cursor:pointer;
  font:600 .875rem/1 "IBM Plex Sans",system-ui,sans-serif;
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  box-shadow:var(--sh2);-webkit-tap-highlight-color:transparent;
  transition:background .16s var(--ease-state),transform .16s var(--ease-state)}
.vidwrap.needs-play .vidplay{display:inline-flex}
.vidplay:hover{background:rgba(18,50,56,.92);transform:translateY(-2px)}
.vidplay svg{width:1rem;height:1rem;fill:currentColor;flex:none}


/* ============================================================
   14. PARTNERS AND FOUNDERS
   ============================================================ */
.insts{display:grid;gap:.875rem}
@media(min-width:640px){.insts{grid-template-columns:1fr 1fr}}
.inst{display:flex;gap:1rem;align-items:center;background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.16);border-radius:var(--r-md);padding:1.25rem}
.inst img{width:2.875rem;height:2.875rem;object-fit:contain;flex:none;
  /* Partner marks stay legible on the ink band without a solid white card. */
  padding:.35rem;border-radius:.375rem;background:#fff}
.inst .mono{width:2.875rem;height:2.875rem;border-radius:50%;flex:none;display:grid;place-items:center;
  background:rgba(255,255,255,.1);font:700 .875rem/1 "Montserrat";color:#7FD4DE}
.inst b{display:block;font:700 1rem/1.3 "Montserrat";color:#fff;margin-bottom:.25rem}
.inst span{font-size:.9375rem;color:rgba(255,255,255,.72);line-height:1.5}
.quote{margin-top:2.125rem;border-left:.25rem solid var(--orange);padding:.375rem 0 .375rem 1.375rem;
  font:600 clamp(1.125rem,1.9vw,1.5rem)/1.42 "Montserrat";color:var(--blue-deep);max-width:60ch}
.quote b{color:var(--orange-text)}

.founders{display:grid;gap:1.125rem}
@media(min-width:640px){.founders{grid-template-columns:1fr 1fr}}
.founder{background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);padding:1.75rem;
  display:flex;flex-direction:column}
.founder-top{display:flex;gap:1.125rem;align-items:center;margin-bottom:1rem}
.founder-top picture{flex:0 0 auto;display:block;width:4.75rem;height:4.75rem}
.founder-top img{width:4.75rem;height:4.75rem;border-radius:50%;object-fit:cover;flex:none;
  aspect-ratio:1/1;border:3px solid #fff;box-shadow:var(--sh2)}
.founder .role{font:600 .8125rem/1.3 "Montserrat",system-ui,sans-serif;letter-spacing:.09em;
  text-transform:uppercase;color:var(--orange-text);margin-bottom:.375rem}
@media(max-width:520px){
  .founder{padding:1.375rem}
  .founder-top{gap:.875rem}
  .founder-top picture,.founder-top img{width:3.875rem;height:3.875rem}
}
.founder h3{color:var(--ink)}
.founder p{color:var(--ink-muted);font-size:1rem}
.li{display:inline-flex;align-items:center;gap:.5rem;margin-top:1.125rem;font:600 .9375rem/1 "IBM Plex Sans";
  color:var(--blue-deep);text-decoration:none;align-self:flex-start;
  padding:.5625rem .875rem;border:1px solid var(--line);border-radius:var(--r-pill);
  box-shadow:var(--sh1);transition:all .16s var(--ease-state)}
.li:hover{border-color:var(--blue);background:rgba(20,125,132,.06);transform:translateY(-2px)}
.li svg{width:1rem;height:1rem;fill:currentColor;flex:none}


/* ============================================================
   15. DEMO
   ============================================================ */
/* This used to be a fourth dark band stacked right on top of Partners, Team
   and Closing -- four in a row with barely a breath between them, which is
   most of what read as "the page turns blue and stays blue" at the end. A
   YouTube frame is already a black rectangle; it does not need a dark band
   behind it too. Cream instead, with the frame treated like any other
   bordered media card, so the page gets one clear breath between the two
   dark bands that bookend the close. */
.demo{background:var(--bg-soft);padding:var(--sp-sec) 0}
.demo h2{max-width:22ch}
.demo p.sub{color:var(--ink-muted);font-size:1.0625rem;max-width:60ch;margin:1rem 0 2.5rem}
@media(max-width:700px){.demo p.sub{margin-bottom:1.625rem}}
.demo-frame{max-width:var(--container);margin:0 auto;padding:0 var(--sp-gutter)}
.demo-card{background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);
  padding:.75rem;box-shadow:var(--sh2)}
.demo-frame .ratio{position:relative;width:100%;aspect-ratio:16/9;border-radius:calc(var(--r-lg) - .375rem);
  overflow:hidden;background:#000}
.demo-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.demo-fallback{margin-top:1rem;font-size:.9375rem;color:var(--ink-muted)}
.demo-fallback a{color:var(--blue-deep)}


/* ============================================================
   16. FIELD GALLERY. Real footage from the farm, mixed stills and clips.
   ============================================================ */
.gal{display:grid;gap:.875rem;grid-template-columns:1fr}
/* Six full-width tiles is around four screens of scrolling on a phone for what
   is meant to read as a contact sheet. Two up from the smallest usable width. */
@media(min-width:26rem){.gal{grid-template-columns:1fr 1fr}}
@media(min-width:760px){.gal{grid-template-columns:repeat(3,1fr)}}
.gal-tile{margin:0;position:relative;border-radius:var(--r-lg);background:#000;
  isolation:isolate;cursor:default;z-index:1;box-shadow:var(--sh2);
  -webkit-tap-highlight-color:transparent;
  transition:transform .28s var(--ease-state),box-shadow .28s var(--ease-state)}
.gal-tile[data-hoverplay]{cursor:pointer}
.gal-tile .gal-poster,
.gal-tile > picture,
.gal-tile > picture img,
.gal-tile > img:not(.gal-poster){width:100%;aspect-ratio:16/10;object-fit:cover;display:block;
  border-radius:var(--r-lg)}
.gal-tile > picture{display:block;overflow:hidden;border-radius:var(--r-lg)}
.gal-tile .gal-poster{border-radius:var(--r-lg);transition:opacity .2s linear}
.gal-tile video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  border-radius:var(--r-lg);opacity:0;pointer-events:none;transition:opacity .2s linear}
.gal-tile.is-playing video{opacity:1}
.gal-tile.is-playing .gal-poster{opacity:0}
.gal-hint{position:absolute;top:.75rem;right:.75rem;z-index:3;
  font:600 .75rem/1 "IBM Plex Sans";letter-spacing:.04em;text-transform:uppercase;
  color:#fff;background:rgba(var(--ink-rgb),.55);padding:.5rem .625rem;border-radius:var(--r-pill);
  opacity:0;transition:opacity .2s linear;pointer-events:none}
.gal-tile[data-hoverplay]:hover .gal-hint,
.gal-tile[data-hoverplay]:focus-within .gal-hint{opacity:1}
.gal-tile.is-playing .gal-hint{opacity:0}
/* Scaling a tile used to overflow the grid at the edges of the viewport.
   A lift and a shadow read as "this one is live" without moving anything
   outside its own column. */
.gal-tile.is-hot{z-index:6;transform:translateY(-.375rem);box-shadow:var(--sh3)}
.gal figcaption{position:absolute;left:0;right:0;bottom:0;padding:1.625rem 1.125rem .875rem;
  color:#fff;font:500 .9375rem/1.4 "IBM Plex Sans";z-index:2;border-radius:0 0 var(--r-lg) var(--r-lg);
  background:linear-gradient(180deg,rgba(var(--scrim-rgb),0) 0%,rgba(var(--scrim-rgb),.82) 62%)}
@media(hover:none){
  .gal-hint{opacity:.85}
  .gal-tile.is-hot{transform:none}
}


/* ============================================================
   17. CLOSING AND FOOTER
   Background is a cross-fading carousel of real farm footage. The still behind
   it is not decoration: if autoplay is refused the videos never paint, and
   without a background underneath them the section rendered as a flat grey
   rectangle -- the scrim over bare white page.
   ============================================================ */
.closing{position:relative;color:#fff;overflow:hidden;padding:var(--sp-sec-lg) 0}
.closing-bg{position:absolute;inset:0;z-index:0;
  background:rgb(var(--scrim-rgb)) url(../assets/media/farm-ponds-poster.webp) center/cover no-repeat}
.closing-bg video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 1.1s linear}
.closing-bg video.on{opacity:1}
.closing::after{content:"";position:absolute;inset:0;background:rgba(var(--scrim-rgb),.72);z-index:1}
/* The wave above this section is a flat fill of --ink, so the video used to
   start with a hard, sharp edge right under it -- a clean curve butting
   straight into a busy, textured frame. This fades in from that exact same
   flat colour over a short distance, on top of the scrim, so the seam reads
   as water settling rather than a cut. Short on purpose: it's there to kill
   the seam, not to hide the video. */
.closing::before{content:"";position:absolute;top:0;left:0;right:0;z-index:2;
  height:clamp(3rem,9vw,5.5rem);pointer-events:none;
  background:linear-gradient(to bottom, var(--ink) 0%, rgba(var(--ink-rgb),0) 100%)}
/* Mirrors the top fade: the footer right below is a flat --ink block too,
   so this hides that same hard seam on the way out of the video. */
.closing-fade-bottom{position:absolute;bottom:0;left:0;right:0;z-index:2;
  height:clamp(3rem,9vw,5.5rem);pointer-events:none;
  background:linear-gradient(to top, var(--ink) 0%, rgba(var(--ink-rgb),0) 100%)}
.closing .wrap{position:relative;z-index:3}
.closing h2{color:#fff;max-width:20ch}
.closing p{color:rgba(255,255,255,.88);margin:1rem 0 1.75rem;max-width:54ch;font-size:1.0625rem}
.closing-cta{display:flex;flex-wrap:wrap;gap:.75rem}
footer{background:var(--ink);color:rgba(255,255,255,.64);padding:2.125rem 0;font-size:.9375rem}
.foot-in{max-width:var(--container);margin:0 auto;padding:0 var(--sp-gutter);
  display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between}
footer a,.foot-link{color:rgba(255,255,255,.78);text-decoration:none;background:none;border:0;
  font:inherit;padding:0;cursor:pointer}
footer a:hover,.foot-link:hover{color:#fff}
/* This one link sits inline inside a sentence rather than a nav-style list,
   so color alone isn't enough to mark it as a link (WCAG 1.4.1 / axe
   link-in-text-block) -- give it an underline instead of raising contrast. */
.foot-copy-link{text-decoration:underline;text-underline-offset:.15em}


/* ============================================================
   18. PILLARS / PROOF BRIDGE CARD
   ============================================================ */
.pillars-grid{display:grid;gap:1rem}
@media(min-width:800px){.pillars-grid{grid-template-columns:repeat(3,1fr);gap:1.5rem}}
/* Solid orange step cards on the ink band (no gradient). Titles are large/bold
   so white-on-orange clears AA large; body stays the same white treatment. */
.pillar{padding:1.875rem 1.625rem 2rem;background:var(--orange);border-radius:1.5rem}
.pillar-n{font:inherit;letter-spacing:inherit;color:inherit}
.pillar h3{color:#fff;margin:0 0 .875rem;font-size:clamp(1.25rem,2.1vw,1.625rem);font-weight:700;
  letter-spacing:-.01em}
.pillar p{color:#fff;margin:0;font-size:1.0625rem;line-height:1.55}

/* A small, contained card rather than another full-bleed band. It puts the
   ink/blue pairing that runs through Partners, Team, Demo and Closing in
   front of the reader once, early and in miniature, so the dark stretch at
   the end of the page reads as a color already introduced instead of a
   surprise. Lives inside #proof; kicker is eyebrow-scale, not an h2. */
.mission-card{background:linear-gradient(135deg,var(--ink) 0%,var(--blue-deep) 130%);
  border-radius:var(--r-lg);box-shadow:var(--sh2);
  padding:clamp(1rem,2.5vh,1.5rem) clamp(1.25rem,3.5vw,2.25rem)}
.mission-kicker{margin:0;font:700 .8125rem/1.2 "Montserrat",system-ui,sans-serif;
  letter-spacing:.08em;text-transform:uppercase;color:#fff}


/* ============================================================
   19. CONTACT MODAL
   ============================================================ */
.contact{border:0;padding:0;max-width:min(36rem,calc(100vw - 2rem));width:100%;
  background:transparent;color:var(--ink)}
.contact::backdrop{background:rgba(var(--scrim-rgb),.55);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
.contact-card{background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh3);padding:1.75rem 1.5rem 1.5rem;margin:0}
@media(min-width:600px){.contact-card{padding:2rem}}
.contact-head{display:flex;gap:1rem;align-items:flex-start;justify-content:space-between;margin-bottom:1.25rem}
.contact-head h2{margin:.25rem 0 .625rem;color:var(--blue)}
.contact-head p{margin:0;color:var(--ink-muted);font-size:.975rem;max-width:42ch}
.contact-x{width:2.5rem;height:2.5rem;border:1px solid var(--line);border-radius:50%;
  background:var(--bg-soft);color:var(--ink);font-size:1.5rem;line-height:1;cursor:pointer;flex:none}
.contact-x:hover{border-color:var(--blue);color:var(--blue)}
.contact-fields{display:grid;gap:1rem}
@media(min-width:560px){.contact-fields{grid-template-columns:1fr 1fr}.contact-full{grid-column:1 / -1}}
.contact-fields label{display:grid;gap:.375rem;font:600 .8125rem/1.3 "IBM Plex Sans",system-ui,sans-serif;color:var(--ink)}
.contact-fields input,.contact-fields textarea{width:100%;border:1px solid var(--line);border-radius:var(--r-md);
  padding:.75rem .875rem;font:400 1rem/1.4 "IBM Plex Sans",system-ui,sans-serif;color:var(--ink);
  background:var(--bg);resize:vertical}
.contact-fields input:focus,.contact-fields textarea:focus{outline:3px solid rgba(206,127,13,.35);border-color:var(--orange)}
.contact-actions{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1.25rem}
.contact-note{margin:.875rem 0 0;font-size:.8125rem;color:var(--ink-muted)}
body.contact-open{overflow:hidden}


/* ============================================================
   20. MOTION
   ============================================================ */
.rv{opacity:0;transform:translateY(.75rem);transition:opacity .4s var(--ease-in),transform .4s var(--ease-in)}
.rv.in{opacity:1;transform:none}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .rv,.slide-l,.slide-r{opacity:1;transform:none;transition:none}
  .hero-bg{transform:none!important}
  .gal-tile.is-hot{transform:none}
  /* No sticky runway and no scrubbing: the film sections become an ordinary
     heading, final frame and caption. */
  .film-track{height:auto}
  .film-stick{position:static;height:auto;padding:var(--sp-sec) var(--sp-gutter);display:block}
  .film-head{width:auto;margin-bottom:1.625rem}
  .film-hint{display:none}
  .film-stage{display:block;max-width:min(86vw,1180px);margin:0 auto;height:auto}
  .film-stage canvas,.film-stage .film-still{width:100%;height:auto;max-height:none}
  .film-cap{opacity:1;transform:none;width:auto;max-height:none;overflow:visible;margin:1.625rem auto 0}
  /* The scroll transition is motion-only. Keep both product views available
     as ordinary content when reduced motion disables its JavaScript. */
  .hw{min-height:auto}
  .hw .wide{position:static}
  .hw-visual{min-height:0;gap:1.5rem}
  .hw-unit-view,.hw-dock-view{position:static;inset:auto;opacity:1;
    transform:none;pointer-events:auto;width:100%;max-width:none}
  .hw-dock-view{max-width:40rem}
  *{animation-duration:.01ms!important;transition-duration:.01ms!important}
}
