/* FOV — GENERATED by port.py. The block below is the design's own
 * <helmet> stylesheet, verbatim. Additions follow it. Do not hand-edit.
 */

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:#fff;color:#000;font-family:'Archivo','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:300;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:clip;-webkit-text-size-adjust:100%;text-size-adjust:100%;-webkit-tap-highlight-color:transparent}
a{color:#000;text-decoration:none;transition:color .3s ease,opacity .3s ease}
a:hover{color:#8a8a8a}
::selection{background:#000;color:#fff}
input,textarea,select,button{font:inherit;color:inherit;border-radius:0}
input:focus,textarea:focus,select:focus{outline:none;border-color:#000 !important}
select{appearance:none;-webkit-appearance:none}
textarea{resize:none}
input,select,textarea{color:inherit}
option{color:#000;background:#fff}
::placeholder{color:#6e6e6e}
@keyframes fovdrift{from{transform:scale(1.02) translate3d(0,0,0)}to{transform:scale(1.16) translate3d(-2%,-1.5%,0)}}
@keyframes fovbreathe{from{transform:scale(1.015) translate3d(0,0,0)}to{transform:scale(1.075) translate3d(-1.4%,-.9%,0)}}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
@media (max-width:900px){
[data-navlink]{display:none !important}
[data-header]>div{padding:13px clamp(18px,5vw,80px) !important}
[data-header] [data-btn]{padding:15px 18px 14px !important}
}
@media (max-width:430px){
[data-brandsub]{display:none !important}
}
@media (max-width:820px){
[data-stickycol]{display:none !important}
[data-stepindex]{display:flex !important}
[data-approach]{padding-top:clamp(190px,32vh,320px) !important;padding-bottom:clamp(70px,11vh,150px) !important}
[data-approach] [data-bandfade="top"]{height:clamp(180px,30vh,320px) !important}
[data-steps]{padding-top:0 !important;padding-bottom:0 !important;gap:clamp(46px,9vh,90px) !important}
[data-step]{min-height:0 !important}
[data-briefcol]{position:static !important}
}
@media (max-width:640px){
[data-bodypinwrap]{height:auto !important}
#problem{min-height:0 !important}
[data-gap]{flex:none !important;min-height:clamp(52px,8vh,96px) !important}
[data-bodypin]{position:static !important}
#top{padding-top:88px !important;padding-bottom:118px !important}
#top p{mix-blend-mode:normal !important}
[data-bodypin]{justify-content:center !important}
[data-bodyfocus]{font-size:16.5px !important;text-align:center !important}
[data-cycletext]{font-size:27px !important;text-align:center !important}
[data-svcink]{top:auto !important;bottom:0 !important;left:-30% !important;width:152% !important}
[data-rowinner]{gap:12px !important;padding:20px 14px !important}
[data-service] h3{font-size:26px !important}
[data-caps] p{font-size:16px !important}
[data-chip]{display:inline-flex !important;align-items:center !important;padding:0 16px !important;min-height:44px !important}
[data-form] [data-btn]{width:100% !important;text-align:center !important}
[data-footcol]{gap:0 !important}
[data-footcol] a{min-height:42px;display:flex;align-items:center}
}


/* Footer bottom bar: copyright left, company number right. When it wraps on a
   phone, the design's 20px gap splits them into two unrelated lines; 8px keeps
   them one block. */
@media (max-width: 640px) {
  footer div:has(> [data-companyno]) { row-gap: 8px !important; }
}

/* ---- hero intro: the aperture ------------------------------------------------
   FOV is a field of view, so the intro is one opening. All CSS, so it starts on
   the first frame rather than after the module boots (the design's JS rise flashed
   "FOV" before hiding it). Every keyframe runs FROM a hidden state TO the element's
   resting style with fill-mode backwards, so nothing can be left hidden: no
   stylesheet or reduced motion simply means no intro.
     100ms   F, O, V are cut in by a rising mask edge while drifting up, 90ms apart.
             The ink is still closed, so the difference blend shows them black.
     300ms   a 1px hairline draws out from the centre. Behind the letters the blend
             turns it white, so it reads as a cut through them.
     850ms   the hairline splits into a letterbox slit and opens to full bleed; the
             ink counter-zooms inside it like a lens settling. As it floods out
             behind the wordmark, the blend flips each letter from black to white.
     1700ms  the subtitle develops left to right through a feathered mask.
     1900ms  the dust drifts in.
   Rules this has already paid for:
   - never animate the ink's own opacity: an image first painted at opacity 0 is
     not counted as the largest paint, so LCP fell to the subtitle (2.0s -> 3.3s);
   - never animate the header: it is the first text painted, and fading it in
     pushed First Contentful Paint from 1.6s to 3.0s;
   - transforms, clip-path and masks only, so none of it is a layout shift. */
[data-herowordmark] [data-l] { display: inline-block; }

/* ---- Safari: difference blending over composited layers ----------------------
   WebKit drops mix-blend-mode on an element that is NOT composited when what sits
   behind it IS: the blend silently resolves as normal. The hero ink breathes and
   the dust canvas animates, so both get their own layers a moment after load, and
   the wordmark then turned solid white on iPhone ("loads correctly then after a
   few seconds loses the masking"). The services titles went pale over their ink
   the same way. Giving every blended element its own layer too restores it;
   reproduced and confirmed in the iOS Simulator. Chrome is unaffected either way.
   Both spellings: JS rewrites some of these style attributes, and Chrome
   reserializes them with a space. */
[style*="mix-blend-mode:difference"],
[style*="mix-blend-mode: difference"] { will-change: transform; }
#top::before {                       /* the hairline; at rest it is not there */
  content: ""; position: absolute; left: 0; right: 0; top: calc(50% + var(--fov-mid, 0px)); height: 1px;
  margin-top: -.5px; background: #000; opacity: 0; pointer-events: none;
}
#top > [aria-hidden]:first-child picture { display: block; width: 100%; height: 100%; }
@media (prefers-reduced-motion: no-preference) {
  [data-herowordmark] [data-l] { animation: fov-letter 1.05s cubic-bezier(.19,1,.22,1) .1s backwards; }
  [data-herowordmark] [data-l]:nth-child(2) { animation-delay: .19s; }
  [data-herowordmark] [data-l]:nth-child(3) { animation-delay: .28s; }
  #top::before                     { animation: fov-hairline 1.25s cubic-bezier(.65,0,.35,1) .3s backwards; }
  #top > [aria-hidden]:first-child { animation: fov-aperture 1.5s cubic-bezier(.76,0,.24,1) .85s backwards; }
  #top > [aria-hidden]:first-child picture { animation: fov-lens 2.6s cubic-bezier(.16,1,.3,1) .85s backwards; }
  [data-herowordmark]              { animation: fov-settle 2.2s cubic-bezier(.16,1,.3,1) .85s backwards; }
  [data-herosub]                   { animation: fov-develop 1.2s cubic-bezier(.33,0,.2,1) 1.7s backwards; }
  #top > [data-inkdust]            { animation: fov-fade 1.4s ease 1.9s backwards; }
}
/* The mask edge travels in the letter's own box while the box drifts up, so the
   edge outruns the glyph. Negative insets at the end so no stroke is ever shaved. */
@keyframes fov-letter {
  from { transform: translateY(24%); clip-path: inset(120% -12% -24% -12%); }
  to   { transform: none;            clip-path: inset(-24% -12% -24% -12%); }
}
@keyframes fov-hairline {
  0%   { transform: scaleX(0); opacity: 1; }
  45%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}
@keyframes fov-aperture {
  from { clip-path: inset(calc(50% + var(--fov-mid, 0px)) 0 calc(50% - var(--fov-mid, 0px)) 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes fov-lens { from { transform: scale(1.24); } }
@keyframes fov-settle { from { transform: scale(1.045); } }
@keyframes fov-develop {
  from { -webkit-mask-image: linear-gradient(90deg, #000 40%, transparent 60%);
         mask-image: linear-gradient(90deg, #000 40%, transparent 60%);
         -webkit-mask-size: 260% 100%; mask-size: 260% 100%;
         -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  to   { -webkit-mask-image: linear-gradient(90deg, #000 40%, transparent 60%);
         mask-image: linear-gradient(90deg, #000 40%, transparent 60%);
         -webkit-mask-size: 260% 100%; mask-size: 260% 100%;
         -webkit-mask-position: 0 0; mask-position: 0 0; }
}
@keyframes fov-fade { from { opacity: 0; } }
/* Splitting the letters into boxes loses the F-O and O-V kerning pairs: measured
   7.2px wider at 403px. Give it back, so the intro costs nothing at rest. */
[data-herowordmark] [data-l] + [data-l] { margin-left: -.009em; }

/* On phones the design sizes the wordmark to fill the measure by MEASURING it in
   JS. That measurement is font-dependent, so it resizes twice: once at boot against
   the fallback face, again when Archivo lands. Hidden behind the old JS rise, that
   never counted; with the intro visible from the first frame it cost CLS 0.156 on
   Lighthouse mobile. The fill is a fixed ratio of the font (text width = 1.9817 x
   font-size, flat from 320 to 640px), so state it in CSS and it is right before any
   script or font arrives. 1.982 rounds toward never overflowing. verify.mjs checks
   the ratio still fills the box, so a font or tracking change fails loudly. */
@media (max-width: 640px) {
  #top [data-fitbox] { container-type: inline-size; }
  #top [data-herowordmark] { font-size: calc(100cqw / 1.982) !important; }
}

/* ============================================================
   Added by port.py — not in the design's own <style> block
   ============================================================ */

/* intl-tel-input, themed for a black form.
   The library ships a light theme and exposes it entirely through custom properties,
   so this is variables plus a few rules for the input itself — not a rewrite of its
   stylesheet, which would break on the next version bump. #brief is #000. */
.iti {
  --iti-country-selector-bg: #0a0a0a;
  --iti-border-color: #262626;
  --iti-border-width: 1px;
  --iti-hover-color: rgba(255, 255, 255, .07);
  --iti-icon-color: #8a8a8a;
  --iti-spacer-horizontal: 12px;
  width: 100%;
}

/* The design's inputs are transparent with no border; the wrapper must not
   reintroduce one, and the tel input has to keep inheriting the form's type. */
.iti__tel-input,
.iti input[type="tel"] {
  width: 100%;
  border: none;
  background: transparent;
  padding: 0;
  font-family: inherit;
  font-size: 18px;
  font-weight: 300;
  letter-spacing: -.01em;
  color: #fff;
}

/* Square corners: everything else on this site sets border-radius:0. */
.iti__country-selector,
.iti__search-input { border-radius: 0; }

.iti__selected-dial-code {
  font-size: 18px;
  font-weight: 300;
  color: #fff;
}

.iti__search-input { color: #fff; font-size: 15px; padding-top: 12px; padding-bottom: 12px; }
.iti__search-input::placeholder { color: #6e6e6e; }
.iti__country { color: #fff; }
.iti__dial-code { color: #8a8a8a; }
.iti__country-list { scrollbar-width: thin; }

/* The country button is a real button and needs a visible focus ring, since the
   design's inputs rely on a border colour change this element does not have. */
.iti__selected-country:focus-visible,
.iti__selected-country-primary:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* The design has no skip link; the header is fixed and the hero is tall. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 130;
  background: #000;
  color: #fff;
  padding: 14px 22px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.skip-link:focus { left: 12px; top: 12px; color: #fff; }

/* Service rows are exposed as buttons, so they need a visible focus ring.
   The design gives them none because it never expected them to be focusable. */
[data-service]:focus-visible {
  outline: 2px solid #000;
  outline-offset: -3px;
}

/* Reduced motion: the JS already branches on it for everything it drives, but
   the two drift keyframes are set from markup/JS as inline animation, so they
   need stopping here too. The composed image must remain, not disappear. */
@media (prefers-reduced-motion: reduce) {
  [data-inkbreathe],
  [data-herowordmark],
  [data-wordmark] {
    animation: none !important;
    transform: none !important;
  }
}

/* ============================================================
   Phones — only what the design's own breakpoints do NOT cover.

   The design now ships four of them (900 / 820 / 640 / 430) and they are good:
   it hides the nav links, unpins the body and approach blocks, drops the hero
   subtitle's mix-blend-mode, gives the chips a 44px min-height, resizes the
   service type, and repositions the ink wash to the bottom on phones. All of
   that used to live here and has been deleted rather than duplicated —
   two rules setting the same property is how a re-port silently drifts.

   What is left is the three things the design still does not do:
     1. Form CONTROLS under the 44px touch floor. The design fixed the chips and
        the footer links; the inputs and selects are still ~20px tall, and a
        sub-16px font makes iOS zoom the page on focus.
     2. Mono labels at 9.5-10.5px. The design hides the brandmark subtitle below
        430px but the seven field labels, the chip heading, the reply note and
        the footer's three are still 10px.

   The services ink layer is deliberately NOT touched here. Its dark mass is the
   point: the service names are #fff in mix-blend-mode:difference, so a near-black
   backdrop renders them white and a near-white backdrop renders them black. This
   image is near-binary ink on white, so both ends are well served and only a
   narrow transition sits in the unreadable middle. An earlier pass here narrowed
   the box and dropped its opacity, which moved the whole layer into that middle,
   lost the drama, and — by pulling the box's right edge into the dark mass —
   exposed the mask's edge cut, which is invisible at the design's own width
   because there the edge falls on near-white pixels.

   Inline declarations outrank any plain selector, so anything competing with
   one needs !important. Documented repo rule, not decoration.
   ============================================================ */

@media (max-width: 720px) {
  /* 1. form controls */
  /* :not([aria-hidden]) keeps the honeypot at 1px — inflating a deliberately
     hidden input to 44px does nothing for a human and trips the tap-target audit. */
  [data-form] input:not([aria-hidden]),
  [data-form] select,
  [data-form] textarea {
    min-height: 44px !important;
    font-size: 17px !important;   /* under 16px and iOS zooms the page on focus */
  }
  [data-form] textarea { min-height: 96px !important; }
  [data-form] button[type="submit"] { min-height: 52px !important; }

  /* The design sets [data-footcol] a to 42px, which is just under the floor. */
  [data-footcol] a { min-height: 44px !important; }

  /* Links and the brand mark, which carry no padding of their own. */
  a[href^="mailto:"] { min-height: 44px; display: inline-flex; align-items: center; }
  [data-header] > div > a { min-height: 44px; align-items: center !important; }
  [data-header] [data-btn] { min-height: 44px; display: inline-flex !important; align-items: center; }
  .skip-link { min-height: 44px; display: inline-flex; align-items: center; }

  /* 1a. dead space around the "We handle everything" block.
     Measured on a phone: the cycler ends and then there is 196px of pure padding
     before the next section's headline — 93px under #problem plus 103px over
     #services — with a further 68px spacer inside the section itself. That reads
     as the page having run out rather than as breathing room, which is what was
     reported. Roughly halved, and the block is centred in what remains so the
     leftover space sits either side of the text instead of all below it. */
  #problem {
    padding-bottom: 44px !important;
    justify-content: center !important;
  }
  #problem [data-gap] { min-height: 30px !important; }
  #services { padding-top: 56px !important; }

  /* 1b. the services ink, on phones only.
     The design moves this layer to bottom:0, left:-30%, width:152% below 640px, so
     a dense photographic block sits directly behind the six service titles — and
     the layer also carries a scroll parallax, so the overlap MOVES as you scroll.
     The titles are #fff in mix-blend-mode:difference, which resolves cleanly
     against a near-white or near-black backdrop and turns to grey-on-grey in the
     middle. At full opacity the block sweeps the whole range past the text as you
     scroll, so some scroll position is always unreadable. Reported from a device.

     This is a bound, not a taste call. Composited over white, the backdrop cannot
     go below 255(1 - opacity). Difference-blended white text reads as black while
     the backdrop stays above ~175, so:

         255(1 - a) >= 175   ->   a <= 0.31

     At .28 the darkest possible backdrop is 184, so every title renders near-black
     at EVERY scroll position, and the ink stays as visible texture. It also scales
     the layer's own edge steps by the same factor, which is most of the hard line.
     Desktop is untouched at opacity 1 — there the mass is the point, it is measured
     at 0-1% unreadable, and verify.mjs asserts the density. */
  [data-svcink] { opacity: .28 !important; }

  /* 2. readable label type.
     The design's small mono labels are set inline, so the inline declaration IS
     the selector — precise, and it needs no list of containers to drift out of
     date. An enumerated version of this missed four of the eleven.
     BOTH spacings are required: any JS write through el.style.* reserializes the
     whole attribute and Chrome emits "font-size: 10px" WITH a space, silently
     un-matching the unspaced form. That is also why [data-btnlabel] is named
     explicitly — the header inversion rewrites its colour. A fresh load looked
     correct and only failed after that had run. */
  [style*="font-size:9.5px"],
  [style*="font-size: 9.5px"] { font-size: 11px !important; letter-spacing: .14em !important; }
  [style*="font-size:10px"],
  [style*="font-size: 10px"],
  [style*="font-size:10.5px"],
  [style*="font-size: 10.5px"],
  [data-btnlabel] { font-size: 11.5px !important; letter-spacing: .12em !important; }
}

/* Tablets are touch too, and the design's phone rules stop at 640px. */
@media (pointer: coarse) {
  [data-form] input:not([aria-hidden]),
  [data-form] select,
  [data-form] textarea { min-height: 44px !important; font-size: 17px !important; }
  [data-form] textarea { min-height: 96px !important; }
}

/* A tablet in portrait is 768px — past the design's 640px phone rules but still
   narrow enough that the ink crowds the titles. Same bound as above. */
@media (min-width: 641px) and (max-width: 900px) {
  [data-svcink] { opacity: .5 !important; }
}
