/* ============================================================
   FOX PRECISION TOOLING — SHARED STYLESHEET
   Used by every page (index.html, eoat.html, robot-cells.html,
   bespoke-machinery.html, company.html) so the whole site stays
   visually consistent. Edit colours/fonts here and it updates
   everywhere at once.
   ============================================================ */

/* ============================================================
   COLOUR SYSTEM — 60/30/10
   60% Dominant canvas: off-white/light-gray (--ink/--ink2/--ink3 below,
   despite the "ink" name — kept so every existing rule sitewide that
   already reads var(--ink2) etc. for a card/section surface didn't need
   touching one by one) with dark charcoal text (--text/--text-dim/
   --text-dimmer). This is the :root default, so body and every ordinary
   content section/card inherits it automatically.
   30% Structure & identity: pure black/charcoal. The header, footer,
   cookie UI, hero/page-header banners, the marquee ticker, the stats and
   testimonials bands and the orange CTA strip all re-declare the SAME
   variable names back to dark values (plus light text) in the override
   block just below :root — CSS custom properties cascade, so every
   existing child rule inside those containers keeps working unmodified
   and just resolves dark instead of light.
   10% Action points: signature orange, untouched — buttons, links,
   icons, anchor points.
   ============================================================ */
:root{
  --ink:#100f0e;
  --ink2:#222222;
  --ink3:#2D2D2D;
  --orange:#FF5D1D;
  --orange-dim:rgba(255,93,29,.14);
  --orange-dark:#FF5D1D;
  --line:rgba(255,255,255,0);
  --text:#f2f1ee;
  --text-dim:#9a9a9e;
  --text-dimmer:#6c6c70;
  /* Adaptive hairline/border tones — a dark-on-light rgba(0,0,0,..) border
     reads as almost invisible once its container sits on the dark canvas,
     so components that need a visible default border (outline buttons, a
     couple of card dividers) reference these instead of a literal
     rgba(0,0,0,..); the light-scope override block below (the topbar —
     the one part of the site that stays light) flips them back for use
     inside a light container. */
  --outline-border:rgba(255,255,255,.28);
  --hairline:rgba(255,255,255,.14);
  /* Permanently dark — NOT re-scoped by the light-container override
     below. For the handful of spots where text/an icon sits on a surface
     that is always dark or always white regardless of the surrounding
     theme (icon-tile hover states, the white form fields) — using the
     ambient --ink there would flip to light-on-light the moment that
     component appears inside the light-scoped topbar. */
  --solid-dark:#181818;
  --font-display:'Archivo Black', Arial, sans-serif;
  --font-body:'Inter', Arial, sans-serif;
  --max:1280px;
  --radius:8px;
}

/* Light-container override — the site is dark/black by default (see the
   colour system note above); the topbar contact strip is the one
   exception that stays light, so it gets its own override the same way
   dark structure used to be carved out of a light canvas. Every
   descendant of .topbar that reads var(--ink)/var(--ink2)/var(--ink3)/
   var(--text)/var(--text-dim)/var(--text-dimmer)/var(--outline-border)/
   var(--hairline) picks up the light values instead of the :root dark
   ones, with no per-rule edits needed anywhere else in this file. The
   explicit `color` re-declares plain text colour too, so a child element
   with no colour rule of its own (inherited, not var-based) still
   resolves to dark text instead of silently inheriting the page's light
   default from higher up.
   Text/text-dim/text-dimmer below are off-white rather than the near-black
   they'd normally be for a "light container" — Corey's call, now that the
   container itself is solid orange rather than pale cream. */
.topbar{
  --ink:#F4F2EE;
  --ink2:#FFFFFF;
  --ink3:#ECE6D8;
  --text:#F4F2EE;
  --text-dim:#F4F2EE;
  --text-dimmer:rgba(244,242,238,.6);
  --outline-border:rgba(0,0,0,.22);
  --hairline:rgba(0,0,0,.12);
  color:var(--text);
}
/* .page-header's own dark look normally comes entirely from its
   ::after gradient overlay (see .page-header-img::after below), which
   is always at least semi-dark - this is just an explicit fallback
   background so the section is correctly dark even before/without that
   overlay. */
.page-header{background:var(--ink);}

*{box-sizing:border-box;}
html{scroll-behavior:smooth; background:var(--ink);}
body{
  margin:0;
  background:var(--ink);
  color:var(--text);
  font-family:var(--font-body);
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
/* Page-to-page transition: every page fades in on arrival, and script.js below fades the current
   page out for a beat before an internal link actually navigates away - together these stop
   clicking around the site feeling like an instant hard cut between pages. The fade-in is a plain
   CSS animation, not something script.js turns on, so it still plays even if that script fails to
   load or errors out elsewhere on the page - nobody ever gets stuck looking at a blank body.
   Deliberately short (page-fade-out's transition duration is read by script.js via
   getComputedStyle, so the two stay in lockstep automatically - see PAGE_TRANSITIONS there) so it
   reads as a natural beat, not a loading delay. */
@keyframes page-fade-in{ from{opacity:0;} to{opacity:1;} }
body{animation:page-fade-in .18s ease both;}
body.page-fade-out{animation:none; opacity:0; transition:opacity .15s ease;}
@media(prefers-reduced-motion:reduce){
  body{animation:none;}
  body.page-fade-out{transition:none;}
}
img{max-width:100%; display:block;}
a{color:inherit; text-decoration:none;}
.wrap{max-width:var(--max); margin:0 auto; padding:0 32px;}
section{scroll-margin-top:88px;}

h1,h2,h3{font-family:var(--font-display); font-weight:400; line-height:1.06; margin:0; letter-spacing:.005em;}
.accent{color:var(--orange);}
/* Every page title/section heading uses the same eyebrow + heading pattern. The emphasised
   phrase used to sit on a solid brand-orange highlighter block (a marker-pen-stroke look) —
   removed sitewide per request, so it's back to plain orange text via the base .accent rule
   above, same as the homepage already did. Site-wide because this one shared rule is what every
   page's h1/h2/h3 accent spans hook into — no per-page changes needed. */

/* Homepage: no orange text at all on headings — the emphasised phrase reads as part of the same
   solid white heading, fully unstyled. Every other page shows it as plain orange text (no
   background) via the base .accent rule above. */
body.home h1 .accent, body.home h2 .accent, body.home h3 .accent{
  color:inherit;
}
/* "Automated Success" in the hero (first slide) is the one exception to the homepage's
   no-orange-headings rule above - called out by id so it beats the body.home override. */
#heroAccentSuccess{
  color:var(--orange);
}

.eyebrow{
  display:flex; align-items:center; gap:10px;
  font-size:11.5px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  color:var(--orange); margin-bottom:14px;
}
/* The little orange rule before the label - previously dropped per-page via id in a few spots
   (breadcrumbs sitting over a photo, the 404 label) - removed sitewide by request instead, so
   content:none here covers every .eyebrow everywhere with no per-page exceptions needed. */
.eyebrow::before{content:none;}
/* 404 page: "ERROR 404" enlarged into the page's main visual anchor, bigger than the h1 below it
   (58px max) rather than the small uppercase kicker text every other .eyebrow sitewide uses. */
#notFoundEyebrow{
  font-family:var(--font-display);
  font-size:clamp(42px,7vw,76px);
  font-weight:400;
  letter-spacing:.01em;
  margin-bottom:18px;
}

.section-head{max-width:640px; margin-bottom:44px;}
.section-head h2{font-size:clamp(26px,3.6vw,40px);}
.section-head p{color:var(--text-dim); font-size:15px; margin-top:14px; max-width:520px;}

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:14px 26px; border-radius:999px; font-weight:700;
  font-size:12.5px; letter-spacing:.06em; text-transform:uppercase;
  cursor:pointer; border:1px solid transparent; box-shadow:inset 0 0 0 0 transparent;
  transition:background .15s ease, color .15s ease, border-color .15s ease, box-shadow .35s ease, transform .15s ease;
}
/* Every button on the site (all .btn-* variants inherit this base rule) grows slightly on hover
   and eases back to its normal size the moment the pointer leaves — no separate "unhover" rule
   needed, removing the hover state is what drives the transition back to scale(1). */
.btn:hover{transform:scale(1.045);}
.btn-primary{background:var(--orange); color:#fff;}
.btn-primary:hover{background:var(--orange-dark);}
.btn-outline{background:transparent; color:var(--text); border-color:var(--outline-border);}
.btn-outline:hover{border-color:var(--orange); color:var(--orange); box-shadow:inset 0 0 0 1.5px var(--orange);}
.btn-dark{background:var(--solid-dark); color:#fff;}
.btn-dark:hover{background:#000;}
.btn-sm{padding:11px 20px; font-size:11.5px;}

.btns{display:flex; align-items:center; gap:14px; flex-wrap:wrap;}
@media(max-width:640px){
  .btns{gap:12px;}
  .btn{padding:12px 22px; font-size:11.5px;}
}

.header-wrap{position:sticky; top:0; z-index:51; background:#181818;}

.topbar{
  background:var(--orange); border-bottom:1px solid var(--hairline); font-size:12.5px; color:var(--text-dim);
  display:grid; grid-template-rows:1fr; grid-template-columns:100%; overflow:hidden;
  transition:grid-template-rows .32s ease, opacity .22s ease, border-color .22s ease;
}
.topbar > .wrap{min-height:0; overflow:hidden;}
.topbar.scrolled{grid-template-rows:0fr; opacity:0; border-color:transparent; pointer-events:none;}
/* Full width, not a centered column — the site-wide 1280px .wrap default (and even the nav's
   wider 1680px cap) left this sitting as a narrow centered island with big empty margins on
   wide screens. The topbar is meant to read as a full-bleed strip: contact details hard against
   the left edge, the secondary links hard against the right edge. */
.topbar .wrap{display:flex; gap:24px; padding:8px 88px; align-items:center; justify-content:space-between; transition:padding .32s ease; max-width:none; width:100%; margin:0;}
/* The row's own top/bottom padding has to collapse alongside grid-template-rows — otherwise the
   padding alone holds the box open at ~28px even once the track itself is animated to 0fr, which
   is what caused the last bit of the old animation to look like a sudden snap instead of a
   smooth finish. */
.topbar.scrolled .wrap{padding-top:0; padding-bottom:0;}
.topbar a{color:var(--text-dim); display:inline-flex; align-items:center; gap:6px;}
.topbar a:hover{color:#fff;}
.topbar .divider{color:var(--text-dimmer);}
.topbar-contact{display:flex; align-items:center; gap:24px;}
.topbar-contact span{display:inline-flex; align-items:center; gap:6px;}
.topbar-icon{width:14px; height:14px; flex-shrink:0; stroke:currentColor; fill:none; stroke-width:2;}
/* Secondary link row on the right of the topbar (About Us / Case Studies / Support / Get a
   Quote) — the same "utility bar" pattern used by sites like Piab, kept in Fox Precision's own
   beige/ink colours. Desktop only: on tablet and phone widths there isn't room for a second row
   of links next to the contact details, so it's hidden below the nav breakpoint rather than
   wrapping onto its own line. */
.topbar-links{display:none; list-style:none; margin:0; padding:0; gap:22px; align-items:center; font-weight:600;}
.topbar-links a:hover{color:#fff;}
@media(min-width:1320px){ .topbar-links{display:flex;} }
@media(max-width:640px){
  /* The top contact/utility bar adds height and clutter that isn't needed on a phone screen —
     the phone number and email are already reachable from the footer and Enquire page, so
     the whole bar is dropped on mobile rather than trying to wrap it onto extra lines. */
  .topbar{display:none;}
}

/* ===== NAV ===== */
/* No longer position:sticky itself — it's carried by .header-wrap above, which is the single
   sticky element for the whole header complex. This is what lets the topbar collapse and the
   nav's position update in the same smooth motion (see .header-wrap comment). */
/* Was a single flat rgba fill — read as a dead, overly-uniform slab of colour with no depth to
   it. Layered a very subtle top-to-bottom gradient (a faint hint of light falling from above)
   with a fine film-grain texture (an inline SVG noise filter, blended on top at low opacity) so
   the bar reads as a real surface rather than a flat digital rectangle. Both effects are
   intentionally subtle — this should not look "textured" at a glance, just less sterile. */
header.site-header{
  background:
    linear-gradient(180deg, rgba(24,24,24,.97) 0%, rgba(24,24,24,.97) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-blend-mode:normal, overlay;
  background-repeat:no-repeat, repeat;
  backdrop-filter:blur(10px); border-bottom:3px solid var(--orange); position:relative; z-index:50;
}
header.site-header > nav.wrap{max-width:1680px; padding-top:18px; padding-bottom:18px;}
nav{display:flex; align-items:center; padding:18px 0;}
.logo{display:flex; align-items:center; margin-right:40px; padding:4px 0; flex-shrink:0;}
.logo img{height:48px; width:auto;}
/* margin:0 auto (desktop only - the <1399px block below resets this back to a plain block for
   the mobile flyout) keeps the link list's own tight gap/spacing exactly as designed, but centers
   the whole group as one block in the leftover space between the logo and the Request a Quote
   button/search bar - auto margins on both sides of a flex item split the free space evenly
   between them, so the group sits in the middle instead of packed flush against either edge.
   (Tried justify-content:space-between/flex:1 first, fanning the individual items out across the
   full row - reverted since that changed the look/spacing of the links themselves, not just where
   the group sits, which wasn't the goal.)
   Scaled up overall (bigger logo + row padding above); the link labels themselves were sized down
   from that at first, but nudged back up slightly per feedback that the row read too small. */
.nav-links{display:flex; gap:32px; list-style:none; margin:0 auto; padding:0; font-size:14px; font-weight:700; letter-spacing:.03em; text-transform:uppercase; flex-shrink:0;}
.nav-links > li{position:static;}
/* Home sits right against the EOAT dropdown at the shared 32px gap - a touch more breathing room
   between just this pair, everything after keeps the standard gap. */
.nav-links > li:first-child{margin-right:14px;}
.nav-links > li > a{display:flex; align-items:center; gap:6px; padding-bottom:6px; border-bottom:2px solid transparent; transition:border-color .15s ease, color .15s ease; color:var(--text-dim); white-space:nowrap;}
/* Was 9px - at that size the ▾ read as a stray mark rather than a dropdown indicator, easy to
   miss entirely against the low-contrast label colour. Bigger and a touch brighter than the
   label text it sits next to (rather than matching --text-dim exactly) so it registers as its
   own clickable/interactive signal rather than blending into the words.
   Still wasn't reading as a dropdown indicator even at 14px/--text - bumped again and switched
   to the brand orange so EOAT/Company visibly carry an "expands" cue at rest, not just on hover
   (matching var(--orange) used elsewhere for interactive/active accents), rather than relying on
   opacity alone against the dark header. */
.nav-links > li > a .caret{font-size:17px; color:var(--orange); opacity:.9; transition:transform .15s ease, opacity .15s ease;}
.nav-links > li > a:hover .caret{opacity:1;}

/* ===== HEADER SEARCH BAR ===== */
/* Wide desktop only (>=1680px): sits compact in the nav row itself, right after the Request a
   Quote button — part of the persistent nav bar, so it no longer needs to collapse on scroll;
   it's always there, same as the CTA button beside it.
   Below 1680px: dropped back to being its own full-width row under the nav (and keeps the old
   scroll-collapse behaviour there, in sync with the topbar), rather than squeezing into the same
   row as the nav links and CTA button — that squeeze was what forced "Request a Quote" onto two
   lines, since the flex row had less room than the nav links + button + search box needed. */
.site-search{position:relative; flex-shrink:0; width:270px; margin-left:16px;}
@media(min-width:1450px){ .site-search{width:300px;} }
.site-search input{
  width:100%; padding:10px 46px 10px 18px; border-radius:999px; border:1px solid var(--line);
  background:var(--ink3); color:var(--text); font-family:var(--font-body); font-size:13px;
}
.site-search input::placeholder{color:var(--text-dimmer);}
.site-search input:focus{outline:2px solid var(--orange); outline-offset:1px;}
.site-search button{
  position:absolute; right:5px; top:50%; transform:translateY(-50%); width:32px; height:32px;
  border:none; border-radius:50%; background:var(--orange); color:#fff; cursor:pointer;
  display:flex; align-items:center; justify-content:center; transition:background .15s ease;
}
.site-search button:hover{background:var(--orange-dark);}
.site-search svg{width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:2;}
@media(max-width:1679px){
  nav{position:relative;}
  .site-search{
    position:absolute; top:100%; left:0; right:0; width:auto; margin:0;
    background:var(--ink2); padding:8px 32px;
    max-height:80px; overflow:hidden;
    transition:max-height .28s ease, opacity .2s ease, padding .28s ease, border-color .2s ease;
  }
  @media(max-width:640px){ .site-search{padding:8px 16px;} }
  /* Scoped to the direct-child (un-relocated) position only — see the max-width:1399px block
     below, where the search bar has moved inside #navLinks and this scroll-collapse behaviour
     no longer applies (it's tucked inside the closed burger menu instead).
     The real DOM chain is nav.wrap > li.nav-search-item > form.site-search — .nav-search-item's
     display:contents removes its own box from rendering, but it does NOT remove it from the DOM,
     so a ">nav >.site-search" combinator still has to hop through it or it never matches. That
     was the bug here: this rule silently never fired, so the search bar's white background never
     collapsed on scroll at 1400-1679px, sitting open and overlapping the page content below it
     instead of tucking away like the topbar above it does.
     :not(.search-open) exception: #searchResults is a sibling of .site-search, positioned
     relative to header.site-header (not to .site-search itself), so it stays visible and open
     regardless of whether .site-search is collapsed. Without this exception, scrolling past
     COLLAPSE_AT while the results panel is open (or scrolling with it already open) hid the
     input/pill entirely — opacity:0, pointer-events:none — while the results dropdown kept
     rendering right below it, leaving results floating under a search bar the visitor could no
     longer see or type into. initSiteSearch() in script.js toggles .search-open on the header
     alongside #searchResults' own .open class so the two stay in lockstep. */
  header.site-header.scrolled:not(.search-open) > nav > .nav-search-item > .site-search{max-height:0; opacity:0; padding-top:0; padding-bottom:0; border-top-color:transparent; pointer-events:none;}
  /* positionPanel() in script.js pins #searchResults to .site-search's actual rendered bottom
     edge (rather than relying on the top:100%-of-header default, which landed both elements at
     the same y and let the panel paint straight over the still-collapsed-looking pill — see the
     .search-results comment below for the full story). That measurement has to happen with the
     .scrolled collapse transition switched off for the instant it's taken: adding .search-open
     removes the collapse rule immediately, but max-height/opacity/padding above are still
     transitioning (.28s/.2s) at that exact moment, so an untimed getBoundingClientRect() can
     catch the pill still mid-animation, close to its old collapsed (0px) height, and miscompute
     the panel's offset. Higher specificity than the plain .site-search transition rule above
     (two classes vs. one) needs no !important to win. */
  .site-search.no-transition{transition:none;}
  /* Button grown from 32px toward the ~44px comfortable tap-target minimum - it's the search
     bar's only control on every page, mobile included, so worth the couple of extra px even
     though the pill itself isn't much taller than that. */
  .site-search input{font-size:13.5px; padding:9px 50px 9px 18px;}
  .site-search button{width:36px; height:36px; right:4px;}
  .site-search svg{width:15px; height:15px;}
}

/* The search bar is wrapped in <li class="nav-search-item"> purely so it's valid to relocate
   into the <ul class="nav-links"> menu on small screens (see below) — display:contents makes
   that wrapper invisible to layout everywhere else, so at every other breakpoint .site-search
   behaves exactly as if the <li> weren't there at all (unchanged from before this wrapper
   existed). */
.nav-search-item{display:contents; list-style:none;}

/* Below the burger-menu breakpoint, the search bar no longer sits in its own always-visible
   row right under the header — which used to cover the top of the page, above the hero, on
   every single visit. relocateSiteSearch() in script.js moves the <li class="nav-search-item">
   into #navLinks itself at this width, so it only appears — as the first item, alongside the
   nav links — once the burger menu is actually opened. These rules just style it once it's
   there; the display:none below is a flash-of-unmoved-content guard (and a plain-HTML fallback
   if JS ever fails) for while it's still sitting in its original spot. */
@media(max-width:1399px){
  nav.wrap > .nav-search-item{display:none;}
  /* The gutter/background framing lives on the <li> wrapper, NOT on .site-search itself —
     the search button is positioned `right:5px` relative to .site-search's own edge, so if
     .site-search carried its own padding, the input (100% width of .site-search's padded
     content box) would end well short of .site-search's true right edge while the button stayed
     pinned near that true edge, overhanging past the input's rounded corner. Keeping
     .site-search's own padding at 0 here means the input fills it edge-to-edge, so the button
     sits neatly 5px inside the input's edge — the same relationship the wide-desktop inline
     search bar already relies on (see the unconditional .site-search rule below, which also
     carries no padding of its own). */
  #navLinks .nav-search-item{
    display:block; padding:14px 24px; background:var(--ink2); border-bottom:1px solid var(--line);
  }
  #navLinks .site-search{
    position:relative; top:auto; left:auto; right:auto; width:auto; margin:0; padding:0;
    max-height:none; overflow:visible; opacity:1; transition:none; pointer-events:auto;
  }
  #navLinks .site-search input{font-size:13.5px;}
}

/* Live search results — a full-width white panel dropping below the header (matching the
   piab.com search-suggestions pattern): Suggestions / Categories / Products / Content columns,
   a dimmed backdrop over the rest of the page, and a "Show all results" row along the bottom. */
.search-backdrop{display:none; position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:40; pointer-events:none;}
.search-backdrop.open{display:block;}
/* Off-white panel with near-black text (Corey's call, same "not quite pure" treatment as the
   nav dropdown above) instead of the dark panel this used to be. Local custom-property override
   (same pattern as .topbar's light-container override higher up this file) so every descendant
   already reading var(--text)/var(--text-dim)/var(--text-dimmer)/var(--ink2)/var(--ink3)/
   var(--hairline) - the column heads, tags, thumbnails, empty state, footer divider - picks up
   light-panel values automatically, no per-rule edits needed below. */
.search-results{
  --ink2:#EDEAE3;
  --ink3:#EAE6DE;
  --text:#17181B;
  --text-dim:#4A4A50;
  --text-dimmer:#7A7A80;
  --hairline:rgba(23,24,27,.14);
  display:none; position:absolute; top:100%; left:0; right:0; background:var(--ink2); color:var(--text);
  border-top:1px solid var(--hairline); box-shadow:0 24px 48px rgba(0,0,0,.45);
  max-height:calc(100vh - 160px); overflow-y:auto; z-index:65;
}
.search-results.open{display:block;}
.search-results-inner{max-width:var(--max); margin:0 auto; padding:32px; display:grid; grid-template-columns:1fr; gap:28px 32px;}
@media(min-width:860px){ .search-results-inner{grid-template-columns:repeat(4,1fr);} }
.search-results-empty{padding:32px; color:var(--text-dim); font-size:14px;}
.sr-col-head{font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--text-dimmer); margin-bottom:14px;}
.sr-col ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px;}
.sr-col a{display:block; padding:8px 10px; margin:0 -10px; border-radius:6px; color:var(--text); font-size:14px; font-weight:600; line-height:1.4; transition:background .12s ease, color .12s ease;}
.sr-col a:hover{background:rgba(255,93,29,.16); color:var(--orange-dark);}
.sr-col a span.sr-tag{display:block; font-size:12px; font-weight:500; color:var(--text-dimmer); margin-top:1px;}
.search-results mark{background:rgba(255,93,29,.22); color:inherit; border-radius:2px;}

/* Products column: small square thumbnail + name, matching piab.com's product-row pattern */
.sr-col.sr-products li a{display:flex; align-items:center; gap:12px;}
.sr-thumb{width:38px; height:38px; flex-shrink:0; border-radius:0; overflow:hidden; background:var(--ink3); border:1px solid var(--hairline);}
.sr-thumb img{width:100%; height:100%; object-fit:cover;}

/* Content column: editorial image tiles, matching piab.com's "Content" cards */
.sr-col.sr-content ul{gap:18px;}
.sr-col.sr-content li a{padding:0; margin:0; display:block;}
.sr-content-img{aspect-ratio:16/10; border-radius:0; overflow:hidden; margin-bottom:10px; background:var(--ink);}
.sr-content-img img{width:100%; height:100%; object-fit:cover;}
.sr-col.sr-content li a:hover .sr-content-img{outline:2px solid var(--orange);}
.sr-col.sr-content li a:hover{background:none;}

.search-results-footer{border-top:1px solid var(--hairline); padding:18px 32px; text-align:center;}
.search-results-footer a{display:inline-flex; align-items:center; gap:6px; color:var(--orange-dark); font-weight:700; font-size:14px;}
.search-results-footer svg{width:15px; height:15px; stroke:currentColor; fill:none; stroke-width:2.4;}


/* Dropdown panel — a full-width mega panel attached directly to the bottom of the header
   (no gap, matching background/blur), so it reads as one continuous surface rather than a
   separate floating card. Positioned relative to header.site-header itself (not the nav
   item), which is why .nav-links > li no longer needs position:relative — removing it lets
   the dropdown's absolute positioning escape up to the header. */
/* Off-white panel (Corey's call, after trying solid orange first) - dropped the blur along with
   the old translucent dark glass version since there's nothing left to blur through. Background
   is off-white rather than pure #fff, label/link text is near-black rather than pure #000
   (same "not quite pure" treatment as the topbar's off-white text elsewhere in this file), and
   links go orange on hover instead of white-on-orange now that the panel itself is light. */
.dropdown{
  position:absolute; top:100%; left:0; right:0; width:auto; transform:none;
  background:#EDEAE3; border-top:1px solid var(--line);
  box-shadow:0 24px 48px rgba(0,0,0,.45);
  opacity:0; visibility:hidden;
  transition:opacity .15s ease, visibility .15s;
  z-index:55;
  max-height:calc(100vh - 140px); overflow-y:auto;
}
.dropdown-inner{padding:30px 32px 34px;}
@media(min-width:1400px){
  .nav-links > li > a:hover, .nav-links > li > a.active{border-color:var(--orange); color:var(--orange);}
  /* Without this, the trigger's orange underline (above) only shows on literal :hover — the
     moment the pointer leaves the link itself to travel down into the open dropdown panel, the
     underline dropped out even though the dropdown was still open (dd-open still present). This
     keeps it lit for as long as the dropdown itself is open, matching the dropdown's own
     open/close state instead of just the link's hover state. */
  .nav-links > li.has-dropdown.dd-open > a{border-color:var(--orange); color:var(--orange);}
  .nav-links > li.has-dropdown.dd-open > a .caret{transform:rotate(180deg);}
  .nav-links > li.has-dropdown.dd-open .dropdown{opacity:1; visibility:visible;}
}
.nav-links > li > a.active{border-color:var(--orange); color:var(--orange);}
.dropdown .dd-head{
  font-size:11px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:rgba(23,24,27,.62);
  margin-bottom:16px; padding-bottom:14px; border-bottom:1px solid rgba(23,24,27,.16);
}
.dropdown ul{list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:2px 40px;}
.dropdown li{border-bottom:none;}
.dropdown a{
  display:flex; align-items:center; gap:7px; padding:9px 0; font-size:12.5px; font-weight:700;
  letter-spacing:.02em; text-transform:uppercase; color:#17181B;
}
.dropdown a:hover{color:var(--orange);}
.dropdown-icon{width:17px; height:17px; flex-shrink:0; stroke:currentColor; fill:none; stroke-width:3.2;}

/* Brought back as a proper nav-row button: Enquire was otherwise only reachable via the small
   topbar text link (>=1320px only) or buried inside the Company dropdown, both easy to miss for
   a first-time visitor looking for how to get a quote. Desktop-row only (>=1400px, the same
   breakpoint where .nav-links itself switches from the burger flyout to an inline row) - below
   that the floating .quote-tab "i" button plus the Company dropdown's own Enquire link already
   cover it without adding a second CTA into the already-tight mobile/tablet header bar.
   flex-shrink:0 stops it being squashed/wrapped when the inline search bar also joins the row
   at >=1680px. */
.nav-cta{display:none;}
@media(min-width:1400px){ .nav-cta{display:inline-flex; flex-shrink:0; white-space:nowrap;} }
/* The hamburger is the primary mobile nav trigger on every page, but with no padding its actual
   tappable box was only ~33x30px - noticeably under the ~44x44px minimum comfortable touch target,
   making it an easy mis-tap on a real phone. Padding grows the tap area without changing how big
   the "☰" glyph itself looks; the header row (nav.wrap) has 32px of right padding to spare, so the
   larger box still sits comfortably clear of the screen edge. */
.burger{display:flex; align-items:center; justify-content:center; background:none; border:none; color:#fff; font-size:24px; cursor:pointer; margin-left:auto; padding:10px;}
@media(min-width:1400px){ .burger{display:none;} .nav-links{display:flex;} }
@media(max-width:1399px){
  /* display stays flex at all times (rather than the old display:none/flex toggle) so opacity/
     transform can actually transition — display can't be animated, so the open/closed state is
     driven entirely by opacity + transform + visibility below, toggled via the same .open class
     script.js already applies on burger click. pointer-events:none while closed stops the
     off-screen-but-still-`flex` panel from intercepting taps on the page behind it. */
  .nav-links{
    display:flex; position:absolute; top:100%; left:0; right:0; margin:0; background:var(--ink2); flex-direction:column; justify-content:flex-start; gap:0; border-top:1px solid var(--line); max-height:calc(100vh - 60px); overflow-y:auto; font-size:11.5px;
    opacity:0; visibility:hidden; transform:translateY(-10px); pointer-events:none;
    transition:opacity .22s ease, transform .22s ease, visibility .22s;
  }
  .nav-links.open{opacity:1; visibility:visible; transform:translateY(0); pointer-events:auto;}
  .nav-links > li > a{display:flex; justify-content:space-between; padding:11px 24px; border-bottom:1px solid var(--line);}
  /* Visibility here is controlled solely by the .open class (toggled on tap via script.js) —
     intentionally NOT hover-based, since sticky :hover on touch devices caused the dropdown
     to mis-render (blank rows) on real phones. */
  .dropdown{
    position:static; width:auto; transform:none; box-shadow:none; border-radius:0;
    opacity:1; visibility:visible; transition:none; max-height:none; overflow-y:visible;
    background:var(--ink3); display:none;
  }
  .nav-links > li.has-dropdown.open .dropdown{display:block;}
  .dropdown-inner{padding:0;}
  .dropdown .dd-head{display:none;}
  .dropdown ul{display:block;}
  /* Tighter rows than the desktop flyout so long lists don't blow out the height of the mobile
     menu overlay - but 10.5px made these read noticeably smaller than every other item in the
     menu (HOME, EOAT, etc.), which are all 13.5px+. Every category here is 5-6 items now, so
     there's room to bring the text closer to the rest of the menu without the list running long. */
  .dropdown a{padding:9px 36px; font-size:13px; color:#e7e6e2; background:var(--ink3);}
  .dropdown a:hover{background:var(--ink3); color:var(--orange);}
  .dropdown li{border-bottom:1px solid var(--line);}
}

/* ===== HOME HERO ===== */
/* Shorter, centered "banner" hero rather than a near-full-viewport block — the video reads
   as a wide strip behind the text instead of a tall square, and the text sits level within it
   instead of anchored to the bottom edge. */
.hero{position:relative; height:64vh; min-height:440px; max-height:560px; overflow:hidden; display:flex; align-items:center;}
.hero video, .hero img.hero-fallback{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
/* Loop-swap pair: both videos sit stacked in the same spot, both already playing. JS swaps
   which one is on top with an instant opacity toggle (no transition) right as the visible one
   reaches its end — no dissolve/fade look, just a clean cut with no stutter, since the one
   swapping in is already mid-playback rather than restarting from a cold stop. */
.hero video.hero-video{opacity:0; z-index:0;}
.hero video.hero-video:first-of-type{opacity:1;}
.hero::before{content:''; position:absolute; inset:0; background:linear-gradient(to top, rgba(24, 24, 24,.88) 0%, rgba(24, 24, 24,.58) 45%, rgba(24, 24, 24,.72) 100%);}
/* Main homepage hero: video runs full-bleed edge-to-edge with square corners at every width
   (previously floated as a rounded, inset card on wide screens - removed per request so it reads
   as one continuous banner instead of a framed card). .hero-split's dark backing is harmless
   dead weight now the video always covers the full section, so it's left in place rather than
   risking a flash of bare background on a slow video load. */
/* Bigger, more cinematic banner than the original short 64vh strip — taller box, and the text
   block is vertically centered within it (align-items:stretch here + .hero-carousel's own
   flex-column/justify-content:center below) rather than sitting mid-height inside a short band. */
.hero-split{background:var(--ink); height:86vh; min-height:640px; max-height:840px; align-items:stretch;}
/* Brand-orange glow from the right edge, fading out by 38% width - layered on top of the same
   vertical dark gradient the base .hero::before rule uses (re-declared here since a `background`
   shorthand replaces rather than merges), so the centered slide text keeps its contrast. */
.hero-split::before{
  inset:0; border-radius:0;
  background:
    linear-gradient(to left, rgba(24, 24, 24,1) 0%, rgba(24, 24, 24,0) 50%),
    linear-gradient(to top, rgba(24, 24, 24,.88) 0%, rgba(24, 24, 24,.58) 45%, rgba(24, 24, 24,.72) 100%);
}
/* Below the 1399px breakpoint where the nav collapses to the hamburger menu: the section switches
   to auto height rather than the fixed vh/min/max band used above 1399px — min-height keeps the
   video from ever going postage-stamp-small on a short slide; no max-height, so a longer slide
   (e.g. Brick & Tile's 3-line heading) can still grow the box rather than clipping. The text stays
   vertically centered (align-items:stretch, same as desktop) rather than bottom-anchored, and the
   slide-controls bar (see .hero-carousel/.hero-slide-controls below) supplies its own bottom
   spacing, so no padding-bottom is needed on the section itself here. */
@media(max-width:1399px){
  .hero-split{height:auto; min-height:600px; max-height:none; overflow:hidden; display:flex; align-items:stretch;}
}
.hero-inner{position:relative; z-index:2; width:100%;}
.hero h1, .hero-slide h2{font-size:clamp(40px, 6.4vw, 78px); text-transform:uppercase; max-width:1200px; white-space:nowrap;}
@media(max-width:640px){ .hero h1, .hero-slide h2{white-space:normal;} }
.hero p.lede{max-width:560px; font-size:16px; color:#d6d5d1; margin:22px 0 0;}
.hero .btns{display:flex; gap:14px; flex-wrap:wrap; margin-top:34px;}

/* ===== HOME HERO — CAROUSEL VARIANT ===== */
/* Full-bleed video hero, same as the original single-message hero (video, dark gradient overlay,
   white heading/lede/buttons are all inherited unchanged from the base .hero rules above) — the
   only difference is the text content now cycles through multiple .hero-slide messages via
   prev/next arrows + dot indicators instead of showing one static message. The video swap-loop
   pair inside .hero-split-media is the same JS-driven mechanism as every other hero on the site
   (see script.js initHeroLoop) and keeps playing continuously underneath every slide. */
/* Square corners, no margin, no inset - covers .hero-split completely edge-to-edge at every
   width, with the slide text (.hero-inner) overlaid on top of it. */
.hero-split-media{position:absolute; inset:0; z-index:0; border-radius:0; overflow:hidden;}
/* .hero-inner (this element also carries .wrap + .hero-carousel) is stretched full-height by
   .hero-split's align-items:stretch above; turning it into a centered flex column here is what
   lets the slide text sit mid-height in the now-much-taller banner, with room reserved at the
   bottom (padding-bottom) for the .hero-slide-controls bar, which is pinned there via its own
   position:absolute rather than sitting in normal flow under the text. */
.hero-carousel{display:flex; flex-direction:column; justify-content:center; min-height:100%; padding-top:32px; padding-bottom:104px;}
.hero-slide{display:none;}
.hero-slide.is-active{display:block;}
/* Entrance animation for the slide's own content - kicks in both on first page load (the initial
   slide already carries .is-active in the markup) and every time the carousel switches to a new
   slide (JS just toggles the .is-active class - display:none swapping back to a value the
   animation applies to is what re-triggers a CSS animation, no extra JS needed). Each piece
   (kicker, heading, lede, buttons) fades and slides up on its own staggered delay so the slide
   reads as building rather than popping in all at once. Purely a CSS-animation entrance, not a
   cross-fade between outgoing/incoming slides - the outgoing slide still just disappears via the
   display:none switch above, same as before. */
.hero-slide.is-active > *{animation:hero-content-in .7s cubic-bezier(.16,1,.3,1) both;}
.hero-slide.is-active .f-num{animation-delay:.02s;}
.hero-slide.is-active h1, .hero-slide.is-active h2{animation-delay:.1s;}
.hero-slide.is-active p.lede{animation-delay:.22s;}
.hero-slide.is-active .btns{animation-delay:.34s;}
@keyframes hero-content-in{
  from{opacity:0; transform:translateY(26px);}
  to{opacity:1; transform:translateY(0);}
}
@media(prefers-reduced-motion:reduce){
  .hero-slide.is-active > *{animation:none;}
}
/* Base .hero h1 is white-space:nowrap at a large clamp(40px,6.4vw,78px) size, tuned for the
   original single short message — with multiple slides of varying length, a longer phrase (e.g.
   the Brick & Tile slide) needs to both wrap onto its own line instead of overflowing past the
   viewport edge, and run a size smaller so 3 wrapped lines still fit inside the hero's fixed
   height without clipping at the top. The highlighted .accent span already handles wrapping
   cleanly (box-decoration-break:clone draws a separate highlight block per line), same as every
   other heading on the site — same treatment already used at this smaller size on .hero-mini.
   Only the first (default-active) slide uses an actual <h1> - SEO best practice is one <h1> per
   page, so the 2nd/3rd slide headings are marked up as <h2> instead even though only one slide is
   ever visible at a time. Selectors below cover both tags so all three slides still look
   identical. */
.hero-slide h1, .hero-slide h2{white-space:normal; font-size:clamp(30px, 5vw, 58px);}

/* Moved off the bottom of the text block and pinned to the bottom edge of the (now much taller)
   banner instead — prev arrow at the bottom-left corner, next arrow at bottom-right, dots
   centered between them. With exactly 3 flex children and justify-content:space-between, the
   two equal-width (36px) arrows land symmetrically at the edges, which centers the dots group
   in the middle for free — no extra positioning math needed. */
.hero-slide-controls{
  position:absolute; left:0; right:0; bottom:32px; z-index:3;
  display:flex; align-items:center; justify-content:space-between;
  padding:0 32px; margin-top:0;
}
.hero-arrow{width:36px; height:36px; border-radius:50%; border:1px solid rgba(255,255,255,.35); background:transparent; color:#fff; display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; flex-shrink:0; transition:background .15s ease, color .15s ease, border-color .15s ease;}
.hero-arrow:hover{background:var(--orange); color:#fff; border-color:var(--orange);}
.hero-arrow svg{width:15px; height:15px; stroke:currentColor; fill:none; stroke-width:2.2;}
.hero-dots{display:flex; align-items:center; gap:10px;}
.hero-dot{width:9px; height:9px; border-radius:50%; border:1.5px solid rgba(255,255,255,.5); background:transparent; padding:0; cursor:pointer; transition:background .15s ease, border-color .15s ease, transform .15s ease;}
.hero-dot.is-active{background:var(--orange); border-color:var(--orange); transform:scale(1.15);}
@media(max-width:640px){
  .hero-slide-controls{padding:0 20px; bottom:20px;}
}

/* Smaller cinematic video-backed banner — same swap-loop pattern as the main hero, reused for
   the standalone Brick & Tile specialism section on the home page. Same floating-rounded-card
   treatment as the main .hero-split video above: dark backing shows through as a frame, and the
   darkening gradient is inset + rounded to match. The video pair is wrapped in .hero-mini-media
   (same pattern as .hero-split-media) rather than inset directly — a <video> is a replaced
   element, so its inherited width:100%/height:100% (from the base ".hero video" rule) conflicts
   with also setting inset directly on it, pushing the box off to one side. Insetting the
   non-replaced wrapper div instead avoids that entirely. */
/* Height intentionally NOT overridden here — .hero-mini keeps the exact same
   height:64vh; min-height:440px; max-height:560px as the main homepage hero (.hero-split),
   so the two video banners are precisely the same size rather than the Brick & Tile one
   running shorter. It only needed its own background here; sizing comes from the shared
   .hero base rule via the "hero hero-mini" classes on the section itself. */
.hero-mini{background:var(--ink);}
.hero-mini-media{position:absolute; top:16px; right:32px; bottom:16px; left:32px; z-index:0; border-radius:0; overflow:hidden;}
.hero-mini::before{top:16px; right:32px; bottom:16px; left:32px; border-radius:24px;}
@media(min-width:1000px){
  .hero-mini-media{right:48px; left:48px;}
  .hero-mini::before{right:48px; left:48px;}
}
@media(max-width:640px){
  /* Same fix as .hero-split above: this video is a 16:9 shot too, so overlaying text on a tall
     narrow card was cropping most of every frame away instead of just resizing to fit the text.
     The video becomes its own true-proportioned 16:9 band and the eyebrow/heading/paragraph/button
     flow underneath it in normal document flow instead of overlaid on top. */
  .hero-mini{height:auto; min-height:0; max-height:none; display:block; overflow:visible; padding-bottom:36px;}
  .hero-mini::before{display:none;}
  .hero-mini-media{
    position:relative; inset:auto; top:auto; right:auto; bottom:auto; left:auto;
    width:auto; height:auto; aspect-ratio:16/9; margin:16px 32px 24px; border-radius:0;
  }
}
/* Brick & Tile split variant overrides the shared .hero-mini-media rules above — no inset
   margin and no rounding, so the photo runs flush to the section's own edges (square corners)
   instead of floating as a rounded inset card, matching the flush two-panel reference layout. */
.hero-mini-split .hero-mini-media{top:0; right:0; bottom:0; left:0; border-radius:0;}
@media(min-width:1000px){ .hero-mini-split .hero-mini-media{right:0; left:0;} }
@media(max-width:640px){ .hero-mini-split .hero-mini-media{border-radius:0;} }
.hero-mini .hero-inner{text-align:center; max-width:760px; margin:0 auto;}
.hero-mini .f-num{color:var(--orange); font-weight:700; font-size:13px; letter-spacing:.06em; text-transform:uppercase; margin-bottom:14px;}
.hero-mini h2{font-size:clamp(28px, 4vw, 44px); max-width:none;}
.hero-mini p.lede{max-width:600px; margin:18px auto 0;}
.hero-mini .btns{justify-content:center; margin-top:28px;}

/* ===== HOME HERO — MINI SPLIT-PANEL VARIANT (Brick & Tile) =====
   Swaps the video + full-width dark-vignette treatment every other .hero-mini uses for a still
   photo plus a solid, slightly-transparent orange panel laid over roughly the left half —
   matching a reference layout (a photo on one side, a solid colour panel with the copy on the
   other). The photo still spans the full card edge-to-edge underneath the panel (rather than two
   genuinely separate boxes) so "transparent" reads literally - a hint of the photo shows through
   the orange rather than the panel being a flat opaque block. */
.hero-mini-media img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
/* The base .hero::before dark vignette is designed for text sitting directly on top of the full
   photo - not needed here since the copy now sits on its own solid panel instead, and keeping it
   would muddy the photo on the exposed (right) side for no reason. */
.hero-mini-split::before{content:none;}
/* .hero-mini-split doesn't try to match the main hero's 64vh/440-560px (the "keep them identical"
   note above the shared .hero-mini rule applies when there are other video-backed .hero-mini
   banners to stay consistent with - there aren't). object-fit:cover on a photo this wide relative
   to the banner's height crops most of its top and bottom away to fill the short, wide box; a
   taller box needs less of that crop to still fill edge-to-edge, so more of the photo (more of
   the tooling rig, more headroom around any text baked into the shot) reads as visible instead of
   being cropped off. Taken further here than the original pass - these source photos are closer
   to square (~4:3) than the section's own wide banner shape, so even at the previous 660px cap a
   lot of top/bottom was still being cut away and reading as an overly tight, "zoomed in" crop;
   this taller cap roughly halves how much gets cropped at typical desktop widths. Scoped to
   min-width:641px so it doesn't fight the mobile breakpoint just below (max-width:640px), which
   switches this section to height:auto with the photo as its own true-proportioned 16:9 band
   instead - without this guard, .hero-mini-split's equal-specificity height would win there too
   and force the section to a tall fixed height with a big empty gap under the now-much-shorter
   stacked content. */
@media(min-width:641px){ .hero-mini-split{height:85vh; min-height:560px; max-height:820px;} }
.hero-mini-panel{
  position:absolute; z-index:1; top:0; bottom:0; left:0;
  width:50%;
  background:rgba(255,93,29,.88);
  border-radius:0;
  display:flex; align-items:center;
  overflow:hidden;
}
/* Flip variant: same split-panel treatment, mirrored - the orange panel sits over the right half
   instead of the left (photo shows through on the left instead), so a second section using this
   pattern further down the page can alternate sides rather than repeating the same layout. Only
   the panel's own side changes; the copy inside it stays left-aligned within its own half either
   way, same as .feature-row/.eng-grid's .flip only swapping which side each half sits on. */
.hero-mini-split.flip .hero-mini-panel{left:auto; right:0;}
/* Both panels are translucent orange with the photo showing through behind the copy (Corey asked
   for this back after an earlier pass made both solid/opaque). Kept at 88% so white text over the
   busier Robot Cells photo (workshop floor with cabling, a control cabinet and a robot arm) still
   reads cleanly. */
/* Adjacent stacking gap: the only two .hero-mini-split sections on the site (Brick & Tile, then
   Robot Cells Specialism) sit back to back with no other content between them, so their full-bleed
   photo/panel edges touched directly with zero breathing room. Scoped to the adjacent-sibling case
   only, so a single .hero-mini-split elsewhere on the site (or one separated by other sections)
   isn't affected. */
.hero-mini-split + .hero-mini-split{margin-top:56px;}
/* Same reasoning, other end of the stack: the second (Robot Cells/Turnkey Integration)
   hero-mini-split section's full-bleed orange panel touches whatever section follows it directly,
   with zero gap. Originally that was .clients ("Trusted By"), but News/Case Studies now sits there
   after the section reorder, so the rule targets .ncs-section. Scoped to .hero-mini-split.flip so a
   lone, non-stacked hero-mini-split elsewhere on the site isn't affected. */
.hero-mini-split.flip + .ncs-section{margin-top:56px;}
.hero-mini-split .hero-inner{
  text-align:left; max-width:none; margin:0; padding:0 40px;
}
@media(min-width:640px){ .hero-mini-split .hero-inner{padding:0 52px;} }
.hero-mini-split .f-num{color:#fff;}
.hero-mini-split h2{color:#fff;}
/* The shared .hero-mini p.lede rule below centres the paragraph itself via margin:auto (not just
   text-align, which .hero-mini-split .hero-inner above already sets to left) - that auto margin
   still centres the lede's own box inside the panel regardless of text-align, leaving it indented
   relative to the flush-left heading/button above and below it. Zero it out here so the paragraph
   sits flush left with the rest of the panel's content, matching the kicker/heading/button. */
.hero-mini-split p.lede{color:rgba(255,255,255,.92); margin-left:0; margin-right:0;}
.hero-mini-split .btns{justify-content:flex-start;}
.hero-mini-split .btn-primary{background:var(--solid-dark); color:#fff;}
.hero-mini-split .btn-primary:hover{background:#000;}
@media(max-width:640px){
  /* Same stacked fallback as the plain .hero-mini variant above: the panel becomes a plain block
     of copy sitting under the (now full-width, un-covered) photo instead of an overlaid box -
     there isn't room to keep two side-by-side halves legible at phone width. */
  .hero-mini-panel{
    position:static; width:auto; top:auto; bottom:auto; left:auto;
    background:none; border-radius:0; padding:0 32px;
  }
  .hero-mini-split .hero-inner{padding:0;}
  .hero-mini-split .f-num, .hero-mini-split h2, .hero-mini-split p.lede{color:var(--text);}
  .hero-mini-split .btn-primary{background:var(--orange); color:#fff;}
  .hero-mini-split .btn-primary:hover{background:var(--orange-dark);}
}

/* ===== INTERIOR PAGE HEADER (banner for eoat/robot-cells/machinery/company) ===== */
.page-header{position:relative; padding:88px 0 64px; border-bottom:1px solid var(--line); overflow:hidden;}
.page-header .wrap{position:relative; z-index:2;}
.page-header h1{font-size:clamp(34px,5vw,58px); text-transform:uppercase; max-width:760px;}
/* Publish date on individual news articles — sits between the headline and the lede, matching
   the month/year already shown on the news listing cards. Plain and small (not the orange
   .eyebrow treatment used for the breadcrumb above the headline) so it reads as a byline detail,
   not another heading. */
.page-header .article-date{font-size:13px; color:var(--text-dim); margin-top:16px;}
.page-header p.lede{max-width:560px; font-size:16px; color:var(--text-dim); margin-top:20px;}
.page-header-img{position:absolute; inset:0; z-index:1;}
.page-header-img img{width:100%; height:100%; object-fit:cover; opacity:.16;}
/* Video backgrounds read much darker than a still photo at the same opacity - motion and
   compression both crush perceived brightness - so they get their own, higher opacity rather
   than sharing the plain .page-header-img img value tuned for photos. */
.page-header-img video{width:100%; height:100%; object-fit:cover; opacity:.9;}
.page-header-img::after{content:''; position:absolute; inset:0; background:linear-gradient(to right, var(--ink) 20%, rgba(24, 24, 24,.5) 75%);}
/* Solid black from the left edge, fading out across to the middle of the banner. */
.page-header-img:has(video)::after{
  background:linear-gradient(to right, rgba(24, 24, 24,1) 0%, rgba(24, 24, 24,0) 85%);
}
/* BESPOKE MACHINERY — the header render was reading too dark against the sitewide .16 opacity +
   gradient combo tuned for photos. Rather than brighten the image file itself (which would fight
   its own moody, deliberately dark lighting), let more of it show through as-is: a higher image
   opacity plus a lighter gradient tail, scoped to this page only so every other header keeps the
   standard treatment. */
#page-bespoke-machinery .page-header-img img{opacity:.32;}
#page-bespoke-machinery .page-header-img::after{background:linear-gradient(to right, var(--ink) 20%, rgba(24, 24, 24,.22) 75%);}
.page-header .btns{display:flex; gap:14px; flex-wrap:wrap; margin-top:30px;}
@media(max-width:640px){
  /* Interior pages (Brick & Tile, industries, EOAT types, etc.) all share this banner —
     the desktop padding left the page feeling oversized before any real content on mobile. */
  .page-header{padding:52px 0 36px;}
  .page-header p.lede{margin-top:14px;}
}

/* ===== MARQUEE ===== */
.marquee{background:var(--ink2); overflow:hidden; padding:14px 0;}
.marquee-track{display:flex; width:max-content; animation:marquee 34s linear infinite; gap:0;}
.marquee-track span{font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--text-dimmer); padding:0 22px; white-space:nowrap;}
.marquee-track span.sep{color:var(--orange);}
@keyframes marquee{ from{transform:translateX(0);} to{transform:translateX(-50%);} }

/* ===== BUILD (feature rows) ===== */
.build{padding:68px 0 32px;}
/* Homepage "What We Build" grid now sits directly under the tabbed EOAT-by-platform picker
   (.icon-strip) instead of under the hero - top padding stays trimmed so it sits close to the
   section above rather than the default .build top padding (tuned for interior pages that sit
   under a page-header, not another full-bleed homepage section). Bottom padding is the shared
   .build default, which left its "Explore ->" links touching the full-bleed Brick & Tile
   hero-mini-split section directly below with no breathing room - the sibling rule further down
   (.build + .hero-mini-split) adds that gap back without changing .build's own padding, so
   interior pages using .build are unaffected. */
#build{padding-top:28px;}
/* Used on interior pages directly under .page-header, which already carries its own bottom
   padding — this trims the stacked whitespace down on mobile so the page doesn't feel oversized
   before any real content shows up. */
.build-after-header{padding-top:80px;}
@media(max-width:640px){ .build-after-header{padding-top:40px;} }
/* The homepage "What We Build" section (#build) sat inside the same 1280px wrap as every other
   section, which read as small/centered under the full-bleed hero above it — run it out to the
   page edges like the icon strip below it. Scoped to #build only, so the single-feature-row
   .build-after-header sections on interior pages are unaffected. */
@media(min-width:1000px){ .build#build > .wrap{max-width:none; padding:0 48px;} }
/* Gap between the "What We Build" cards and the full-bleed Brick & Tile hero-mini-split section
   that follows it on the homepage - matches the 56px gap already used between the two stacked
   hero-mini-split sections below, so the whole run of homepage sections breathes consistently.
   Scoped to the adjacent-sibling case so .build's own padding (and its use on interior pages) is
   untouched. */
.build + .hero-mini-split{margin-top:56px;}
.feature-row{display:grid; grid-template-columns:1fr; gap:36px; align-items:center; padding:44px 0; border-bottom:1px solid var(--line);}
.feature-row:first-of-type{border-top:1px solid var(--line);}
/* Grid items default to min-width:auto, so they refuse to shrink below their content's
   intrinsic min-content size. Kept sitewide as a safety net for any wide intrinsic content
   (a table, a long unbroken string, an embed) that might otherwise force the column - and the
   page - wider than the viewport on phones. */
.f-body{min-width:0;}
@media(min-width:860px){ .feature-row{grid-template-columns:1fr 1fr; gap:64px;} .feature-row.flip .f-img{order:2;} .feature-row.flip .f-body{order:1;} }
/* Dual Parallel Gripper case study: the real workshop photos read a little large sitting at a
   full 50/50 split next to the copy - narrow the image column a bit and let the text take the
   extra width, on this page only. */
@media(min-width:860px){ #page-dpg-case-study .feature-row{grid-template-columns:0.8fr 1.2fr;} }
/* Robot Cells page, "Designed and built in the UK" row: align-items:center above vertically
   centers the shorter column against the taller one, which reads fine when the two are close in
   height - but this row's copy is just a heading and one short paragraph next to a full 4/3
   photo, so centering left a large, empty-looking gap above the heading. Top-align this row only
   so the heading starts level with the photo instead. */
#page-robot-cells .feature-row.flip{align-items:flex-start;}
/* This photo was cropped down to a letterbox strip (see the source .webp) to keep the messy
   cable runs above and below the branded EOAT box out of frame. Left at the standard 4/3
   .f-img ratio, object-fit:cover would zoom in and crop the sides too, cutting into the FOX
   logo - so this one slot gets its own wider aspect-ratio matching the crop instead. */
#page-robot-cells .f-img:has(img[src="assets/robot-cell-eoat-branding.webp"]){aspect-ratio:1152/360;}
.f-img{border-radius:0; overflow:hidden; background:var(--ink2); aspect-ratio:4/3;}
/* color:transparent + font-size:0 hide a missing image's broken-icon-and-alt-text glyph so a
   404'd photo degrades to the plain --ink2 box instead (see .news-img / .cc-img for the same
   fix, prompted by a real broken image on the live site). */
.f-img img{width:100%; height:100%; object-fit:cover; color:transparent; font-size:0;}
/* Same fill treatment as .f-img img, for feature rows carrying a background video instead of a
   still photo (e.g. brick-tile.html's "Dedicated Specialism" card) - see .page-header-img video
   for the equivalent pattern on page-header banners. */
.f-img video{width:100%; height:100%; object-fit:cover;}
.f-num{font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--orange); margin-bottom:14px;}
.f-body h3{font-size:clamp(24px,3vw,34px); text-transform:uppercase; margin-bottom:16px;}
.f-body p{color:var(--text-dim); font-size:15px; max-width:460px; margin-bottom:22px;}
.feature-row .f-body{display:flex; flex-direction:column;}
/* The small orange kicker label (.f-num) that used to sit directly above every .feature-row
   heading - a caps restatement of the h3 right underneath it - has been removed from every page
   (it just repeated the heading rather than adding a real category label, and read as a random
   floating line of text). .f-num itself is untouched and still used elsewhere (related-card,
   rs-panel, build-card, footer nav cards, the homepage hero) where it's a genuine short label
   rather than a restated headline. */
/* Reusable flip for the image/copy engineering blocks (.eng-grid), same idea as .feature-row.flip
   above but targeting whichever two children the block actually has (.eng-drawing/.eng-copy on
   most pages, .f-img/a plain copy div on the "robots" section variant) rather than fixed class
   names, so one rule covers every .eng-grid layout on the site. Desktop-only (matches the
   min-width the grid itself goes 2-column at) so mobile always shows image-then-copy regardless. */
@media(min-width:900px){ .eng-grid.flip > :first-child{order:2;} .eng-grid.flip > :last-child{order:1;} }

/* Homepage "What We Build" — side-by-side card grid instead of stacked alternating rows,
   so the four disciplines read as one connected set rather than a long scroll. Scoped to
   .build-grid only, so .feature-row on interior pages (aerospace.html etc.) is untouched. */
/* Gantry/Cartesian page's build-grid only has 3 cards, so a straight grid's 2-column tablet tier
   (and 4-column desktop tier) both leave the last card packed to the left with an empty gap -
   same orphaned-row issue fixed for the icon-strip rows elsewhere. Flex-wrap centres a leftover
   row regardless of item count. */
.build-grid{display:flex; flex-wrap:wrap; justify-content:center; gap:36px; padding:44px 0 8px; border-top:1px solid var(--line);}
.build-grid .build-card{flex:0 1 100%;}
@media(min-width:640px){ .build-grid{gap:32px 28px;} .build-grid .build-card{flex:0 1 calc((100% - 28px)/2);} }
@media(min-width:1100px){ .build-grid{gap:26px;} .build-grid .build-card{flex:0 1 calc((100% - 78px)/4);} }
.build-card{display:flex; flex-direction:column;}
.build-card .f-img{aspect-ratio:16/9; margin-bottom:20px;}
.build-card .f-num{font-size:11px; margin-bottom:10px;}
.build-card h3{font-size:clamp(17px,1.7vw,21px); text-transform:uppercase; margin-bottom:10px; line-height:1.18;}
.build-card p{color:var(--text-dim); font-size:13.5px; line-height:1.65; margin-bottom:20px; flex-grow:1; max-width:none;}
.build-card .btn{align-self:flex-start;}
/* Three-card variant (delta-robots.html): the base .build-grid's 4-column desktop track left a
   fourth phantom column's worth of empty space to the right of the last card instead of the row
   reading as centered. A real 3-column track (rather than 4 columns holding 3 items) plus a
   capped, auto-margined width keeps the row centered under the heading above it, matching the
   section-head.centered treatment, without touching the shared 4-card .build-grid elsewhere. */
@media(min-width:1100px){ .build-grid.build-grid-3{grid-template-columns:repeat(3,1fr); max-width:980px; margin-left:auto; margin-right:auto;} }

/* ===== CATALOG CARD (Standard Range) ===== */
/* Product-grid card pattern (image + "Configure" badge overlay, tags, "View" link) modelled on
   piab.com's product catalogue, adapted to Fox Precision's dark card surfaces instead of white. */
.catalog-grid{display:grid; grid-template-columns:1fr; gap:40px; padding:8px 0 8px;}
@media(min-width:640px){ .catalog-grid{grid-template-columns:1fr 1fr;} }
@media(min-width:1100px){ .catalog-grid{grid-template-columns:repeat(3,1fr);} }
.catalog-card{display:flex; flex-direction:column; background:var(--ink2); border:1px solid var(--line); border-radius:0; overflow:hidden; transition:border-color .15s ease, transform .15s ease;}
.catalog-card:hover{border-color:var(--orange); transform:translateY(-3px);}
/* overflow:hidden + a transparent/zeroed alt-text fallback (matching .f-img and .news-img
   elsewhere) so a missing image degrades to the plain --ink3 box instead of the broken-image
   glyph and alt text spilling out of the aspect-ratio box and overlapping the .cc-badge pill
   sitting on top of it. */
.cc-img{position:relative; aspect-ratio:4/3; background:var(--ink3); overflow:hidden;}
.cc-img img{width:100%; height:100%; object-fit:cover; color:transparent; font-size:0;}
/* No backdrop-filter here (was blur(4px)) — a blurred badge sitting on top of a scrolling/
   zooming photo forces the browser to recomposite that blur on every scroll and hover frame,
   which is what was reading as scroll/hover lag on these cards. A slightly more opaque flat
   background keeps the badge just as legible over a busy photo without the recompositing cost. */
/* Badge's own pill background is hardcoded dark regardless of the surrounding section's theme
   (it sits directly on top of a photo), so its text is hardcoded light to match rather than
   using var(--text) — that would go dark-on-dark the moment this card sits in a light-canvas
   section, which is most of them now. */
.cc-badge{position:absolute; top:14px; left:14px; display:inline-flex; align-items:center; gap:6px; background:rgba(24, 24, 24,.94); border:1px solid var(--line); border-radius:999px; padding:7px 14px 7px 11px; font-size:11.5px; font-weight:700; letter-spacing:.02em; color:#f2f1ee; transition:background .15s ease, color .15s ease, border-color .15s ease;}
.cc-badge:hover{background:var(--orange); color:#fff; border-color:var(--orange);}
.cc-badge svg{width:13px; height:13px; stroke:currentColor; fill:none; stroke-width:2;}
.cc-body{padding:22px 22px 24px; display:flex; flex-direction:column; flex-grow:1;}
.cc-body h3{font-size:18px; text-transform:none; margin-bottom:8px; line-height:1.25;}
.cc-meta{font-size:12px; color:var(--text-dim); margin-bottom:12px;}
.cc-desc{font-size:13.5px; color:var(--text-dim); line-height:1.6; margin-bottom:18px; flex-grow:1;}
.cc-tags{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:20px;}
.cc-view{display:inline-flex; align-items:center; gap:6px; font-size:13px; font-weight:700; letter-spacing:.02em; color:var(--orange); align-self:flex-start;}
.cc-view svg{width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2.4; transition:transform .15s ease;}
.catalog-card:hover .cc-view svg{transform:translateX(3px);}

/* ===== EOAT BY ROBOT PLATFORM (icon strip) ===== */
.icon-strip{padding:72px 0 48px;}
/* Homepage only: this strip now leads directly under the full-bleed hero (moved above "What We
   Build" per request). Top/bottom padding both bumped up from the tight values this had right
   after the reorder - the tabs were sitting almost flush under the hero's own carousel controls,
   and the section ran straight into the "What We Build" cards below with barely any breathing
   room. More padding on both sides gives each section room to read as its own block instead of
   crowding into its neighbours. Scoped to the adjacent-sibling case so .icon-strip's padding
   everywhere else — eoat.html, robot-cells.html, etc. — is untouched. */
/* Top padding measured directly off the reference screenshot (hero bottom to tab row) and
   converted to CSS px - a noticeably more generous gap than this had before. */
.hero-split + .icon-strip{padding-top:130px; padding-bottom:88px;}
/* No heading in this section, so let the icon row itself use the full page width rather than
   sitting inside the standard 1280px wrap — the row runs out toward the browser edges while
   each icon stays a fixed, condensed size (capped column width below) instead of stretching. */
@media(min-width:1000px){ .icon-strip > .wrap{max-width:none; padding:0 48px;} }
/* Homepage's tabbed Robot Solutions panel (.rs-carousel) is the ONLY thing inside .icon-strip
   here - unlike the plain full-bleed icon grid the rule above is written for (eoat.html,
   robot-cells.html, etc.), so it doesn't need to run edge-to-edge. The reference layout this
   panel is matching keeps its tabs+photo+copy block centred with generous side margins instead
   of flush against the browser edge - reusing the same "wide" 1560px measure as .rs-panel below
   (and the footer/related-pages elsewhere) and centering it (margin:auto, inherited from the
   base .wrap rule) gives the whole block room to breathe on wide screens instead of hugging the
   left edge, matching the reference's proportions much more closely. */
@media(min-width:1000px){ .hero-split + .icon-strip > .wrap{max-width:1560px; padding:0 32px;} }
.icon-strip-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:44px 20px; text-align:center;}
@media(min-width:640px){ .icon-strip-grid{grid-template-columns:repeat(3,1fr);} }
@media(min-width:1000px){ .icon-strip-grid{grid-template-columns:repeat(6,minmax(0,170px)); justify-content:space-between; gap:24px;} }
/* Homepage robot-platform tiles: grouped together in the middle of the row instead of spread
   space-between edge to edge across the full page width — opt-in via an extra class so the
   plain edge-to-edge .icon-strip-grid used elsewhere (and the bordered .icon-tile variant) keep
   their existing spacing. EOAT page's "Robot Solutions" row (5 platforms, Custom & Multi-Robot
   removed) is the only user of this exact combo, so the column count is set to match it exactly -
   6 columns for 5 real tiles was leaving a visibly empty 6th slot pulling the row off-centre. */
@media(min-width:1000px){ .icon-strip-grid.icon-strip-grid-tight{grid-template-columns:repeat(5,minmax(0,170px)); justify-content:center; gap:40px;} }
/* EOAT page's "Robot Solutions" row is 5 items, same as the desktop fix above, but the base
   .icon-strip-grid's mobile (2-col) and tablet (3-col) rules above don't divide evenly into 5
   either - same orphaned-row problem, just at a narrower width. Scoped with :not(.icon-strip-grid-3x2)
   so this only touches this eoat-only combo and leaves the homepage's -3x2 variant (fixed
   separately, its own flex setup) untouched. Flex-wrap auto-centres a leftover row regardless of
   item count, same approach as -3x2. */
.icon-strip-grid.icon-strip-grid-tight:not(.icon-strip-grid-3x2){display:flex; flex-wrap:wrap; justify-content:center; gap:44px 20px;}
.icon-strip-grid.icon-strip-grid-tight:not(.icon-strip-grid-3x2) .icon-strip-item{flex:1 1 40%; max-width:220px;}
@media(min-width:640px){
  .icon-strip-grid.icon-strip-grid-tight:not(.icon-strip-grid-3x2) .icon-strip-item{flex:1 1 28%;}
}
@media(min-width:1000px){
  .icon-strip-grid.icon-strip-grid-tight:not(.icon-strip-grid-3x2){gap:40px;}
  .icon-strip-grid.icon-strip-grid-tight:not(.icon-strip-grid-3x2) .icon-strip-item{flex:0 1 170px; max-width:170px;}
}
/* Homepage: EOAT platforms and Industries side-by-side, each its own 3x2 icon grid rather than
   two separate full-width rows of 6 — stays 3 columns even at desktop width, since each half
   only has half the page to work with. */
/* One shared area split by a single divider rather than two separate bordered boxes — the two
   halves rarely end up the same height (industries has a "view all" link the platforms side
   doesn't), which read as visibly mismatched box heights side by side. A single line down the
   middle doesn't have that problem since there's no box outline to look uneven. */
.icon-strip-split-grid{display:grid; grid-template-columns:1fr; gap:0;}
.icon-strip-col{padding:36px 0;}
.icon-strip-split-grid > .icon-strip-col:not(:first-child){border-top:1px solid var(--hairline);}
@media(min-width:1000px){
  .icon-strip-split-grid{grid-template-columns:1fr 1fr; gap:0; align-items:start;}
  .icon-strip-split-grid > .icon-strip-col{padding:8px 48px;}
  .icon-strip-split-grid > .icon-strip-col:not(:first-child){
    border-top:none; border-left:1px solid var(--hairline);
  }
}
/* Homepage "Robot Solutions" tiles only: wraps at roughly 3 across on phone/tablet, back to a
   single centred row from 1000px (laptop) up — matching the plain 6-across strip used elsewhere,
   just with the boxed orange-tile treatment. Every tile is sized to be a true square at both step
   counts (equal width AND height), not just an equal-width rectangle. Flexbox (not grid) here
   specifically because the tile count (5, after Custom & Multi-Robot was removed) doesn't divide
   evenly into 3 - a grid leaves its last, incomplete row packed to the left with an empty gap
   where the 6th tile used to be; flex-wrap centres each row independently, so a leftover row of
   1 or 2 tiles still sits centred instead of stranded on one side. */
.icon-strip-grid-3x2{display:flex !important; flex-wrap:wrap; justify-content:center !important; gap:16px !important;}
.icon-strip-grid-3x2 .icon-strip-item{flex:0 1 140px;}
/* Uncapped 1fr columns fill a narrow phone edge-to-edge correctly (there isn't 3x140px of room to
   begin with, so the columns shrink to fit anyway), but on a tablet-width screen there's suddenly
   spare room and "1fr" stretched each column out — same "flat bar" problem as the earlier desktop
   bug, just triggered at a narrower width. Capping at 140px here (matching the mobile tile sizing
   below) keeps every 3-across tile the same square size regardless of exactly how much room the
   viewport has to spare. */
/* Equal-width 1fr columns still respect each item's min-content by default — a longer unbroken
   label ("Collaborative Robots", "Gantry / Cartesian") was forcing its own column wider than the
   other two, since grid only shrinks a track below its content's natural width once min-width is
   explicitly zeroed out. This is what made the homepage's 3x2 row look "broken" (middle box
   bigger than its neighbours); with min-width:0 all three columns share the row equally and the
   labels simply wrap onto their own lines instead. */
.icon-strip-grid-3x2 .icon-strip-item{min-width:0;}
/* At 3-across, each tile is roughly a third of the row's width — the icon-box tile's original
   padding/icon/text sizing was tuned for the wider 2-per-row (mobile) and 6-across (desktop)
   layouts, so it made these narrower tiles noticeably taller than wide instead of reading as a
   true square. Scaled down (icon, gap, text, padding) so each tile is a square at 3-across. */
@media(max-width:999px){
  .icon-strip-grid-3x2 .icon-strip-item.icon-box{padding:16px 8px 14px; gap:10px;}
  .icon-strip-grid-3x2 .icon-strip-item.icon-box svg{width:32px; height:32px;}
  .icon-strip-grid-3x2 .icon-strip-item.icon-box span{font-size:10px; line-height:1.25;}
}
/* Tablet-width in-between step: the 140px column cap above has real breathing room to fill at
   this width (unlike a narrow phone, where there isn't 3x140px available so the columns shrink
   below the cap on their own), so the phone-tuned tile above reads as a short, wide rectangle
   here instead of a square. A touch more padding fills that same 140px-wide tile back out to
   square without needing a whole separate size tier. */
@media(min-width:700px) and (max-width:999px){
  .icon-strip-grid-3x2 .icon-strip-item.icon-box{padding:38px 8px 34px;}
}
/* From 1000px (laptop) up: back to one row of 6, same as the plain (non-boxed) 6-across strip
   used elsewhere on the site — capped tile width + centered row, same pattern as that strip, so
   this doesn't stretch into flat bars on a wide monitor. Icon/padding/gap are tuned down from the
   sitewide icon-box defaults (which were sized for the wider 2-per-row mobile tiles) so each tile
   comes out square instead of a short, wide rectangle. */
@media(min-width:1000px){
  /* The base rule above is flex now (not grid) so a leftover row still centres itself at any
     item count; here that just means overriding the flex-basis/gap for the wider desktop tile
     size instead of a column count. 168px tiles, 52px gap, still auto-centred by the base rule. */
  .icon-strip-grid-3x2{justify-content:center !important; gap:80px !important;}
  .icon-strip-grid-3x2 .icon-strip-item{flex:0 1 168px;}
  .icon-strip-grid-3x2 .icon-strip-item.icon-box{padding:38px 14px 34px; gap:14px;}
  .icon-strip-grid-3x2 .icon-strip-item.icon-box svg{width:52px; height:52px;}
  .icon-strip-grid-3x2 .icon-strip-item.icon-box span{font-size:11.5px; line-height:1.3;}
}
/* Industries page: 10 items rather than 6, so it needs its own column count — same item
   sizing/spacing as the robot-platform strip, just laid out 5-across (two rows) at desktop. */
@media(min-width:1000px){ .icon-strip-grid.grid-10{grid-template-columns:repeat(5,minmax(0,170px)); justify-content:space-between; gap:44px 24px;} }
.icon-strip-item{display:flex; flex-direction:column; align-items:center; gap:18px; text-decoration:none; padding:8px; border-radius:var(--radius); transition:transform .15s ease;}
.icon-strip-item:hover{transform:translateY(-4px);}
.icon-strip-item svg{width:82px; height:82px; stroke:var(--text); fill:none; stroke-width:1.35; transition:stroke .2s ease;}
@media(min-width:1000px){ .icon-strip-item svg{width:112px; height:112px;} }
@media(min-width:1000px){ .icon-strip-grid-3x2 .icon-strip-item svg{width:88px; height:88px;} }
.icon-strip-item span{font-size:12.5px; font-weight:700; letter-spacing:.03em; text-transform:uppercase; color:var(--text); line-height:1.3; transition:color .2s ease;}
.icon-strip-item:hover svg{stroke:var(--orange);}
.icon-strip-item:hover span{color:var(--orange);}

/* Boxed variant — a solid-orange tile with a white icon that inverts to a white tile with a
   black icon on hover. Opt-in via an extra .icon-box class alongside .icon-strip-item; used on
   the homepage and eoat.html's robot-platform strips. */
.icon-strip-item.icon-box{background:var(--orange); border-radius:10px; padding:26px 14px 22px; transition:background .15s ease, transform .15s ease;}
.icon-strip-item.icon-box svg{width:52px; height:52px; stroke:#fff; transition:stroke .2s ease;}
@media(min-width:1000px){ .icon-strip-item.icon-box svg{width:60px; height:60px;} }
.icon-strip-item.icon-box span{color:#fff; font-weight:800; transition:color .2s ease;}
.icon-strip-item.icon-box:hover{background:#fff;}
.icon-strip-item.icon-box:hover svg{stroke:var(--solid-dark);}
.icon-strip-item.icon-box:hover span{color:var(--solid-dark);}

/* Wide variant — the same on-brand orange icon-box (colour + hover-invert above) laid out as a
   long horizontal bar (icon and label side-by-side) instead of a stacked square tile, so the
   robot-type links read more like a scannable list. Reuses .icon-box for colour/hover and only
   overrides the shape, rather than duplicating it. */
/* Flex rather than grid, same reasoning as the orphaned-row fix on .icon-strip-grid-tight above:
   5 tiles don't divide evenly into 2 or 4 columns, so a plain grid leaves the last row's
   leftover tile(s) stuck flush left with an empty gap where the missing column would be.
   justify-content:center plus a fixed flex-basis (matching column width, not growing wider)
   centres a short last row as a group instead.
   At >=900px the column count/gap here deliberately mirror .build-grid-full's own >=900px rule
   (4 columns, 24px gap) so these bars line up edge-to-edge with the 4 "What We Build" photo
   cards above - both sections' .wrap gets the same max-width:none/padding:0 48px treatment at
   >=1000px, so matching the column math here is what keeps the two rows' left/right edges
   aligned, per request. */
.icon-strip-grid.icon-strip-grid-wide{display:flex; flex-wrap:wrap; justify-content:center; gap:36px;}
.icon-strip-grid.icon-strip-grid-wide .icon-strip-item{flex:0 1 100%;}
@media(min-width:640px){ .icon-strip-grid.icon-strip-grid-wide .icon-strip-item{flex:0 1 calc((100% - 36px)/2);} }
@media(min-width:900px){
  .icon-strip-grid.icon-strip-grid-wide{gap:24px;}
  .icon-strip-grid.icon-strip-grid-wide .icon-strip-item{flex:0 1 calc((100% - 72px)/4);}
}
.icon-strip-grid.icon-strip-grid-wide .icon-strip-item.icon-box{
  /* No width/margin overrides here - each bar just fills the flex-basis column set above, so a
     short last row (e.g. the 5th tile left over from 5) gets the exact same gap as a full row
     instead of a much wider one. An earlier width:95%/margin-inline:auto pairing was meant to
     inset each bar slightly within its column, but auto margins on a flex item absorb a share of
     the row's leftover space themselves - on an incomplete row that meant each bar's own
     left/right auto margins soaked up most of the free space directly around it, on top of what
     justify-content already centred the row with, roughly doubling the gap between leftover
     tiles instead of matching the row above. */
  flex-direction:row; align-items:center; justify-content:flex-start; gap:22px;
  padding:34px 30px; text-align:left; border-radius:0;
  /* Small angled "chip" cut into the bottom-right corner - the one detail kept from the
     reference tiles, without copying their colour or full ribbon shape. border-radius is
     zeroed above since a clip-path polygon doesn't respect it on the other three corners -
     square everywhere but this one cut reads as deliberate rather than a rendering mismatch. */
  clip-path:polygon(0 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%);
}
.icon-strip-grid.icon-strip-grid-wide .icon-strip-item.icon-box svg{width:52px; height:52px; flex-shrink:0;}
@media(min-width:1000px){ .icon-strip-grid.icon-strip-grid-wide .icon-strip-item.icon-box svg{width:52px; height:52px;} }
.icon-strip-grid.icon-strip-grid-wide .icon-strip-item.icon-box span{text-align:left; font-size:17px;}
.icon-strip-grid.icon-strip-grid-wide .icon-strip-item:hover{transform:none;}

/* ===== Robot Solutions tabbed carousel (homepage) — replaces the plain row of orange boxes with
   an interactive picker: click (or wait) and the panel below swaps to that robot platform's own
   photo, a short blurb pulled from its page, and a link straight through. Auto-advances like the
   homepage hero carousel (see heroSplitCarousel in script.js), pausing on hover and resetting
   whenever a visitor steps in manually via a tab. eoat.html keeps the plain icon-box row - it
   already pairs that row with a per-platform blurb grid right underneath, so the "boxes with no
   context" problem this solves doesn't apply there. */
.rs-carousel{margin-top:0;}
/* No border-bottom on the tab row itself (removed per request) - the .rs-tabs-indicator sliding
   underline below is the only line here, moving to sit under whichever tab is active. */
/* margin-bottom (tab row to photo/copy panel below) also matched to the reference's own gap
   there, measured the same way as the top padding above - up from 36px. */
.rs-tabs{
  display:flex; flex-wrap:wrap; justify-content:flex-start; gap:8px 28px;
  margin-bottom:75px;
  position:relative;
}
.rs-tab{
  background:none; border:none; cursor:pointer; padding:10px 2px;
  font-family:var(--font-body); font-size:15px; font-weight:700; letter-spacing:.03em;
  text-transform:uppercase; color:var(--text-dim);
  transition:color .15s ease;
}
.rs-tab:hover{color:var(--text);}
.rs-tab.is-active{color:var(--orange);}
/* Single shared underline that slides to sit beneath whichever tab is active, instead of each
   tab drawing its own border — positioned absolutely against .rs-tabs (relative) and animated by
   JS setting transform/width to match the active tab's own offsetLeft/offsetWidth (see
   positionIndicator() in script.js), so it reads as one line moving rather than a hard cut.
   top:0 (not bottom) because the transform's own Y offset does the vertical placement — the tabs
   can wrap onto more than one line at narrow widths, so the active tab isn't always on the last
   line the container's own bottom edge would put a bottom-anchored indicator on. */
.rs-tabs-indicator{
  position:absolute; left:0; top:0; height:2px; width:0; background:var(--orange);
  transition:transform .3s cubic-bezier(.4,0,.2,1), width .3s cubic-bezier(.4,0,.2,1);
}
/* .rs-panel reuses .f-img/.f-body/.f-num (defined above for .feature-row) for the photo/heading/
   copy styling, but without .feature-row's own list-style top/bottom borders - this is a single
   swapped panel, not a row in a repeating stack. Even 1fr/1fr split (rather than a fixed-width
   image column) so the photo reads at the same scale as the reference layout it's matching. */
.rs-panel{display:grid; grid-template-columns:1fr; gap:28px;}
@media(min-width:860px){ .rs-panel{grid-template-columns:1fr 1fr; gap:48px; align-items:center;} }
/* .icon-strip > .wrap runs edge-to-edge at >=1000px (see the rule below), which let the panel's
   1fr/1fr split stretch the photo far larger than intended - each column tracked roughly half the
   full viewport width, blowing the 4/3 .f-img out to 600px+ tall. Capping the panel itself (not
   the wrap, which the tab row above and eoat.html's icon-tile grid still rely on running full
   width) brings the photo back down to a contained scale - left-aligned rather than centered, so
   it still starts at the same left edge as the tab row sitting above it. 1560px (not a narrower
   cap) reuses the same "wide" content measure already used elsewhere on the site (footer,
   related-pages @1300px+) - matches the reference's own image/text proportions much more closely
   than a tighter cap does, so the photo reads at the same scale as the reference instead of
   looking small and cramped next to it. */
@media(min-width:1000px){ .rs-panel{max-width:1560px;} }
/* Matches .news-grid .news-card[hidden] above: .rs-panel sets its own display:grid, which would
   otherwise outrank the browser's default [hidden]{display:none}. */
.rs-panels .rs-panel[hidden]{display:none;}
/* Fade-up entrance for whichever panel the tab switch just revealed - same trick as the homepage
   hero's slide entrance (see .hero-slide.is-active above): the JS side only ever toggles the
   `hidden` attribute, so re-showing a panel is a display:none -> display:grid flip, which is what
   restarts a CSS animation on its own. The photo and the copy block animate in together with a
   tiny stagger (copy just behind the photo) rather than the whole two-column row popping in as
   one flat block. */
.rs-panels .rs-panel:not([hidden]) > .f-img,
.rs-panels .rs-panel:not([hidden]) > .f-body{
  animation:rs-panel-in .6s cubic-bezier(.16,1,.3,1) both;
}
.rs-panels .rs-panel:not([hidden]) > .f-body{animation-delay:.08s;}
@keyframes rs-panel-in{
  from{opacity:0; transform:translateY(24px);}
  to{opacity:1; transform:translateY(0);}
}
@media(prefers-reduced-motion:reduce){
  .rs-panels .rs-panel:not([hidden]) > .f-img,
  .rs-panels .rs-panel:not([hidden]) > .f-body{animation:none;}
}
.rs-kicker{
  display:flex; align-items:center; gap:12px; color:var(--orange);
  padding-bottom:16px; margin-bottom:28px; border-bottom:1px solid var(--hairline);
}
.rs-kicker svg{width:28px; height:28px; stroke:var(--orange); fill:none; stroke-width:1.4; flex-shrink:0;}
.rs-kicker span{font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;}
/* Reserves space for 2 lines of heading regardless of how many this particular platform's title
   actually wraps to (some are one line, e.g. "Speed And Precision For Flat-Plane Work" at wide
   widths; others wrap to two, e.g. "Designed To Work Safely Alongside People") - without this,
   the kicker/line above stays put but everything below it (paragraph, buttons, and the photo
   column next to it, via .rs-panel's align-items:center) shifts vertically depending on which tab
   is active, instead of lining up the same way on every panel. */
.rs-panel h3{min-height:2.2em;}

/* Centered section-head: reusable centering treatment for a strip's title, independent of
   whether the strip sits in a bordered tile or runs edge-to-edge with no background. */
.section-head.centered, .icon-tile .section-head{max-width:900px; margin:0 auto 36px; text-align:center;}
.icon-strip .section-head.centered h2{white-space:nowrap;}
@media(max-width:900px){ .icon-strip .section-head.centered h2{white-space:normal;} }
.section-head.centered .eyebrow, .icon-tile .section-head .eyebrow{justify-content:center;}
.section-head.centered p, .icon-tile .section-head p{margin-left:auto; margin-right:auto;}

/* Tiled variant: originally a bordered card (background:var(--ink2) + border) containing a
   centered title above the icon grid, with its own contained width and internal padding to give
   that card some breathing room inside its border. The background/border is gone now (removed
   per request — see eoat.html's "robot platform" picker), and with it the reason for a
   contained width and extra internal padding on top of the section's own — both dropped too, so
   this now spaces exactly like the plain edge-to-edge .icon-strip > .wrap rule above (used on the
   homepage's "Robot Solutions" section) instead of visibly having more room around it. */
.icon-tile{padding:0;}
.icon-tile-cta{display:flex; justify-content:center; margin-top:40px;}

/* ===== STATS ===== */
.stats{background:var(--ink); border-top:1px solid var(--line); border-bottom:1px solid var(--line);}
.stats-row{display:grid; grid-template-columns:repeat(2,1fr); }
@media(min-width:760px){ .stats-row{grid-template-columns:repeat(4,1fr);} }
/* 3-stat variant (case study pages) so three results sit as three even columns instead of
   leaving a gap where a 4th would be. */
@media(min-width:760px){ .stats-row.cols-3{grid-template-columns:repeat(3,1fr);} }
.stat{padding:40px 28px; border-right:1px solid var(--line); border-bottom:1px solid var(--line);}
@media(min-width:760px){ .stat{border-bottom:none;} .stat:last-child{border-right:none;} }
.stat .num{font-family:var(--font-display); font-size:clamp(28px,3.4vw,42px); color:#fff;}
.stat .num em{color:var(--orange); font-style:normal;}
.stat .label{font-size:12.5px; color:var(--text-dim); margin-top:8px; max-width:180px;}

/* ===== ACCREDITATIONS ===== */
.accreds{padding:80px 0; background:var(--ink); border-bottom:1px solid var(--line);}
.accred-grid{display:grid; grid-template-columns:1fr; gap:1px; background:var(--line); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;}
@media(min-width:760px){ .accred-grid{grid-template-columns:repeat(4,1fr);} }
.accred-cell{background:var(--ink); padding:28px 26px;}
.accred-cell svg{width:26px; height:26px; stroke:var(--orange); fill:none; stroke-width:1.7; margin-bottom:16px;}
.accred-cell h4{font-size:14px; font-weight:700; letter-spacing:.03em; margin-bottom:8px;}
.accred-cell p{font-size:13px; color:var(--text-dim); line-height:1.6;}

/* ===== ROBOT TYPES ===== */
.robots{padding:88px 0;}
/* Every page using this grid has exactly 3 cards, but a straight grid's 2-column tablet tier
   (640-999px) doesn't divide evenly into 3, leaving the last card packed to the left with an
   empty gap - same orphaned-row issue fixed for the icon-strip rows elsewhere. Flex-wrap centres
   a leftover row regardless of item count. */
.rt-grid{display:flex; flex-wrap:wrap; justify-content:center; gap:20px;}
.rt-grid .rt-cell{flex:0 1 100%;}
@media(min-width:640px){ .rt-grid .rt-cell{flex:0 1 calc((100% - 20px)/2);} }
@media(min-width:1000px){ .rt-grid .rt-cell{flex:0 1 calc((100% - 40px)/3);} }
.rt-cell{background:var(--ink2); padding:28px 26px; border-radius:var(--radius); transition:background .15s ease;}
.rt-cell:hover{background:var(--ink3);}
.rt-top{display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:16px;}
.rt-index{font-family:var(--font-display); font-size:13px; color:var(--text-dimmer);}
/* margin:0 shorthand (not just margin-bottom) - h4/p both carry a browser-default top margin
   that was never reset here, which was adding real, visible empty space above the title and
   between the title/description in every .rt-cell card sitewide (16 pages use this component) -
   not just an inconsistency, an actual layout bug on all of them. */
.rt-cell h4{font-size:16px; font-weight:700; margin:0 0 10px;}
.rt-cell p{font-size:13.5px; color:var(--text-dim); margin:0 0 16px; line-height:1.65;}
.rt-tags{display:flex; flex-wrap:wrap; gap:6px;}
.rt-tags span{font-size:10.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--text-dim); background:var(--ink3); border-radius:4px; padding:4px 9px;}

/* ===== ENGINEERING / DRAWING ===== */
.engineering{background:var(--ink); border-top:1px solid var(--line); border-bottom:1px solid var(--line); padding:80px 0;}
.eng-grid{display:grid; grid-template-columns:1fr; gap:44px; align-items:center;}
@media(min-width:900px){ .eng-grid{grid-template-columns:1fr 1.1fr;} }
/* Dual Parallel Gripper case study: same reasoning as the .feature-row override above - the
   technical-detail photo is narrowed a bit in favour of the copy, on this page only. */
@media(min-width:900px){ #page-dpg-case-study .eng-grid{grid-template-columns:0.8fr 1.3fr;} }
.eng-drawing{background:#fff; border-radius:0; padding:10px; }
/* Same graceful-degradation treatment as .f-img img (color:transparent + font-size:0) - a missing
   source now collapses to the plain white card instead of showing a broken-image glyph and alt
   text. Noticed this was missing while testing the .eng-grid.flip work above; a few of these
   slots (custom-multi-robot.html, end-of-arm-tooling/delta-robots.html,
   end-of-arm-tooling/gantry-cartesian-robots.html) reference images that still aren't in the
   assets folder - not something this round touches, but worth flagging to Corey. */
.eng-drawing img{width:100%; height:auto; display:block; border-radius:0; color:transparent; font-size:0;}
/* Dual Parallel Gripper case study: this slot holds a real workshop photo rather than an
   engineering drawing, so the white paper-style background/padding read as a stray white
   outline around it - dropped for this page only, matching the plain rounded-corner treatment
   used for the other photos on this page. */
#page-dpg-case-study .eng-drawing{background:none; padding:0;}
#page-dpg-case-study .eng-drawing img{border-radius:0;}
/* Same fix, as a reusable class instead of a page-id override - for any .eng-drawing slot
   (like the first one on brick-tile.html) holding a real photo rather than a white-background
   engineering drawing, where the paper-style frame reads as a stray white border. */
.eng-drawing-photo{background:none; padding:0;}
.eng-drawing-photo img{border-radius:0;}
/* Same "kicker to the bottom" treatment as .feature-row .f-body above, for the handful of
   .eng-copy blocks (mostly case-study "Technical Detail" panels) that also carry an .f-num tag
   above their heading - moved after the rest of the copy via flex order so it reads as a tag
   under the content instead of a headline interrupting it. */
.eng-copy{display:flex; flex-direction:column;}
.eng-copy .f-num{order:5; margin-bottom:0; margin-top:18px;}
.eng-copy p{color:var(--text-dim); font-size:15px; margin-bottom:18px; max-width:460px;}
.eng-list{list-style:none; margin:26px 0 0; padding:0; display:grid; gap:14px;}
.eng-list li{display:flex; align-items:flex-start; gap:14px; font-size:14px; color:var(--text-dim);}
.eng-list li::before{
  content:''; flex-shrink:0; width:7px; height:7px; margin-top:8px;
  border-radius:50%; background:var(--orange);
}
/* EOAT page: the image half of the engineering section (broken - source file was never supplied)
   was removed from the markup, leaving just the copy column. Drop back to a single, unconstrained
   column here instead of the image/copy 1fr/1.1fr split every other page with this section uses. */
#eoatEngineering .eng-grid{grid-template-columns:1fr;}
#eoatEngineering .eng-copy{max-width:700px;}
/* Brick & tile page: the CAD render in this engineering section's image half was removed at
   Corey's request, leaving just the copy column. Same single-column drop-back as the EOAT page
   rule above. */
#brickTileEngineering .eng-grid{grid-template-columns:1fr;}
#brickTileEngineering .eng-copy{max-width:700px; margin:0 auto;}

/* ===== PROCESS STEPS (robot-cells / machinery pages) ===== */
.process{padding:88px 0; border-bottom:1px solid var(--line);}
.process-grid{display:grid; grid-template-columns:1fr; gap:1px; background:var(--line); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;}
@media(min-width:760px){ .process-grid{grid-template-columns:repeat(4,1fr);} }
.process-cell{background:var(--ink2); padding:26px 24px;}
.process-cell .p-index{font-family:var(--font-display); font-size:13px; color:var(--orange); margin-bottom:14px;}
.process-cell h4{font-size:15px; font-weight:700; margin-bottom:8px;}
.process-cell p{font-size:13px; color:var(--text-dim); line-height:1.6;}

/* ===== INTERACTIVE STEPPER (click-through process, e.g. "How We Work") =====
   A horizontal numbered track with a connecting line — no boxes, no borders. Clicking a step
   swaps the shared content panel below via a small fade. Steps stack vertically on mobile
   instead of wrapping awkwardly.
   No max-width here (was previously capped at 760px, well short of the section's ~1216px
   content width) — the three steps use flex:1 already, so letting the track fill its parent
   spreads them evenly across the full row instead of leaving them bunched against the left
   edge with empty space on the right. */
.stepper{}
.stepper-track{display:flex; align-items:flex-start; gap:0; position:relative; margin-bottom:44px;}
.stepper-step{
  flex:1; display:flex; flex-direction:column; align-items:center; gap:16px; text-align:center;
  background:none; border:none; cursor:pointer; padding:0 8px; position:relative; font-family:inherit;
}
.stepper-step::before{
  content:''; position:absolute; top:25px; left:-50%; width:100%; height:2px; background:var(--line); z-index:0;
}
.stepper-step:first-child::before{content:none;}
.stepper-num{
  position:relative; z-index:1; width:52px; height:52px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; font-family:var(--font-display); font-size:19px;
  background:var(--ink2); border:2px solid var(--line); color:var(--text-dim);
  transition:background .2s ease, border-color .2s ease, color .2s ease;
}
.stepper-step.active .stepper-num{background:var(--orange); border-color:var(--orange); color:#fff;}
.stepper-label{font-size:15.5px; font-weight:700; color:var(--text-dim); letter-spacing:.02em; line-height:1.3; transition:color .2s ease;}
.stepper-step.active .stepper-label{color:var(--text);}
.stepper-step:hover .stepper-label{color:var(--text);}
.stepper-panel{min-height:120px;}
.stepper-panel p{display:none; color:var(--text-dim); font-size:17.5px; line-height:1.75; max-width:720px; animation:stepperFade .3s ease;}
.stepper-panel p.active{display:block;}
@keyframes stepperFade{ from{opacity:0; transform:translateY(4px);} to{opacity:1; transform:translateY(0);} }
@media(max-width:640px){
  .stepper-track{flex-direction:column; align-items:stretch; gap:8px;}
  .stepper-step{flex-direction:row; text-align:left; gap:16px; padding:12px 0;}
  .stepper-step::before{content:none;}
  .stepper-num{width:42px; height:42px; font-size:16px;}
  .stepper-label{font-size:15px;}
  .stepper-panel p{font-size:16px;}
}

/* ===== CLIENT SLIDER ===== */
.clients{padding:80px 0 70px;}
.clients .section-head{margin-bottom:34px;}
.client-slider{position:relative; overflow:hidden;}
.client-slider::before, .client-slider::after{
  content:''; position:absolute; top:0; bottom:0; width:460px; z-index:2; pointer-events:none;
}
/* Wider fade zone than a bare edge-to-transparent gradient — logos are noticeably obscured
   well before they reach the true edge, instead of staying visible almost all the way there. */
.client-slider::before{left:0; background:linear-gradient(to right, var(--ink) 0%, var(--ink) 35%, rgba(24, 24, 24,0) 100%);}
.client-slider::after{right:0; background:linear-gradient(to left, var(--ink) 0%, var(--ink) 35%, rgba(24, 24, 24,0) 100%);}
.client-track{display:flex; align-items:center; width:max-content; will-change:transform; animation:clientScroll 42s linear infinite;}
@keyframes clientScroll{ from{transform:translateX(0);} to{transform:translateX(-50%);} }
.client-item{
  flex:0 0 200px; width:200px; height:80px;
  display:flex; align-items:center; justify-content:center;
  opacity:.85;
}
.client-item .logo-mark{width:150px; height:56px; background-repeat:no-repeat; background-position:center; background-size:contain;}
@media(max-width:640px){
  /* The desktop fade (460px each side) covers the entire logo strip on a phone screen, hiding
     logos. Rather than just shrinking it, drop it entirely below this width — the fade is a nice
     desktop touch but isn't worth losing visible logos over on a small screen. */
  .client-slider::before, .client-slider::after{display:none;}
  .client-item{flex:0 0 120px; width:120px; height:56px;}
  .client-item .logo-mark{width:88px; height:34px;}
}

/* ===== PAGE HEADER — CELL SHOWCASE BACKGROUND (image marquee) =====
   Collaborative Robots' own .page-header background. This page shared the generic EOAT panel
   photo/video every other interior header uses, but that render is a fenced six-axis arm - the
   wrong machine for a cobot page (Corey's call). Swapped it for a continuous, gapless, square-
   edged wall of the cell-simulation renders instead, sitting inside .page-header-img in exactly
   the spot a photo or video normally would, so it still inherits that layer's absolute-fill
   sizing and the dark left-to-right legibility gradient (re-tuned just below for this treatment,
   scoped to this page only). Same continuous-scroll technique as .client-slider elsewhere on the
   site: the track holds the image set twice back to back and animates translateX(0) ->
   translateX(-50%), so the loop point is invisible. No gaps and no rounded corners this time -
   it reads as one continuous banner image, not a strip of separate cards. */
.page-header-img .cell-showcase-slider{position:absolute; inset:0; overflow:hidden;}
/* Duration is tuned to the item count, not a fixed number - the half-track distance the
   translateX(-50%) animation covers grows with every image added to the list below, so the
   duration has to grow by the same ratio or the strip suddenly scrolls faster. 13 items now
   (was 10) at the original ~34.9px/s pace: 137.5s * (13/10) = 178.75s. If more renders get added
   to the showcase later, rescale this the same way rather than leaving it at 178.75s. */
.page-header-img .cell-showcase-track{
  display:flex; align-items:stretch; height:100%; width:max-content;
  will-change:transform; animation:cellShowcaseScroll 178.75s linear infinite;
}
@keyframes cellShowcaseScroll{ from{transform:translateX(0);} to{transform:translateX(-50%);} }
.page-header-img .cell-showcase-item{flex:0 0 480px; width:480px; height:100%;}
/* Overrides the sitewide .page-header-img img{opacity:.16} rule (tuned for a single dim photo
   behind text) - these renders need to show at full strength since the darkening for text
   legibility now comes entirely from the gradient below, same approach as the video variant. */
.page-header-img .cell-showcase-item img{width:100%; height:100%; object-fit:cover; display:block; opacity:1;}
#page-collaborative-robots .page-header-img::after{
  background:linear-gradient(to right, rgba(24,24,24,.95) 0%, rgba(24,24,24,.7) 45%, rgba(24,24,24,.5) 100%);
}
/* The cell-simulation renders are bright, white-background CAD exports, so even with the
   legibility gradient above they read as washed-out against the rest of the dark site (Corey's
   feedback). Darkened the renders themselves so the whole strip sits closer to the site's tone,
   rather than relying on the gradient alone to do it. */
#page-collaborative-robots .page-header-img .cell-showcase-item img{filter:brightness(.62);}
@media(max-width:640px){
  .page-header-img .cell-showcase-item{flex:0 0 240px; width:240px;}
  /* Same rescale as the desktop duration above, same 13/10 ratio: 77.5s * (13/10) = 100.75s. */
  .page-header-img .cell-showcase-track{animation-duration:100.75s;}
}

/* ===== TESTIMONIALS ===== */
.testimonials{padding:80px 0; background:var(--ink); border-bottom:1px solid var(--line);}
.test-grid{display:grid; grid-template-columns:1fr; gap:1px; background:var(--line); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;}
@media(min-width:800px){ .test-grid{grid-template-columns:repeat(3,1fr);} }
.test-card{background:var(--ink); padding:30px 28px; display:flex; flex-direction:column;}
.test-card .quote-mark{color:var(--orange); font-family:var(--font-display); font-size:34px; line-height:1; margin-bottom:10px;}
.test-card p{font-size:14.5px; color:#d6d5d1; line-height:1.7; flex:1; margin:0 0 20px;}
.test-card .who{border-top:1px solid var(--line); padding-top:14px;}
.test-card .name{font-size:13px; font-weight:700;}
.test-card .role{font-size:12.5px; color:var(--text-dim); margin-top:2px;}

/* ===== MONTAGE ===== */
.montage{background:var(--ink);}
/* Gallery (10 photos) and homepage (5 photos) both use this grid, and neither count divides
   evenly into the 3-column mobile tier, leaving the last row's photo(s) packed to the left with
   empty tracks on the right - same orphaned-row issue fixed for the icon-strip rows elsewhere.
   Flex-wrap centres a leftover row regardless of item count, so this works for both photo counts
   without special-casing either page. */
.montage-grid{display:flex; flex-wrap:wrap; justify-content:center; gap:3px;}
.m-item{aspect-ratio:4/5; overflow:hidden; background:var(--ink2); flex:0 1 calc((100% - 6px)/3);}
@media(min-width:700px){ .m-item{aspect-ratio:3/4; flex-basis:calc((100% - 12px)/5);} }
.m-item img{width:100%; height:100%; object-fit:cover; transform:scale(1.01); transition:transform .5s ease;}
.m-item:hover img{transform:scale(1.06);}

/* Homepage montage only (#montageGrid): paginated with arrows instead of showing every photo at
   once like the full gallery does, so it needs a real CSS grid here (initCardPager in script.js
   reads the visible-per-page count off grid-template-columns) — scoped to the id rather than
   touching the shared .montage-grid/.m-item flex rules above, which gallery.html's larger,
   unpaginated photo count still relies on to avoid an orphaned last row. */
.montage{position:relative;}
#montageGrid{display:grid; grid-template-columns:repeat(3,1fr);}
@media(min-width:700px){ #montageGrid{grid-template-columns:repeat(4,1fr);} }
#montageGrid .m-item[hidden]{display:none;}
/* Homepage montage strip only, shorter than the shared .m-item ratio above (which gallery.html
   still uses at full height) — the row was reading as too tall/dominant on the homepage, scoped
   to the #montageGrid id so gallery.html's own photo grid is unaffected. */
#montageGrid .m-item{aspect-ratio:16/10;}
@media(min-width:700px){ #montageGrid .m-item{aspect-ratio:16/9;} }
/* .hero-arrow's own background is transparent (it relies on the hero's dark gradient overlay
   behind it to stay visible) - the montage strip has no such overlay, so its arrows need a
   solid backing of their own or they disappear against a light patch of whatever photo happens
   to be underneath them. */
.montage-arrow{position:absolute; top:50%; transform:translateY(-50%); z-index:2; background:rgba(24, 24, 24,.6); border-color:rgba(255,255,255,.55);}
.montage-prev{left:14px;}
.montage-next{right:14px;}
.montage-arrow:disabled{opacity:.35; cursor:not-allowed; pointer-events:none;}

/* ===== LATEST NEWS ===== */
.news{padding:88px 0; border-bottom:1px solid var(--line);}
.news > .wrap{max-width:none; padding:0 16px;}
.news .section-head{margin-bottom:38px;}
.news-slider{position:relative;}
/* These two homepage rows (Latest News, Case Studies) used to be horizontally-scrolling "tracks" —
   overflow-x:auto with scroll-snap and a JS wheel handler forwarding vertical scroll to the page.
   That's exactly the setup that, no matter how it was tuned, could make page scrolling feel
   different while the cursor was over these cards (see script.js's history on initCardCarousel).
   The "What We Build" services grid a little further up the page never had that problem, for the
   simple reason that it was never a scrolling container in the first place — it just lays every
   card out in a plain CSS grid, so the browser never has anything to intercept. Doing the same
   here removes the whole category of issue at the root instead of continuing to patch the track:
   it's a plain grid, and Previous/Next (see initCardPager in script.js) just shift which single
   card slides into/out of the visible row — a click-driven recount-and-toggle with no scroll or
   wheel listener anywhere in it, so there's nothing left that can ever interfere with page
   scrolling again. */
.news-grid{display:grid; grid-template-columns:1fr; gap:32px;}
/* Same card-per-row counts the old horizontally-scrolling track used, just as grid columns
   instead of flex-basis widths — initCardPager() reads this column count back at runtime, so
   the visible row still holds exactly one row's worth of cards at every breakpoint. */
@media(min-width:700px){ .news-grid{grid-template-columns:repeat(2,1fr);} }
@media(min-width:1050px){ .news-grid{grid-template-columns:repeat(3,1fr);} }
@media(min-width:1500px){ .news-grid{grid-template-columns:repeat(4,1fr);} }
/* .news-card sets its own display:flex below, which otherwise outranks the browser's default
   [hidden]{display:none} — without this, a card initCardPager() slides out of view would still
   render as an empty flex box taking up a grid cell. */
.news-grid .news-card[hidden]{display:none;}
.news-card{
  /* flex/scroll-snap-align below are leftover from the old track and no-op in this grid layout —
     left in place since .news-card is also used inside news.html's own static grid, unrelated to
     this homepage section, and harmless either way. */
  flex:0 0 100%; scroll-snap-align:start;
  display:flex; flex-direction:column;
  padding:20px; border-radius:var(--radius);
  cursor:pointer;
}
.news-img{position:relative; border-radius:0; overflow:hidden; background:var(--ink2); aspect-ratio:16/10; margin-bottom:22px;}
/* color:transparent + font-size:0 hide a missing image's broken-icon-and-alt-text glyph, which
   otherwise renders at the top-left of the box (overflow:hidden doesn't clip it away since it's
   small enough to sit fully inside the box) — degrades to the plain --ink2 box instead. */
.news-img img{width:100%; height:100%; object-fit:cover; transition:transform .35s ease; color:transparent; font-size:0;}
/* Same "hover anywhere on the card" trigger as the Read More button below — a gentle zoom on
   the photo itself, clipped by .news-img's overflow:hidden so it never spills past the rounded
   corners. */
.news-card:hover .news-img img, .news-card:focus-within .news-img img{transform:scale(1.06);}
.news-date{font-size:11.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--orange); margin-bottom:10px;}
/* Underline uses native text-decoration rather than an absolutely-positioned ::after bar - a
   fixed-width bar only ever sits under the LAST wrapped line, but takes its width from the
   widest line (needed for a correct single-line result), so on a two-line headline it stretched
   past the shorter second line instead of hugging it. text-decoration underlines every wrapped
   line individually at that line's own width for free, on both single- and multi-line headlines,
   which a hand-built bar can't do without measuring each rendered line in JS. Starts transparent
   and fades to orange on card hover/focus, replacing the old grow-in-from-the-left bar. */
.news-card h3{
  font-family:var(--font-body); font-size:19px; font-weight:800; line-height:1.3; margin-bottom:12px;
  text-decoration:underline; text-decoration-color:transparent; text-decoration-thickness:2px;
  text-underline-offset:4px; transition:text-decoration-color .3s ease;
}
.news-card:hover h3, .news-card:focus-within h3{text-decoration-color:var(--orange);}
.news-card p{color:var(--text-dim); font-size:14.5px; line-height:1.65; margin-bottom:22px; flex:1;}
/* Thin rule above the Read More/Read Case Study link on case-study preview cards, matching the
   FANUC-style reference: a divider separating the copy from the CTA rather than the link just
   trailing straight off the paragraph. Scoped to .cs-card so plain .news-card (news grid) is
   unaffected - subtle low-opacity white rather than --line (transparent by design elsewhere) or
   full orange, so it reads as a quiet separator, not another accent. */
.cs-card .cs-divider{border-top:1px solid var(--hairline); margin:0 0 18px;}
/* "Read More" text link inside news/case-study cards (swapped from a filled pill - see
   .news-read-more below): no chrome of its own — instead it darkens the moment the pointer
   enters the card ANYWHERE, not just when directly over the link, so the whole card still reads
   as one clickable unit. */
.news-card:hover .news-read-more, .news-card:focus-within .news-read-more{color:var(--orange-dark);}
.news-card:hover .news-read-more svg, .news-card:focus-within .news-read-more svg{animation:read-more-nudge 1.6s ease-in-out infinite;}
/* In the homepage News and Case Studies rows (.news-grid), the photo zoom above previously had
   its arrow-nudge neighbour disabled alongside it for a real, measured cost: while the page
   scrolls and the cursor sits still on screen, the browser keeps re-testing :hover as each card
   passes underneath it, retriggering these transitions dozens of times over the course of one
   scroll gesture. A frame-timing test (scripted scroll, cursor parked over a card) showed dropped
   visible frames specifically while over a card, stopping once both transforms were disabled.
   Per a later, explicit request the arrow-nudge is back on here too, matching every other card
   grid on the site - so only the photo zoom keeps the disable now. If scroll jank on this section
   reappears, this arrow-nudge re-enable is the thing to revisit first. */
.news-grid .news-card:hover .news-img img, .news-grid .news-card:focus-within .news-img img{transform:none;}
/* "Read More" as a small arrow-link (matching .rc-arrow on the related-pages cards) instead of a
   filled button - less "big box" against the card's already-heavy image and headline. */
.news-read-more{
  display:inline-flex; align-items:center; gap:6px; align-self:flex-start;
  font-size:12px; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  color:var(--orange); text-decoration:none; transition:color .15s ease;
}
/* Hover nudge - a small, slow, continuous side-to-side motion that only starts once the card
   is actually hovered/focused (see .news-card:hover/:focus-within above), rather than running
   unprompted for every card on the page the moment it's on screen. Static/still by default. */
@keyframes read-more-nudge{
  0%, 100%{transform:translateX(0);}
  50%{transform:translateX(5px);}
}
.news-read-more svg{
  width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2.4;
  transition:transform .15s ease;
}
.news-read-more:hover, .news-read-more:focus-visible{color:var(--orange-dark);}
.news-read-more:hover svg, .news-read-more:focus-visible svg{animation:read-more-nudge 1.6s ease-in-out infinite;}
/* Stagger so a row of cards doesn't bounce in perfect unison. */
.news-card:nth-child(2) .news-read-more svg{animation-delay:.2s;}
.news-card:nth-child(3) .news-read-more svg{animation-delay:.4s;}
.news-card:nth-child(4) .news-read-more svg{animation-delay:.6s;}
.news-controls{display:flex; justify-content:center; gap:14px; margin-top:44px; flex-wrap:wrap;}
@media(max-width:640px){
  .news-controls{gap:8px;}
  .news-controls .btn{padding:9px 14px; font-size:10.5px;}
}
.news-btn:disabled{opacity:.35; cursor:not-allowed; pointer-events:none;}

/* ===== NEWS / CASE STUDIES LINK TILES (homepage) =====
   Replaces what used to be two full 4-card preview grids (Latest News, Case Studies) - each one
   now a single photo-plus-caption tile pointing at the full listing instead of duplicating its
   content on the homepage. One shared component for both; only the image/heading/link differ.
   .ncs-row lays all three tiles out in a single row on desktop and tablet, stacking straight to
   one column on narrow viewports - no intermediate 2-then-1 wrap. */
.ncs-section{padding:64px 0;}
/* On wide screens the standard 1280px .wrap leaves a lot of empty margin either side of just
   three tiles - widen this section's own wrap the same way footer/.related-pages already do
   elsewhere in this file, so the row (and each tile) reads noticeably bigger on desktop without
   touching the tablet/mobile behaviour above. */
@media(min-width:1300px){ .ncs-section > .wrap{max-width:1560px;} }
/* .ncs-section's own bottom padding sits inside its dark background, but the full-bleed image
   montage strip directly below it on the homepage is a separate section with no top spacing of
   its own, so the two touched with zero gap between the News/Case Studies cards and the first row
   of gallery photos. Scoped to the adjacent-sibling case so .montage elsewhere (or .ncs-section
   followed by something else) is unaffected. */
.ncs-section + .montage{margin-top:56px;}
.ncs-row{display:grid; grid-template-columns:1fr 1fr 1fr; gap:24px; max-width:1560px; margin:0 auto;}
@media(max-width:900px){ .ncs-row{gap:16px;} }
@media(max-width:640px){ .ncs-row{grid-template-columns:1fr; gap:24px;} }
.ncs-tile{
  display:block; max-width:680px; margin:0 auto; overflow:hidden;
  background:var(--ink2); border:1px solid var(--line); border-radius:var(--radius);
  transition:transform .2s ease, border-color .2s ease;
}
.ncs-row .ncs-tile{max-width:none; margin:0;}
.ncs-tile:hover, .ncs-tile:focus-visible{transform:translateY(-4px); border-color:var(--orange);}
.ncs-img{aspect-ratio:16/9; overflow:hidden;}
.ncs-img img{width:100%; height:100%; object-fit:cover; display:block; transition:transform .35s ease;}
.ncs-tile:hover .ncs-img img, .ncs-tile:focus-visible .ncs-img img{transform:scale(1.06);}
.ncs-box{padding:30px 34px; display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;}
.ncs-box h3{font-size:23px;}
.ncs-cta{
  display:inline-flex; align-items:center; gap:8px; flex-shrink:0;
  color:var(--orange); font-weight:700; font-size:13.5px; letter-spacing:.05em; text-transform:uppercase; white-space:nowrap;
}
.ncs-cta svg{width:15px; height:15px; stroke:currentColor; fill:none; stroke-width:2.4;}
.ncs-tile:hover .ncs-cta, .ncs-tile:focus-visible .ncs-cta{color:var(--orange-dark);}
@media(max-width:480px){
  .ncs-box{padding:20px 22px;}
  .ncs-box h3{font-size:18px;}
}

/* ===== ABOUT (company page) ===== */
.about{padding:88px 0; border-bottom:1px solid var(--line);}
.about-grid{display:grid; grid-template-columns:1fr; gap:48px;}
/* Match the founder-grid column split above (180px avatar + 48px gap) so the copy in these
   text-only sections lines up under the founder photo's text column rather than the photo itself.
   Scoped to .about-grid .about-copy specifically (not just .about-copy) - a bare .about-copy{grid-column:2}
   rule here was bleeding into the .eng-grid photo sections below, which reuse the .about-copy class for
   paragraph styling but need normal auto-placement so the .flip variant can swap sides correctly. */
@media(min-width:760px){ .about-grid{grid-template-columns:180px 1fr;} .about-grid .about-copy{grid-column:2;} }
.about-copy p{color:var(--text-dim); font-size:15.5px; line-height:1.8; margin-bottom:18px; max-width:700px;}
.about-facts{list-style:none; margin:0; padding:0; display:grid; gap:18px; align-content:start;}
.about-facts li{border-left:2px solid var(--orange); padding-left:16px;}
.about-facts .fact-title{font-size:14px; font-weight:700;}
.about-facts .fact-desc{font-size:13px; color:var(--text-dim); margin-top:4px;}

/* ===== CONTACT STRIP (enquire page) =====
   Plain info blocks sitting directly on the page background, above the form — icon, label,
   value. No grid of equal boxed columns, no borders, no dividers between them: just spacing.
   Not a table in look or in markup. */
.contact-strip{padding:56px 0 8px;}
.contact-strip-row{display:flex; flex-wrap:wrap; gap:40px 56px;}
.contact-strip-item{display:flex; gap:16px; align-items:flex-start; flex:1 1 240px;}
.contact-strip-item .c-icon{width:38px; height:38px; border-radius:50%; background:rgba(255,93,29,.12); display:flex; align-items:center; justify-content:center; flex-shrink:0;}
.contact-strip-item .c-icon svg{width:17px; height:17px; stroke:var(--orange); fill:none; stroke-width:2; stroke-linecap:round; stroke-linejoin:round;}
.contact-strip-item h4{font-size:11.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--text-dimmer); margin-bottom:8px;}
.contact-strip-item .big{font-size:16.5px; font-weight:700;}
.contact-strip-item .big a:hover{color:var(--orange);}
.contact-strip-item .sub{font-size:13px; color:var(--text-dim); margin-top:5px; line-height:1.6;}
/* Live Chat tile: shown on all screen sizes (desktop included), but only while Tawk actually
   reports someone live on the other end. Hidden by default here so there's nothing to flash
   before script.js hears back from Tawk (or if that never happens - e.g. the embed is blocked) -
   script.js then reveals it only for a genuinely 'online' status, not 'away' or 'offline'. */
.contact-strip-chat{display:none;}
.contact-strip-item .cs-chat-btn{background:none; border:none; padding:0; margin:0; font:inherit; font-weight:700; color:inherit; cursor:pointer; text-align:left;}
.contact-strip-item .cs-chat-btn:hover{color:var(--orange);}

/* ===== ENQUIRY FORM (enquire page) =====
   Form sits next to a short "what to include" sidebar instead of that content living in its
   own separate section further down the page — one glance tells you what to have ready before
   you start typing. Sidebar sticks in place while the (longer) form scrolls past it on desktop. */
.enquiry{padding:88px 0; border-bottom:1px solid var(--line);}
.enquiry-layout{display:grid; grid-template-columns:1fr; gap:48px;}
@media(min-width:960px){ .enquiry-layout{grid-template-columns:1.6fr 1fr; align-items:start;} }
.enquiry-aside{background:var(--ink2); border:1px solid var(--line); border-radius:var(--radius); padding:32px 30px;}
@media(min-width:960px){ .enquiry-aside{position:sticky; top:110px;} }
.enquiry-aside h3{font-size:clamp(20px,2.4vw,24px); margin-bottom:12px;}
/* "What to include in your enquiry" box: the emphasised phrase gets its own solid orange
   highlighter chip (square corners, white text) rather than the sitewide plain-orange-text
   .accent style, per request - scoped to this one aside so it doesn't affect headings
   elsewhere. white-space:nowrap keeps "your enquiry." together as a single unbroken chip - on
   a narrow aside it was splitting mid-phrase into two separate stacked boxes (one for "your",
   one for "enquiry.") with a gap between them, rather than reading as one square block; now the
   whole phrase wraps as a unit onto its own line instead of breaking inside the highlight. */
.enquiry-aside h3 .accent{
  display:inline; background:var(--orange); color:#fff; border-radius:0;
  padding:2px 8px; white-space:nowrap;
}
.enquiry-aside > p{color:#fff; font-size:14px; line-height:1.65; margin:0 0 22px;}
.enquiry-aside .eng-list{gap:16px;}
/* Bullet list text in this box reused the sitewide muted-grey .eng-list li colour - brightened to
   pure white here specifically (not the .eng-list default, which stays dimmer on the
   image/copy engineering sections elsewhere) since Corey wants this box's copy to read at full
   contrast against its dark card background. */
.enquiry-aside .eng-list li{color:#fff;}
.enquiry-form{max-width:none;}
.form-row{display:grid; grid-template-columns:1fr; gap:20px; margin-bottom:20px;}
@media(min-width:640px){ .form-row{grid-template-columns:1fr 1fr;} }
.form-field{display:flex; flex-direction:column; gap:0;}
.form-field.full{grid-column:1 / -1;}
/* "Highlighter" label chip - solid orange background sitting right on top of its field, rather
   than plain muted text, so each field is easier to scan at a glance. A small negative
   margin-bottom (plus z-index, so it paints over the field rather than under it) lets the chip
   overlap down into the top of the box slightly, like a tab rather than a floating label. */
.form-field label{
  position:relative; z-index:1; align-self:flex-start;
  font-size:11.5px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:#fff; background:var(--orange); padding:5px 10px; margin-bottom:-8px; line-height:1.4;
  border-radius:5px;
}
.form-field label .req{color:#000;}
.form-field input, .form-field select, .form-field textarea{
  background:var(--text); border:1px solid var(--line); border-radius:14px;
  color:var(--solid-dark); font-family:var(--font-body); font-size:15px; padding:18px 18px;
  transition:border-color .15s ease; width:100%;
}
.form-field input::placeholder, .form-field textarea::placeholder{color:#8b8b8f;}
.form-field textarea{resize:vertical; min-height:150px;}
.form-field input:focus, .form-field select:focus, .form-field textarea:focus{
  outline:none; border-color:var(--orange);
}
/* Browsers (Chrome, Safari/iOS included) force their own light autofill background onto a
   filled-in field — a plain `background` property can't override it, hence the email field
   flashing a different pale colour against this now-white field the moment something like a
   saved Gmail address gets autofilled into it. The -webkit-box-shadow inset trick is the
   standard workaround: it paints over the browser's autofill background with the form's own
   white fill, and the long transition-delay stops the browser's autofill colour flashing in
   first. */
.form-field input:-webkit-autofill,
.form-field input:-webkit-autofill:hover,
.form-field input:-webkit-autofill:focus,
.form-field input:-webkit-autofill:active{
  -webkit-text-fill-color:var(--solid-dark);
  -webkit-box-shadow:0 0 0 1000px var(--text) inset;
  box-shadow:0 0 0 1000px var(--text) inset;
  caret-color:var(--solid-dark);
  transition:background-color 5000s ease-in-out 0s;
}
.form-field select{-webkit-appearance:none; -moz-appearance:none; appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23555559' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 14px center; background-size:16px; padding-right:40px;}
/* Custom dropdown, replacing the browser/OS's own native <select> panel with a styled one that
   matches the rest of the form. script.js progressively enhances each select.form-field select
   at load time: the real <select> stays in the DOM (visually hidden, not disabled) so its
   name/value still posts exactly as before via FormData - only its own OS-native popup is
   swapped out for this custom trigger + listbox. Without JS the native select is left alone and
   fully usable, so the field never stops working. */
.custom-select{position:relative;}
.custom-select .custom-select-native{
  position:absolute; opacity:0; width:1px; height:1px; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; pointer-events:none;
}
.custom-select-trigger{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  width:100%; background:var(--text); border:1px solid var(--line); border-radius:14px;
  color:var(--solid-dark); font-family:var(--font-body); font-size:15px; padding:18px 18px;
  cursor:pointer; transition:border-color .15s ease;
}
.custom-select-trigger .cst-value.is-placeholder{color:#8b8b8f;}
.custom-select.open .custom-select-trigger, .custom-select-trigger:focus-visible{
  outline:none; border-color:var(--orange);
}
.custom-select-chevron{width:16px; height:16px; flex-shrink:0; stroke:#555559; fill:none; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; transition:transform .15s ease;}
.custom-select.open .custom-select-chevron{transform:rotate(180deg);}
.custom-select-list{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:20;
  background:var(--text); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 20px 40px rgba(0,0,0,.35);
  max-height:260px; overflow-y:auto;
  list-style:none; margin:0; padding:6px 0;
  opacity:0; visibility:hidden; transform:translateY(-4px);
  transition:opacity .12s ease, transform .12s ease, visibility .12s;
}
.custom-select.open .custom-select-list{opacity:1; visibility:visible; transform:translateY(0);}
.custom-select-list li{padding:12px 18px; font-size:15px; color:var(--solid-dark); cursor:pointer; transition:background .1s ease, color .1s ease;}
.custom-select-list li:hover, .custom-select-list li.is-highlighted{background:var(--orange); color:#fff;}
.custom-select-list li[aria-selected="true"]{font-weight:700;}
.form-note{font-size:13px; color:var(--text-dimmer); margin-top:16px;}
/* Privacy-policy consent and newsletter opt-in checkboxes on the quote form - deliberately
   plain checkbox rows (not the highlighted field-label style above) so they read as agreements
   to tick, not data-entry fields. */
.form-consent{display:flex; flex-direction:column; gap:14px; margin:4px 0 24px;}
.form-check{display:flex; align-items:flex-start; gap:12px; font-size:13.5px; line-height:1.6; color:var(--text-dim); cursor:pointer;}
.form-check input[type="checkbox"]{
  -webkit-appearance:none; -moz-appearance:none; appearance:none; flex-shrink:0; width:20px; height:20px; margin-top:1px;
  background:var(--ink2); border:1px solid var(--text-dimmer); border-radius:3px;
  cursor:pointer; position:relative; transition:background .15s ease, border-color .15s ease;
}
.form-check input[type="checkbox"]:checked{background:var(--orange); border-color:var(--orange);}
.form-check input[type="checkbox"]:checked::after{
  content:""; position:absolute; left:6px; top:2px; width:5px; height:10px;
  border:solid #fff; border-width:0 2px 2px 0; transform:rotate(45deg);
}
.form-check input[type="checkbox"]:focus-visible{outline:2px solid var(--orange); outline-offset:2px;}
.form-check a{color:var(--text); text-decoration:underline;}
.form-check a:hover{color:var(--orange);}
.form-check .req{color:var(--orange);}
/* Netlify spam honeypot — kept in the accessibility tree as hidden rather than display:none,
   since some bots skip fields they can detect are display:none. */
.hp-field{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap;}
.form-status{font-size:14px; margin-top:16px; display:none;}
.form-status.visible{display:block;}
.form-status.success{color:var(--orange);}

/* ===== LEGAL / PRIVACY POLICY ===== */
.legal{padding:72px 0 100px;}
.legal-updated{font-size:13px; color:var(--text-dimmer); margin-bottom:40px; padding-bottom:24px; border-bottom:1px solid var(--line); max-width:760px;}
.legal-body{max-width:760px;}
.legal-body h2{font-size:clamp(20px,2.4vw,26px); margin-top:44px; margin-bottom:16px;}
.legal-body h2:first-child{margin-top:0;}
.legal-body h3{font-size:15.5px; margin-top:26px; margin-bottom:10px; color:var(--text);}
.legal-body p{color:var(--text-dim); font-size:14.5px; line-height:1.8; margin-bottom:16px;}
.legal-body a{color:var(--orange);}
.legal-body ul, .legal-body ol{margin:0 0 16px; padding-left:20px; display:grid; gap:10px;}
.legal-body li{color:var(--text-dim); font-size:14.5px; line-height:1.7;}
.legal-body li::marker{color:var(--orange);}
.legal-body strong{color:var(--text); font-weight:700;}
.cookie-table{width:100%; border-collapse:collapse; margin:0 0 24px; font-size:13.5px;}
.cookie-table th, .cookie-table td{text-align:left; padding:11px 14px; border-bottom:1px solid var(--line); vertical-align:top;}
.cookie-table th{font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-dimmer);}
.cookie-table td{color:var(--text-dim); line-height:1.6;}
.cookie-table td:first-child{color:var(--text); font-weight:700; white-space:nowrap;}
.legal-note{margin-top:44px; padding:18px 20px; border:1px solid var(--line); border-radius:var(--radius); background:var(--ink2);}
.legal-note p{font-size:13px; color:var(--text-dimmer); margin:0; line-height:1.7;}

/* ===== FAQ ===== */
.faq{padding:88px 0;}
.faq-list{max-width:820px;}
.faq-item{border-bottom:1px solid var(--line);}
.faq-q{display:flex; align-items:center; justify-content:space-between; gap:20px; padding:22px 4px; cursor:pointer;}
.faq-q span.q-text{font-size:15.5px; font-weight:600;}
.faq-q span.icon{font-size:20px; color:var(--orange); flex-shrink:0; transition:transform .2s ease; font-family:var(--font-body); font-weight:300;}
.faq-item.open .faq-q span.icon{transform:rotate(45deg);}
.faq-a{max-height:0; overflow:hidden; transition:max-height .25s ease;}
.faq-a-inner{padding:0 4px 22px; font-size:14px; color:var(--text-dim); line-height:1.7; max-width:680px;}

/* ===== EXPLORE MORE ===== */
/* Text and images are two separate tiers here: the .related-grid up top is the site's existing
   text-and-arrow link card (no image, no new styling needed), and .explore-strip below reuses the
   Montage section's photo-strip layout so all four images sit together along the bottom edge
   instead of one per card. */
.explore-more{padding:64px 0 0;}
.explore-more .related-grid{padding-bottom:56px;}

.explore-strip{display:grid; grid-template-columns:repeat(2, 1fr); gap:3px;}
@media(min-width:700px){ .explore-strip{grid-template-columns:repeat(4, 1fr);} }
.explore-strip-item{position:relative; aspect-ratio:4/3; overflow:hidden; background:var(--ink2); display:block;}
.explore-strip-item img{width:100%; height:100%; object-fit:cover; transform:scale(1.01); transition:transform .5s ease;}
.explore-strip-item:hover img{transform:scale(1.06);}
.explore-strip-item span{
  position:absolute; left:0; right:0; bottom:0; padding:14px 16px;
  font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#fff;
  background:linear-gradient(to top, rgba(24, 24, 24,.85), transparent);
}

/* ===== CTA STRIP ===== */
.cta-strip{background:var(--orange); padding:64px 0;}
.cta-strip .wrap{display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:28px;}
.cta-strip h2{color:#fff; font-size:clamp(24px,3.2vw,34px); text-transform:uppercase; max-width:520px;}
.cta-strip .btn-primary{background:var(--ink); color:#fff;}
.cta-strip .btn-primary:hover{background:var(--ink3);}
.cta-strip .btn-outline{border-color:rgba(255,255,255,.5); color:#fff;}
.cta-strip .btn-outline:hover{border-color:#fff; background:rgba(255,255,255,.12);}

/* ===== FOOTER ===== */
footer{background:#191919; border-top:1px solid var(--line); padding:64px 0 32px;}
.footer-grid{display:grid; grid-template-columns:1fr; gap:44px;}
@media(min-width:1100px){ .footer-grid{grid-template-columns:1.1fr 2.4fr; gap:56px; align-items:start;} }
.footer-brand{max-width:340px;}
.footer-logo{height:56px; width:auto; margin-bottom:24px;}
.footer-hours-head{font-family:var(--font-body); font-weight:700; font-size:11.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--text-dimmer); margin-bottom:14px;}
.footer-hours ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:3px; font-size:13.5px;}
.footer-hours li{display:flex; justify-content:space-between; gap:16px; margin:0; padding:6px 10px; border-radius:6px; color:var(--text); transition:background .15s ease, color .15s ease;}
.footer-hours li span:first-child{font-weight:600; color:var(--text);}
.footer-hours li.today{background:rgba(255,93,29,.14);}
.footer-hours li.today span{color:var(--orange); font-weight:700;}
/* Accreditation badges (SafeContractor Approved, East Midlands Chamber) sit under the opening
   hours - both badges' own backgrounds (a white circle, a rounded lavender pill) read fine
   against the dark footer, so no special treatment needed beyond a shared height and a flex row
   so two differently-shaped badges (a square seal, a wide wordmark) still line up cleanly. */
.footer-badges{margin-top:26px; display:flex; align-items:center; flex-wrap:wrap; gap:20px;}
.footer-badge{height:84px; width:auto;}
/* TEMPORARY: SafeContractor accreditation is paused sitewide - hiding the badge without touching
   the <img> markup or deleting assets/safecontractor-badge.png/.webp, so it's a one-line rule to
   delete (restoring the badge everywhere at once) once accreditation is back. The selector matches
   on the image filename rather than a class, so it works from any page depth (assets/... or
   ../assets/...) without needing every page's HTML edited. */
img[src*="safecontractor-badge"]{ display:none !important; }
.footer-nav-cols{display:grid; grid-template-columns:1fr 1fr; gap:32px 24px;}
@media(min-width:520px){ .footer-nav-cols{grid-template-columns:repeat(4,1fr);} }
@media(max-width:639px){ .footer-nav-cols{display:none;} }
footer h4{font-family:var(--font-body); font-weight:700; font-size:11.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--text-dimmer); margin-bottom:18px;}
footer ul{list-style:none; margin:0; padding:0; font-size:15px; color:#d6d5d1;}
footer li{margin-bottom:13px;}
footer li a{transition:color .15s ease;}
footer li a:hover{color:var(--orange);}
footer li a.active{color:var(--orange); font-weight:700;}
/* border-top uses --orange directly (not the shared --line variable, which is deliberately
   transparent sitewide) so this specific divider renders as a visible accent line. */
.footer-bottom{margin-top:52px; padding-top:24px; border-top:1px solid var(--orange); font-size:12.5px; color:var(--text); display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:16px;}
.footer-bottom a{color:var(--text); transition:color .15s ease;}
.footer-bottom a:hover{color:var(--orange);}
.footer-bottom a.active{color:var(--orange); font-weight:700;}
.back-to-top{
  width:44px; height:44px; border-radius:50%; flex-shrink:0;
  background:var(--orange); border:none; color:#fff;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer; transition:background .15s ease, color .15s ease, transform .15s ease;
}
.back-to-top svg{width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round;}
.back-to-top:hover, .back-to-top:focus-visible{background:#fff; color:var(--ink); transform:translateY(-3px);}
/* Widen the footer's own wrap at large viewports (matching the news/icon-tile sections) and
   open the column gap up a bit further — at very wide screens the standard 1280px cap left
   the five columns bunched together in the middle of the page with a lot of unused space
   either side, reading as cramped rather than spacious. */
@media(min-width:1300px){ footer > .wrap{max-width:1560px;} }
.socials{display:flex; gap:10px;}
.socials a{width:34px; height:34px; border-radius:6px; display:flex; align-items:center; justify-content:center; color:#fff; transition:color .15s ease;}
.socials a:hover{color:var(--orange);}

.logo-wienerberger{background-image:url("assets/logos/wienerberger.svg");}
.logo-nestle{background-image:url("assets/logos/nestle.webp");}
.logo-stericycle{background-image:url("assets/logos/Stericycle-Logo.webp");}
.logo-thg{background-image:url("assets/logos/THG.svg");}
/* ===== MOBILE INPUT ZOOM FIX ===== */
/* iOS Safari auto-zooms the page when focusing any form control with a computed font-size
   under 16px. All our inputs sit a little under that for a tighter desktop look, so on phones
   specifically we bump every text input/select/textarea (search bar included) up to exactly
   16px — enough to stop the zoom without visibly changing the desktop design. */
@media(max-width:640px){
  .site-search input,
  .form-field input, .form-field select, .form-field textarea{
    font-size:16px;
  }
}

/* ===== LOCATION MAP (About Us / Get a Quote) ===== */
.location-map{padding:0 0 90px;}
.map-bar{display:flex; flex-wrap:wrap; align-items:center; gap:14px; background:var(--ink2); border:1px solid var(--line); border-bottom:none; border-radius:var(--radius) var(--radius) 0 0; padding:16px 22px;}
.map-bar-address{flex:1; min-width:220px; font-size:13.5px; font-weight:600; color:var(--text-dim); text-align:center;}
.map-bar-btn{font-size:12px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; padding:12px 20px; border-radius:8px; text-align:center; white-space:nowrap; transition:border-color .15s ease, color .15s ease, background .15s ease;}
.map-bar-btn--ghost{background:var(--ink3); color:var(--text); border:1px solid var(--line);}
.map-bar-btn--ghost:hover{border-color:var(--orange); color:var(--orange);}
.map-bar-btn--primary{background:var(--orange); color:#fff;}
.map-bar-btn--primary:hover{background:var(--orange-dark);}
.map-embed{position:relative; aspect-ratio:21/7; border:1px solid var(--line); border-radius:0 0 var(--radius) var(--radius); overflow:hidden; filter:grayscale(.2) contrast(1.05);}
.map-embed iframe{position:absolute; inset:0; width:100%; height:100%; border:0;}
@media(max-width:640px){
  .map-bar{flex-direction:column; align-items:stretch;}
  .map-bar-address{order:-1; text-align:left;}
  .map-embed{aspect-ratio:4/3;}
}

/* ===== FLOATING "REQUEST A QUOTE" TAB ===== */
/* Injected once via script.js (see addQuoteTab), so it appears the same way on every page without
   duplicating markup into ~30 templates. A small squared-off info-icon tab pinned to the right
   edge, offset down below the header (top/nav bar) rather than sitting flush at the very top of
   the viewport where it was overlapping the header content. The icon itself is a literal italic
   "i" glyph (like the reference's own) rather than a stroke-based circle+line+dot, which at this
   size read too much like an alert/warning icon instead of an info one. The visible label is
   dropped in favour of the a[title]/aria-label for a tooltip + accessible name instead.
   z-index:60 keeps it above the header (50) but below the cookie-consent modal (90), so the modal
   still properly blocks it while open. One size for every breakpoint - small enough on its own
   that, unlike the old text version, it doesn't need a separate cramped mobile layout. */
.quote-tab{
  position:fixed; right:0; top:400px; z-index:60;
  display:flex; align-items:center; justify-content:center;
  width:52px; height:52px;
  background:var(--orange); color:#fff; text-decoration:none;
  border-radius:0;
  box-shadow:-3px 3px 14px rgba(0,0,0,.3);
  transition:background .15s ease;
}
.quote-tab:hover{background:var(--orange-dark);}
.quote-tab-i{font-family:Georgia,'Times New Roman',serif; font-style:italic; font-weight:700; font-size:26px; line-height:1; color:#fff;}
@media(max-width:640px){
  .quote-tab{width:44px; height:44px; top:160px;}
  .quote-tab-i{font-size:22px;}
}

/* ===== COOKIE CONSENT ===== */
/* box-shadow lives only on the .visible state (not the base rule) rather than being always-on:
   mobile Safari's bottom toolbar collapses/expands as you scroll, which resizes the fixed
   viewport this bar is anchored to and can repaint a translated-off-screen box-shadow as a
   stray dark band even while the banner itself is fully hidden below the fold. Scoping the
   shadow to .visible means there's nothing to mis-paint when the banner isn't shown - it can
   only ever appear together with the banner, never on its own. will-change hints the browser to
   keep this on its own compositor layer through the slide transition, which is the other half of
   avoiding the same repaint glitch. */
.cookie-banner{position:fixed; left:0; right:0; bottom:0; z-index:80; background:var(--ink2); border-top:1px solid var(--line); padding:20px 0; transform:translateY(110%); transition:transform .35s ease; will-change:transform;}
.cookie-banner.visible{transform:translateY(0); box-shadow:0 -12px 32px rgba(0,0,0,.35);}
.cookie-banner-inner{max-width:var(--max); margin:0 auto; padding:0 32px; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:18px 24px;}
.cookie-banner-inner p{margin:0; font-size:13.5px; color:var(--text-dim); line-height:1.6; max-width:640px;}
.cookie-banner-inner a{color:var(--orange);}
.cookie-banner-actions{display:flex; gap:10px; flex-wrap:wrap;}

.cookie-modal{display:none; position:fixed; inset:0; z-index:90; align-items:center; justify-content:center; padding:20px;}
.cookie-modal.open{display:flex;}
.cookie-modal-backdrop{position:absolute; inset:0; background:rgba(0,0,0,.6);}
.cookie-modal-panel{position:relative; background:var(--ink2); border:1px solid var(--line); border-radius:var(--radius); max-width:520px; width:100%; padding:32px 30px; max-height:calc(100vh - 40px); overflow-y:auto;}
.cookie-modal-panel h3{font-size:19px; margin-bottom:10px; text-transform:uppercase;}
.cookie-modal-panel > p{font-size:13.5px; color:var(--text-dim); line-height:1.65; margin-bottom:22px;}
.cookie-modal-close{position:absolute; top:18px; right:18px; width:32px; height:32px; border:none; background:var(--ink3); border-radius:50%; color:var(--text-dim); font-size:20px; line-height:1; cursor:pointer; transition:color .15s ease, background .15s ease;}
.cookie-modal-close:hover{color:var(--orange); background:var(--ink);}
.cookie-row{display:flex; align-items:flex-start; justify-content:space-between; gap:20px; padding:16px 0; border-top:1px solid var(--line);}
.cookie-row-title{font-size:14px; font-weight:700; margin-bottom:4px;}
.cookie-row-desc{font-size:12.5px; color:var(--text-dim); line-height:1.55;}
.cookie-toggle{flex-shrink:0; width:44px; height:26px; border-radius:999px; border:none; background:var(--ink3); border:1px solid var(--line); position:relative; cursor:pointer; padding:0; margin-top:2px; transition:background .15s ease, border-color .15s ease;}
.cookie-toggle span{position:absolute; top:2px; left:2px; width:20px; height:20px; border-radius:50%; background:var(--text-dim); transition:transform .18s ease, background .15s ease;}
.cookie-toggle[aria-checked="true"]{background:rgba(255,93,29,.25); border-color:var(--orange);}
.cookie-toggle[aria-checked="true"] span{transform:translateX(18px); background:var(--orange);}
.cookie-toggle--locked{cursor:default; opacity:.7;}
.cookie-toggle--locked span{background:var(--orange); transform:translateX(18px);}
.cookie-modal-actions{display:flex; justify-content:flex-end; gap:10px; margin-top:24px; flex-wrap:wrap;}
@media(max-width:640px){
  .cookie-banner-inner{flex-direction:column; align-items:stretch;}
  .cookie-banner-actions{justify-content:stretch;}
  .cookie-banner-actions .btn{flex:1;}
}

/* ===== RECENTLY VIEWED (Standard Range, functional-cookie gated) ===== */
.recently-viewed{padding:36px 0 0;}
.recently-viewed .eyebrow{margin-bottom:16px;}
.rv-list{display:flex; gap:16px; overflow-x:auto; padding-bottom:6px; touch-action:pan-y;}
.rv-card{flex:0 0 auto; width:150px; display:flex; flex-direction:column; gap:10px;}
.rv-img{display:block; aspect-ratio:4/3; border-radius:0; overflow:hidden; background:var(--ink2); border:1px solid var(--line);}
.rv-img img{width:100%; height:100%; object-fit:cover; transition:transform .2s ease;}
.rv-card:hover .rv-img img{transform:scale(1.05);}
.rv-name{font-size:12px; font-weight:600; color:var(--text-dim); line-height:1.4;}
.rv-card:hover .rv-name{color:var(--orange);}

/* ===== FOUNDER & TEAM (About Us) ===== */
.founder{padding:8px 0 80px; border-top:1px solid var(--line);}
.founder-grid{display:grid; grid-template-columns:1fr; gap:36px; align-items:start; padding-top:56px;}
@media(min-width:760px){ .founder-grid{grid-template-columns:180px 1fr; gap:48px;} }
.founder-avatar{width:120px; height:120px; border-radius:50%; background:linear-gradient(145deg, var(--orange), var(--orange-dark)); color:#fff; display:flex; align-items:center; justify-content:center; font-family:var(--font-display); font-size:38px; letter-spacing:.02em; flex-shrink:0; overflow:hidden;}
.founder-avatar img{width:100%; height:100%; object-fit:cover;}
@media(min-width:760px){ .founder-avatar{width:170px; height:170px; font-size:52px;} }
/* Same max-width as .about-copy p just below (the "From a cattle shed"/"Engineering-led"
   sections further down this page) - keep these two in sync: a mismatch makes the paragraph
   column visibly step in or out by a few dozen pixels between this section and the next despite
   both starting at the same left edge, reading as misaligned even though the headings themselves
   line up. Widened from 480px - that read as a narrow, cramped column of text with a lot of
   empty space open to its right inside the page's actual width. */
.founder-copy p{color:var(--text-dim); font-size:15px; line-height:1.75; margin-bottom:16px; max-width:700px;}

.team{padding:0 0 90px;}
.team-grid{display:grid; grid-template-columns:1fr; gap:24px;}
@media(min-width:640px){ .team-grid{grid-template-columns:1fr 1fr;} }
@media(min-width:1100px){ .team-grid{grid-template-columns:repeat(3,1fr);} }
.team-card{background:var(--ink2); border:1px solid var(--line); border-radius:var(--radius); padding:26px 24px;}
.team-avatar{width:52px; height:52px; border-radius:50%; background:var(--ink3); border:1px solid var(--line); color:var(--orange); display:flex; align-items:center; justify-content:center; font-family:var(--font-display); font-size:16px; margin-bottom:16px;}
.team-name{font-size:16px; font-weight:700; margin-bottom:2px;}
.team-role{font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--orange); margin-bottom:12px;}
.team-bio{font-size:13.5px; color:var(--text-dim); line-height:1.65; margin:0;}

/* ===== ROI CALCULATOR ===== */
.roi{padding:0 0 90px;}
.roi-panel{display:grid; grid-template-columns:1fr; gap:0; background:var(--ink2); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;}
@media(min-width:900px){ .roi-panel{grid-template-columns:1.15fr 1fr;} }
.roi-inputs{padding:36px 32px;}
.roi-inputs .form-row{margin-bottom:0;}
.roi-fields{display:grid; grid-template-columns:1fr; gap:22px;}
@media(min-width:560px){ .roi-fields{grid-template-columns:1fr 1fr; column-gap:20px;} }
.roi-field{margin-bottom:0;}
.roi-field label{display:block; font-size:12.5px; font-weight:700; letter-spacing:.03em; text-transform:uppercase; color:var(--text-dim); margin-bottom:8px;}
.roi-field .roi-input-wrap{position:relative;}
.roi-field input{width:100%; background:var(--ink3); border:1px solid var(--line); border-radius:var(--radius); color:var(--text); font-family:var(--font-body); font-size:15px; padding:12px 14px;}
.roi-field input:focus{outline:none; border-color:var(--orange);}
.roi-field .roi-hint{font-size:12px; color:var(--text-dimmer); margin-top:6px;}
.roi-results{padding:36px 32px; background:var(--ink3); display:flex; flex-direction:column; justify-content:center;}
.roi-result-row{display:flex; justify-content:space-between; align-items:baseline; gap:16px; padding:16px 0; border-bottom:1px solid var(--line);}
.roi-result-row:first-child{padding-top:0;}
.roi-result-row:last-of-type{border-bottom:none;}
.roi-result-label{font-size:13px; color:var(--text-dim);}
.roi-result-value{font-family:var(--font-display); font-size:clamp(22px,2.6vw,30px); color:var(--text); white-space:nowrap;}
.roi-result-value.accent{color:var(--orange);}
.roi-disclaimer{font-size:11.5px; color:var(--text-dimmer); line-height:1.6; margin-top:18px;}
.roi-results .btn{margin-top:22px; align-self:flex-start;}
@media(max-width:640px){ .roi-inputs, .roi-results{padding:28px 22px;} }

/* ===== BUILD (4-tile grid, full width, homepage only) ===== */
.build-grid-full{display:grid; grid-template-columns:1fr 1fr; gap:20px;}
@media(max-width:560px){ .build-grid-full{grid-template-columns:1fr;} }
@media(min-width:900px){ .build-grid-full{grid-template-columns:repeat(4,1fr); gap:24px;} }

.build-grid-compact .build-card-compact{
  display:flex; flex-direction:column; padding:14px; border-radius:var(--radius); cursor:pointer;
}
.build-card-compact .f-img{aspect-ratio:16/8.5; border-radius:14px; overflow:hidden; margin-bottom:11px; background:var(--ink2);}
.build-card-compact .f-img img{width:100%; height:100%; object-fit:cover;}
.build-card-compact .f-num{font-size:10.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--orange); margin-bottom:4px;}
/* Title gets an animated underline that scales in from the left on card hover — inline-block so
   the ::after line matches the text's own width rather than stretching the full card. */
.build-card-compact h3{position:relative; display:inline-block; align-self:flex-start; font-size:14.5px; text-transform:uppercase; line-height:1.22; margin-bottom:7px;}
.build-card-compact h3::after{
  content:''; position:absolute; left:0; bottom:-3px; width:100%; height:2px; background:var(--orange);
  transform:scaleX(0); transform-origin:left; transition:transform .3s ease;
}
.build-grid-compact .build-card-compact:hover h3::after,
.build-grid-compact .build-card-compact:focus-within h3::after{transform:scaleX(1);}
.build-card-compact a{font-size:11.5px; font-weight:700; letter-spacing:.02em; color:var(--orange); margin-top:auto;}
/* Underlines on hover/focus anywhere on the card - same trigger as the h3 underline and the
   arrow nudge below, not just when the cursor is directly over this short line of text. */
.build-grid-compact .build-card-compact:hover a,
.build-grid-compact .build-card-compact:focus-within a{text-decoration:underline;}
/* Arrow nudges right on card hover, independent of the link's own underline-on-hover above. */
.build-card-compact a .cta-arrow{display:inline-block; transition:transform .2s ease;}
.build-grid-compact .build-card-compact:hover a .cta-arrow,
.build-grid-compact .build-card-compact:focus-within a .cta-arrow{transform:translateX(4px);}

/* ===== ROI CALCULATOR — standalone, orange, full-bleed, no inner box ===== */
.roi-orange{background:none; padding:64px 0; position:relative; overflow:hidden;}
.roi-orange::before{
  content:''; position:absolute; top:0; bottom:0; left:0; right:340px;
  background:var(--orange-dark); z-index:-1;
}
@media(max-width:2200px){ .roi-orange::before{right:0;} }
.roi-orange > .wrap{position:relative; z-index:1;}
.roi-watermark{
  position:absolute; top:50%; left:-10%; transform:translateY(-50%);
  width:40%; max-width:560px; min-width:220px; opacity:.85; pointer-events:none; z-index:0;
}
@media(max-width:2200px){
  /* The min-width floor meant this stayed a fairly large fixed size even as the fields column
     around it kept shrinking, so the overlap got worse — not better — at the narrow end of its
     visible range. Pushing the hide threshold well past that range instead of nudging it. */
  .roi-watermark{display:none;}
}
/* Outer layout: title sits in the black area to the left, the orange panel (form + results)
   fills the rest — title width is tuned to roughly match where the panel actually starts above. */
.roi-orange-layout{display:grid; grid-template-columns:1fr; gap:36px;}
@media(min-width:2201px){ .roi-orange-layout{grid-template-columns:1fr 340px; gap:0; align-items:center;} }
.roi-orange-title{padding-left:24px; min-width:0; max-width:100%;}
@media(max-width:2200px){
  .roi-orange-title{order:-1; padding-left:0;}
}
.roi-orange .eyebrow{color:#fff;}
.roi-orange h2{color:#fff; margin:0; font-size:clamp(26px,2.4vw,42px); line-height:1.1; overflow-wrap:break-word; word-break:break-word;}
.roi-orange h2 mark{background:#171412; color:#fff; padding:.03em .22em; margin:0 -.05em; border-radius:0; box-decoration-break:clone; -webkit-box-decoration-break:clone;}
.roi-orange-grid{display:grid; grid-template-columns:1fr; gap:32px; padding:0 32px; min-width:0;}
@media(min-width:900px){ .roi-orange-grid{grid-template-columns:1.1fr 1fr; align-items:center; gap:48px; padding:0 48px 0 32px;} }
.roi-orange .roi-fields{display:grid; grid-template-columns:1fr; gap:16px; margin-bottom:0;}
@media(min-width:560px){ .roi-orange .roi-fields{grid-template-columns:1fr 1fr;} }
.roi-orange .roi-field label{color:#fff; font-size:11px; font-weight:800; letter-spacing:.03em; text-transform:uppercase; margin-bottom:7px; display:block;}
.roi-orange .roi-field input{width:100%; background:#fff; border:1px solid #fff; border-radius:8px; color:#171412; font-family:var(--font-body); font-size:15px; padding:12px 14px;}
.roi-orange .roi-field input::placeholder{color:#8b8b8f;}
.roi-orange .roi-field input:focus{outline:none; border-color:#171412;}
.roi-field-throughput{margin-top:22px; padding-top:20px; border-top:1px dashed rgba(255,255,255,.35);}
.roi-throughput-hint{font-size:11.5px; color:rgba(255,255,255,.72); line-height:1.5; margin-top:8px;}
.roi-orange-results .roi-result-row{display:flex; justify-content:space-between; align-items:baseline; gap:16px; padding:14px 0; border-bottom:1px solid rgba(255,255,255,.35);}
.roi-orange-results .roi-result-row:first-child{padding-top:0;}
.roi-orange-results .roi-result-label{font-size:13.5px; color:#fff;}
.roi-orange-results .roi-result-value{font-family:var(--font-display); font-size:clamp(20px,2.2vw,26px); color:#fff;}
.roi-orange .btn{margin-top:22px; align-self:stretch; text-align:center; width:100%;}
.roi-orange .roi-disclaimer{font-size:11.5px; color:#fff; line-height:1.6; margin-top:14px;}

/* ============================================================
   SCROLL REVEAL — opt-in via class="reveal", see script.js.
   A plain fade + gentle rise, nothing bouncy — matches the site's understated tone rather than
   feeling like a template demo. Staggered via the --reveal-delay custom property so groups of
   cards animate in sequence instead of all landing at once. Duration trimmed from .6s to .35s —
   combined with script.js's IntersectionObserver now triggering earlier (while an item is still
   just below the fold, not only once it's well into view), a quicker transition means it's
   finished settling in step with the scroll instead of visibly still catching up after it's
   already on screen.
   ============================================================ */
.reveal{opacity:0; transform:translateY(18px); transition:opacity .35s ease, transform .35s ease; transition-delay:var(--reveal-delay, 0s);}
.reveal.is-visible{opacity:1; transform:translateY(0);}
@media(prefers-reduced-motion: reduce){
  .reveal{opacity:1; transform:none; transition:none;}
}

/* ============================================================
   SIX-AXIS PAGE — wider content on large screens.
   Interior content sections default to the sitewide 1280px .wrap; this page opts into the same
   1560px "wide" measure already used by the footer and icon-strip sections elsewhere, so on
   large monitors the page reads as considered/full rather than a narrow column adrift in dark
   space. Nav/topbar wraps are deliberately excluded — they keep the standard header width used
   across the whole site.
   .testimonials .wrap is deliberately left out of this list (and the brick & tile one below) -
   the quote should read at the same 1280px measure as the homepage testimonial on every page,
   not stretch to each page's wider "page-wide"/brick-tile content measure. */
@media(min-width:1300px){
  body.page-wide .page-header > .wrap,
  body.page-wide .stats > .wrap,
  body.page-wide .build .wrap,
  body.page-wide .robots .wrap,
  body.page-wide .engineering .wrap,
  body.page-wide .process .wrap,
  body.page-wide .faq .wrap,
  body.page-wide .cta-strip .wrap{
    max-width:1560px;
  }
}

/* BRICK & TILE — wider still. Uses the id (higher specificity than the .page-wide class rule
   above) to push this specific page's content measure past the sitewide "wide" 1560px, without
   affecting the other 17 pages that also carry body.page-wide. */
@media(min-width:1300px){
  #page-brick-tile .page-header > .wrap,
  #page-brick-tile .stats > .wrap,
  #page-brick-tile .build .wrap,
  #page-brick-tile .robots .wrap,
  #page-brick-tile .engineering .wrap,
  #page-brick-tile .process .wrap,
  #page-brick-tile .faq .wrap,
  #page-brick-tile .cta-strip .wrap{
    max-width:1760px;
  }
}

/* ===== 6-AXIS: "how we work" cards get an icon ===== */
.rt-icon{width:34px; height:34px; margin-bottom:16px; stroke:var(--orange); fill:none; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round;}
/* .rt-cell is shared by 16 pages (aerospace.html, collaborative-robots.html, etc.) — the hover
   lift is scoped to body.page-wide (six-axis-robots.html only, for now) so this preview doesn't
   silently change those other pages before they've been reviewed. Once the new style is rolled
   out more broadly, body.page-wide can be dropped from this selector. Transition-delay is
   repeated explicitly here so cards that are also .reveal elements keep their stagger instead of
   this later, equal-specificity rule silently resetting it to 0s. */
/* ===== 6-AXIS: "how we work" as a big-numeral editorial index, not a card grid =====
   Replaces an earlier card-grid attempt (and a connector-line diagram attempt before that) —
   both still read as generic template layouts. This is closer to a magazine contents page than
   a UI component: one open row at a time, nothing boxed or bordered into "cards". No index
   numerals, no per-row icon and no leading dash marker — the visual anchor is just the title's
   own colour shift plus the orange edge rule on the active row. Built on the same
   grid-template-rows collapse trick used for the topbar, so the expand/collapse is a genuine
   smooth resize, not a max-height guess. */
.rt-editorial{}
.rt-row{position:relative;}
.rt-row::before{
  content:''; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--orange);
  transform:scaleY(0); transform-origin:top; transition:transform .3s ease;
}
.rt-row.is-active::before{transform:scaleY(1);}
.rt-row-head{
  display:flex; align-items:center; gap:20px; width:100%; padding:26px 4px 26px 28px;
  background:none; border:none; cursor:pointer; text-align:left; font-family:inherit;
}
.rt-title{
  font-family:var(--font-display); font-size:clamp(20px,2.6vw,32px); text-transform:uppercase;
  color:var(--text-dim); transition:color .25s ease; flex:1;
}
.rt-row.is-active .rt-title, .rt-row-head:hover .rt-title{color:var(--text);}
.rt-row-chevron{
  width:20px; height:20px; flex-shrink:0; stroke:var(--text-dimmer); fill:none; stroke-width:2.4;
  transition:transform .3s ease, stroke .2s ease;
}
.rt-row.is-active .rt-row-chevron{transform:rotate(180deg); stroke:var(--orange);}
.rt-row-body{display:grid; grid-template-rows:0fr; overflow:hidden; transition:grid-template-rows .35s ease;}
.rt-row.is-active .rt-row-body{grid-template-rows:1fr;}
.rt-row-body-inner{
  min-height:0; overflow:hidden;
  padding:0 4px 28px 28px;
  transition:padding .3s ease;
}
/* The row's own bottom padding has to collapse alongside grid-template-rows — otherwise the
   padding alone holds the box open at ~30px even once the track itself is animated to 0fr (the
   same fix already used for the topbar's own collapse). */
.rt-row:not(.is-active) .rt-row-body-inner{padding-bottom:0;}
.rt-row-body-inner p{color:var(--text-dim); font-size:15px; line-height:1.6; max-width:560px;}
@media(max-width:640px){
  .rt-row-head{gap:14px; padding:18px 2px 18px 20px;}
  .rt-row-body-inner{padding-left:20px;}
}

/* ===== 6-AXIS: testimonial spotlight =====
   Replaces the old forced single-column grid (a leftover 3-card layout squashed to one card,
   which is why it read as sparse/empty) with a deliberate two-part spotlight: a large pull-quote
   on one side and a short "why this matters" note on the other, so a single testimonial still
   fills the section with intent instead of looking like a placeholder. */
.testimonial-spotlight{display:grid; grid-template-columns:1fr; gap:32px; padding:0;}
@media(min-width:860px){ .testimonial-spotlight{grid-template-columns:1.5fr 1fr; gap:0; padding:0;} }
.testimonial-spotlight .ts-quote{padding:44px 36px;}
@media(min-width:860px){ .testimonial-spotlight .ts-quote{padding:52px 48px; border-right:1px solid var(--line);} }
.testimonial-spotlight .quote-mark{color:var(--orange); font-family:var(--font-display); font-size:48px; line-height:1; margin-bottom:14px;}
.testimonial-spotlight .ts-quote p{font-size:clamp(18px,2vw,23px); line-height:1.5; color:#f2f1ee; margin:0 0 24px;}
.testimonial-spotlight .who{padding-top:18px; border-top:1px solid var(--line);}
.testimonial-spotlight .name{font-size:13.5px; font-weight:700;}
.testimonial-spotlight .role{font-size:12.5px; color:var(--text-dim); margin-top:2px;}
.testimonial-spotlight .ts-note{padding:36px; display:flex; flex-direction:column; justify-content:center; gap:14px;}
@media(min-width:860px){ .testimonial-spotlight .ts-note{padding:48px 40px;} }
.testimonial-spotlight .ts-note-label{font-size:11px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; color:var(--orange);}
.testimonial-spotlight .ts-note p{font-size:14px; color:var(--text-dim); line-height:1.7; margin:0;}
/* Several EOAT pages dropped the "why it worked" note (it was misleadingly framed as a
   robot-type-specific outcome when the quote itself is a general testimonial), leaving just the
   quote card. Without this, it would sit in the grid's 1.5fr column with a stray border and an
   empty 1fr gap beside it. :has() collapses it back to a single, full-width column instead -
   scoped so pages that still carry a genuine two-up note (e.g. brick-tile.html) are unaffected.
   Every page with no .ts-note now gets this (previously the six EOAT robot-type pages and
   custom-multi-robot.html had a separate centred soft-panel treatment here - Corey asked for
   every quote sitewide to match the plain left-aligned look brick-tile.html's two-column
   .ts-note version already has, so that soft-panel styling was removed and this single-column
   grid fix is all that's left: everything else now just falls through to the base
   .testimonial-spotlight styles above (single top quote-mark, left-aligned, no panel). */
@media(min-width:860px){
  .testimonial-spotlight:not(:has(.ts-note)){grid-template-columns:1fr;}
  .testimonial-spotlight:not(:has(.ts-note)) .ts-quote{border-right:none;}
}

/* ===== 6-AXIS: stat band in the signature orange, white text =====
   Scoped to this page (not the shared .stat/.num used on index.html's stats band) since that
   page hasn't been reviewed with this treatment yet. */
body.page-wide .stats{background:var(--orange); border-top-color:transparent; border-bottom-color:transparent;}
body.page-wide .stat{border-right-color:transparent; border-bottom-color:transparent;}
body.page-wide .stat .num{color:#fff;}
body.page-wide .stat .num em{color:#fff;}
body.page-wide .stat .label{color:rgba(255,255,255,.85);}

/* ===== 6-AXIS: keep every section on the same dark tone =====
   .engineering and .testimonials normally use the lighter --ink2 panel colour to alternate with
   the page background elsewhere on the site — on this page that read as an inconsistent lighter
   grey band next to everything else, so both are pinned to the same darker --ink as the rest of
   the page. Scoped to page-wide only; the alternating tone is left as-is on other pages. */
body.page-wide .engineering,
body.page-wide .testimonials{background:var(--ink);}

/* ===== 6-AXIS: eyebrow labels ("Built Around You" etc.) as a highlighter badge =====
   Matches the same white-on-orange highlighter treatment already used for emphasised words in
   headings, applied to the small .f-num labels above each feature row on this page. Switched to
   inline-block so the orange only wraps tightly around the label text, not the full row width. */
body.page-wide .f-num{
  display:inline-block; color:#fff; background:var(--orange); padding:.28em .6em;
}

/* ===== 6-AXIS: Process + FAQ side by side =====
   Two sections that used to stack full-height one after another now share one row, so the page
   doesn't spend two whole screens on "process" then "FAQs" back to back. New section (not reusing
   .process/.faq at the top level) so pages elsewhere that already use those classes are untouched
   — the .stepper and .faq-list components inside are the same shared pieces as before. */
.process-faq{padding:88px 0 48px; border-bottom:1px solid var(--line);}
.process-faq-grid{display:grid; grid-template-columns:1fr; gap:56px;}
@media(min-width:960px){ .process-faq-grid{grid-template-columns:1fr 1fr; gap:64px; align-items:start;} }
.process-faq-col .section-head{max-width:none;}
.process-faq-col .faq-list{max-width:none;}

/* Testimonials directly after the process/FAQ section (six-axis only) sit much closer to it —
   the section-head's own top margin still gives it room to breathe without the old ~170px gap. */
.process-faq + .testimonials{padding-top:32px;}

/* ===== RELATED PAGES (sitewide, near the foot of every interior page) =====
   A consistent "keep exploring" band of 2-4 link cards to logically related pages, sitting just
   above the final CTA strip / footer. Reuses the same card anatomy as .catalog-card/.rt-cell
   (dark tile, orange hover border, lift-on-hover) so it reads as part of the existing design
   language rather than a bolted-on nav component. Column count is set per-page via an inline
   --related-cols custom property (2, 3 or 4 cards) so a page with only two related links doesn't
   stretch into two awkward half-empty columns at desktop width. */
.related-pages{padding:76px 0 88px; border-top:1px solid var(--line); background:var(--ink2);}
.related-pages .section-head{margin-bottom:36px;}
/* --related-cols (above) only ever covered the desktop tier - several pages have 3 related links
   (or, on the EOAT page, one section with 5) without setting it, so at 1000px+ they still fell
   back to 4 columns and left the last row packed to the left with an empty gap; the 640-999px
   tablet tier was hardcoded to 2 columns regardless of --related-cols, so even pages that DID set
   it correctly for desktop still orphaned a 3rd or 5th card at tablet width. Flex-wrap centres a
   leftover row at every breakpoint regardless of item count, so this replaces both hardcoded grids
   rather than auditing --related-cols page by page - same fix pattern as the icon-strip/rt-grid/
   build-grid/montage-grid rows above. */
.related-grid{display:flex; flex-wrap:wrap; justify-content:center; gap:20px;}
.related-card{flex:0 1 100%;}
@media(min-width:640px){ .related-card{flex:0 1 calc((100% - 20px)/2);} }
@media(min-width:1000px){ .related-card{flex:0 1 calc((100% - 20px*(var(--related-cols,4) - 1))/var(--related-cols,4));} }
/* Redesign pass: these cards previously relied on --line for their border, which is fully
   transparent in dark mode (0 alpha by design) - so at rest they were plain flat rectangles with
   no edge definition at all, only gaining a visible border on hover. That read as flatter/plainer
   than the rest of the site. Two changes here, both CSS-only so every page using this shared
   component picks them up automatically without touching 12 pages' worth of markup: a soft
   always-visible top accent (explicit rgba orange, not --line, so it doesn't inherit that
   transparency) gives each card a resting anchor point instead of only "lighting up" on hover, and
   the kicker label becomes a small pill/chip instead of plain caps text, giving the top of each
   card a bit more visual structure. Hover keeps its existing lift + border brighten, plus a subtle
   shadow so the lift reads with a bit of depth instead of just a bare transform. */
.related-card{display:flex; flex-direction:column; justify-content:space-between; background:var(--ink); border:1px solid var(--line); border-top:2px solid rgba(255,93,29,.35); border-radius:var(--radius); padding:26px 24px; text-decoration:none; transition:border-color .15s ease, transform .15s ease, box-shadow .15s ease;}
.related-card:hover{border-color:var(--orange); transform:translateY(-3px); box-shadow:0 14px 28px -18px rgba(0,0,0,.55);}
.related-card .f-num{display:inline-block; font-size:10px; letter-spacing:.08em; background:rgba(255,93,29,.12); color:var(--orange); padding:4px 10px; border-radius:20px; margin-bottom:14px;}
.related-card h4{font-size:16px; font-weight:700; color:var(--text); margin-bottom:8px; line-height:1.3;}
.related-card p{font-size:13px; color:var(--text-dim); line-height:1.6; margin-bottom:0;}
.related-card .rc-arrow{margin-top:18px; display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:700; letter-spacing:.03em; text-transform:uppercase; color:var(--orange);}
.related-card .rc-arrow svg{width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2.4; transition:transform .15s ease;}
.related-card:hover .rc-arrow svg{transform:translateX(3px);}
@media(min-width:1300px){ body.page-wide .related-pages > .wrap{max-width:1560px;} }
@media(min-width:1300px){ #page-brick-tile .related-pages > .wrap{max-width:1760px;} }
