/*
Theme Name:  OveliThub Next
Theme URI:   https://www.ovelit.com/
Author:      OveliThub
Description: The OveliThub site theme. Content comes from the ovelithub-cms plugin through ovh_field(); SEO meta comes from SiteSEO. The design system lives in assets/system.css.
Version:     1.0.0
Requires PHP: 8.1
Text Domain: ovelithub-next
Tags:        custom-colors, custom-menu, translation-ready
*/

/* The system is loaded as a separate file so it stays legible and cacheable.
   This file exists for the theme header WordPress requires. */

/* Skip link: off-screen until focused. Not part of the shared system. */
.skip{position:absolute;left:-9999px;top:0;z-index:200;padding:12px 18px;
  background:var(--surface);color:var(--ink);border-radius:0 0 10px 0;font-weight:700}
.skip:focus{left:0}
.ovh-hp{position:absolute;left:-9999px}

/* Team member page header: photo beside the name from tablet up. Scoped to
   .person-lead, not .person -- .person is also the team CARD, and a grid rule
   there split every card into two columns. */
.person-lead{display:grid;gap:var(--s6);align-items:center}
@media (min-width:760px){.person-lead{grid-template-columns:minmax(0,240px) minmax(0,1fr)}}
.person-photo{margin:0}
.person-photo img{width:100%;height:auto;border-radius:var(--r-md);display:block}

/* Card titles are links, but should read as headings. */
.card h3{margin:0}
.card h3 a{color:inherit;text-decoration:none}
.card h3 a:hover{color:var(--blue)}

/* Review card head: where the review was left on the left, how it scored on
   the right. .quote-top and .stars already come from the system; only the
   source lockup is new. */
.quote-src{display:inline-flex;align-items:center;gap:8px;min-width:0;
  font-size:12.5px;font-weight:800;letter-spacing:.01em;color:var(--ink-2)}
.quote-src svg{flex:none;display:block;width:18px;height:18px}
.quote-src>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Attribution: the client's face, then their name over their role. */
.quote figcaption{display:flex;align-items:center;gap:12px;margin-top:auto;
  padding-top:var(--s4);border-top:1px solid var(--line)}
.quote-face{flex:none;width:44px;height:44px;border-radius:50%;overflow:hidden;
  display:grid;place-items:center;background:var(--grad);color:#fff}
.quote-face img{width:100%;height:100%;object-fit:cover;display:block;border-radius:50%}
.quote-face--text{font-weight:900;font-size:15px;letter-spacing:.02em}
.quote-who{display:flex;flex-direction:column;gap:1px;min-width:0}
.quote-who b{font-weight:800;font-size:15px;line-height:1.25}
.quote-who>span{color:var(--muted);font-size:13.5px;line-height:1.35}

/* Problem/solution lists: icon in the gutter, text aligned to it. */
.marks{display:grid;gap:var(--s3);list-style:none;margin:var(--s6) 0 0;padding:0}
.marks li{display:flex;gap:14px;align-items:flex-start}
.marks li svg{flex:none;margin-top:2px}
.split .btn-row{margin-top:var(--s7)}
.split .h2{margin:var(--s5) 0 0}

/* Brand lockup as an image. Height-capped so the nav bar keeps its rhythm. */
.brand--img{display:inline-flex;align-items:center;padding:0}
/* The lockup stacks a wordmark over a tagline, so it needs more height than a
   single-line mark to stay legible: 44px in a 73px nav bar, a little larger
   again in the footer where there is no bar to sit inside. */
.brand--img img{height:50px;width:auto;display:block}
.foot .brand--img img{height:60px}
@media (max-width:900px){.brand--img img{height:45px}.foot .brand--img img{height:52px}}
@media (max-width:600px){.brand--img img{height:40px}.foot .brand--img img{height:48px}}

/* Spacing corrections ------------------------------------------------
   The trust band sat too far from the hero above it and from the band
   below; the logo rail needed less air under the label. */
.trust{padding:var(--s6) 0}
.trust .logo-rail{margin-top:var(--s4)}
@media (max-width:900px){.trust{padding:var(--s5) 0}}

/* Need Help? stacks a label over a value, which made the column run
   taller than its neighbours. Tighter rows bring the baselines back
   into line with the plain link columns. */
.foot-help li{margin-bottom:var(--s4)}
.foot-help li:last-child{margin-bottom:0}
.foot-help .cap{display:block;font-size:11px;letter-spacing:.11em;
  text-transform:uppercase;margin-bottom:2px;line-height:1.3}
.foot-help li br{display:none}
.foot-help li>span:not(.cap),.foot-help li>a{font-size:14.5px;line-height:1.45}

/* Footer subscribe box.
   One pill rather than two controls: the field and the button share a single
   track, so the box reads as one object instead of a bordered input sitting
   next to a button. The track keeps the footer's own translucent surface --
   a white pill lit up brighter than anything else on a dark footer. Nothing
   wraps: the field shrinks (min-width:0) and the button holds its width,
   which is what keeps the pill intact in a narrow column. */
.foot-sub{margin:var(--s5) 0}
.foot-sub-label{font-size:14px;color:var(--muted);margin:0 0 var(--s3)}
.foot-sub-form{display:flex;align-items:center;flex-wrap:nowrap;gap:0;max-width:360px;
  background:rgba(255,255,255,.05);border:1px solid var(--line-strong);
  border-radius:var(--r-pill);padding:5px 5px 5px 6px;
  transition:border-color var(--t-fast) var(--ease-out),box-shadow var(--t-fast) var(--ease-out)}
.foot-sub-form:hover{border-color:var(--blue-lift)}
.foot-sub-form:focus-within{border-color:var(--blue-lift);box-shadow:0 0 0 3px rgba(2,105,208,.28)}
.foot-sub-form input{flex:1 1 auto;min-width:0;padding:10px 10px 10px 14px;border:0;
  background:transparent;color:#fff;font:inherit;font-size:14.5px;border-radius:var(--r-pill)}
.foot-sub-form input::placeholder{color:#7f93ad}
.foot-sub-form input:focus{outline:none}
.foot-sub-form .btn{flex:0 0 auto;border-radius:var(--r-pill);white-space:nowrap}
@media (max-width:400px){.foot-sub-form .btn{padding-inline:var(--s4)}}
.foot-sub-ok,.foot-sub-err{display:flex;align-items:center;gap:8px;font-size:14px;margin:0}
.foot-sub-ok{color:var(--ok)}
.foot-sub-err{color:var(--pink-text)}
.screen-reader-text{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* Team portrait with a real photo. The system's .portrait is a gradient disc
   sized for initials; a 300x250 photo dropped into it overflowed the card, so
   the photo is cropped to the disc instead. Faces sit above centre in a
   portrait crop, hence the raised focal point. */
.portrait--photo{background:none;box-shadow:0 14px 30px rgba(2,105,208,.22)}
.portrait--photo img{width:100%;height:100%;display:block;border-radius:50%;
  object-fit:cover;object-position:center 22%}

/* Blog card thumbnail: edge to edge inside the media box on every screen.
   Without this the image sat at its natural size on the gradient plate. */
.post-media--img{background:var(--navy-2,#0b1b33)}
.post-media--img::before{display:none}
.post-media img{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;border-radius:inherit}
.post-media--img span{position:absolute;left:50%;bottom:12px;transform:translateX(-50%)}

/* Hero panel charts. The shapes carry no claim of their own, so they are
   allowed to draw themselves in once and then hold still. */
.ovh-chart .ovh-line{stroke-dasharray:520;stroke-dashoffset:520;
  animation:ovhDraw 1.7s cubic-bezier(.22,.8,.3,1) .2s forwards}
.ovh-chart .ovh-area,.ovh-chart .ovh-prev,.ovh-chart .ovh-guide,
.ovh-chart .ovh-ticks,.ovh-donut .ovh-donut-ticks{opacity:0;animation:ovhIn .8s ease forwards}
.ovh-chart .ovh-ticks{animation-delay:.35s}
.ovh-chart .ovh-prev{animation-delay:.55s}
.ovh-chart .ovh-area{animation-delay:.9s}
.ovh-chart .ovh-guide{animation-delay:1.5s}
.ovh-donut .ovh-donut-ticks{animation-delay:.2s}
.ovh-chart .ovh-dots circle{opacity:0;transform-box:fill-box;transform-origin:center;
  animation:ovhPop .45s cubic-bezier(.2,.9,.3,1.4) forwards}
.ovh-chart .ovh-dots circle:nth-child(1){animation-delay:.75s}
.ovh-chart .ovh-dots circle:nth-child(2){animation-delay:1s}
.ovh-chart .ovh-dots circle:nth-child(3){animation-delay:1.25s}
.ovh-chart .ovh-pulse{transform-box:fill-box;transform-origin:center;
  animation:ovhPulse 2.8s ease-in-out 1.6s infinite}
.ovh-donut .ovh-ring{transform-box:fill-box;transform-origin:center;
  animation:ovhRing 1.1s cubic-bezier(.22,.8,.3,1) .3s both}
@keyframes ovhDraw{to{stroke-dashoffset:0}}
@keyframes ovhIn{to{opacity:1}}
@keyframes ovhPop{from{opacity:0;transform:scale(.3)}to{opacity:1;transform:scale(1)}}
@keyframes ovhPulse{0%,100%{transform:scale(1);opacity:.22}50%{transform:scale(1.55);opacity:.06}}
@keyframes ovhRing{from{opacity:0;transform:rotate(-28deg) scale(.86)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .ovh-chart .ovh-area,.ovh-chart .ovh-prev,.ovh-chart .ovh-guide,.ovh-chart .ovh-ticks,
  .ovh-chart .ovh-dots circle,.ovh-donut .ovh-ring,.ovh-donut .ovh-donut-ticks{
    animation:none;opacity:1;transform:none}
  .ovh-chart .ovh-line{animation:none;stroke-dashoffset:0}
  .ovh-chart .ovh-pulse{animation:none;opacity:.22}
}

/* Bios differ in length; the profile link should still line up across the row. */
.card.person{display:flex;flex-direction:column}
.card.person>.link{margin-top:auto;align-self:center;padding-top:var(--s5)}

/* Line legend for the trend chart: same rhythm as the donut legend. */
.legend--line .ln{width:16px;height:3px;border-radius:2px;display:inline-block}
.legend--line .ln--now{background:linear-gradient(90deg,#4DA3F0,#A374F0 60%,#FF3D7F)}
.legend--line .ln--prev{background:repeating-linear-gradient(90deg,rgba(147,166,191,.75) 0 3px,transparent 3px 6px)}

/* Closing CTA -------------------------------------------------------------
   'What happens next' is a real sequence, so it is numbered and joined by a
   rule rather than repeating the same tick three times. The heading was
   capped at 17ch, which left a dead gap between it and the note card on wide
   screens; a longer measure lets it fill its own column. */
@media (min-width:1100px){.cta h2{max-width:24ch}}
.cta-steps{list-style:none;margin:0;padding:0;display:grid;gap:var(--s4)}
.cta-steps li{position:relative;display:flex;gap:14px;align-items:flex-start;
  font-size:15px;line-height:1.5;color:#D6E4F5}
.cta-steps li:not(:last-child)::before{content:"";position:absolute;left:13px;top:30px;
  bottom:calc(-1 * var(--s4));width:1px;
  background:linear-gradient(180deg,rgba(255,255,255,.24),rgba(255,255,255,.04))}
.cta-step{flex:none;width:27px;height:27px;border-radius:50%;display:grid;place-items:center;
  font-size:12.5px;font-weight:800;color:#fff;background:rgba(255,255,255,.09);
  border:1px solid rgba(255,255,255,.2);font-variant-numeric:tabular-nums}
.cta-steps li:first-child .cta-step{background:var(--grad);border-color:transparent;
  box-shadow:0 6px 16px rgba(248,10,98,.28)}

/* Theme switch: the three-way control competes with the logo and the menu
   button for a narrow bar, so it steps down twice before the sheet opens.
   Kept above a 30px tap target rather than shrunk to an icon. */
@media (max-width:900px){
  .theme-switch{padding:2px}
  .theme-switch button{padding:6px 10px;font-size:11.5px}
}
@media (max-width:600px){
  .theme-switch{gap:1px}
  .theme-switch button{padding:6px 9px;font-size:11px}
}

/* Back to top. Sits clear of the footer corner, appears only once there is
   something to scroll back from, and is unreachable by keyboard while off. */
.to-top{position:fixed;right:clamp(16px,2.4vw,32px);bottom:clamp(16px,2.4vw,32px);z-index:60;
  width:46px;height:46px;border-radius:50%;display:grid;place-items:center;cursor:pointer;padding:0;
  color:var(--ink-2);background:var(--surface);border:1px solid var(--line-strong);box-shadow:var(--e2);
  opacity:0;visibility:hidden;transform:translateY(12px);
  transition:opacity var(--t-base) var(--ease-out),transform var(--t-base) var(--ease-out),
             visibility var(--t-base) var(--ease-out),background var(--t-fast) var(--ease-out),
             color var(--t-fast) var(--ease-out),border-color var(--t-fast) var(--ease-out)}
.to-top.is-on{opacity:1;visibility:visible;transform:none}
.to-top:hover{background:var(--grad);color:#fff;border-color:transparent;transform:translateY(-3px)}
.to-top:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
@media (max-width:600px){.to-top{width:42px;height:42px}}
@media (prefers-reduced-motion:reduce){.to-top{transition:opacity .01s,visibility .01s}
  .to-top,.to-top:hover{transform:none}}

/* A result coming back from the form handler, shown at the top of the body
   the answer belongs to. */
.form-alert{margin:0;padding:12px 15px;border-radius:12px;font-size:14.5px;line-height:1.45;
  color:var(--pink-text);background:rgba(248,10,98,.08);border:1px solid rgba(248,10,98,.28)}
.consent span{flex:1}

/* Hero headline ----------------------------------------------------------
   The display size hit its 4.5rem cap at about 1290px and stayed there, while
   the column it sits in keeps getting narrower: a 1280px laptop wrapped the
   headline onto six lines. Scaling with the viewport instead holds it to four
   lines from laptop to ultrawide, and still lands on exactly 72px at 1920,
   which is the width the approved design was drawn at. */
@media (min-width:941px){
  .hero .display{font-size:clamp(2.6rem,3.75vw,4.5rem);text-wrap:balance}
}

/* Light theme ground -------------------------------------------------------
   The stock light palette sits pure-white cards on an almost-white ground,
   which reads as glare across a large screen. Every light surface steps down
   a little and keeps the same blue bias, with slightly stronger lines and
   shadows so cards still lift off the page. Plain :root on purpose: the dark
   blocks in the system are :root:not([data-theme="light"]) and
   :root[data-theme="dark"], both more specific, so dark mode is untouched. */
:root{
  --ground:#E9EFF7;
  --surface:#F8FBFE;
  --surface-2:#DFE9F5;
  --line:rgba(2,105,208,.16);
  --line-strong:rgba(2,105,208,.30);
  --e1:0 1px 2px rgba(6,21,40,.06), 0 2px 8px rgba(6,21,40,.05);
  --e2:0 6px 18px rgba(6,21,40,.09), 0 2px 6px rgba(6,21,40,.05);
  --e3:0 26px 60px rgba(6,21,40,.16), 0 8px 20px rgba(6,21,40,.07);
  --logo-op:.68;
}

/* The trust band stays dark in both themes, so the hero and the client logos
   read as one block instead of the strip switching to a light plate under a
   navy hero. The logos are masked shapes painted with --ink, and the label
   uses --muted, so the band redefines those two tokens for its own subtree
   rather than restating every rule. */
.trust{background:var(--navy);border-bottom-color:rgba(148,190,240,.16);
  --ink:#E8F0FA;--ink-2:#B9C9DD;--muted:#93A6BF;--logo-op:.76;
  --line-strong:rgba(148,190,240,.30)}
.trust .lg:hover{background:#7CC0FF}

/* .band--surface and .card both painted themselves with --surface, so on an
   alternating band the cards had the same fill as the band behind them and
   only a hairline told them apart. In light mode the band now takes its own
   deeper tone and the cards keep the lighter one. Dark mode keeps the value
   it had, declared through all three theme states so the toggle works both
   ways. */
:root{--band-alt:#DFE9F5}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--band-alt:var(--surface)}}
:root[data-theme="dark"]{--band-alt:var(--surface)}
.band--surface{background:var(--band-alt)}

/* Keep the real menu on laptops ------------------------------------------
   The system folds the nav into the mobile sheet below 1300px, which was sized
   for a text wordmark. With the image logo and the header CTA the bar is wider,
   but it still fits comfortably above 1080px -- a 1280 or 1366 laptop was
   getting a hamburger for no reason. This restores the desktop nav for that
   band by putting back the base declarations the 1300px block overrides.
   .nav-sheet returns to display:contents, which makes it generate no box at
   all, so its fixed positioning goes with it -- visibility still inherits,
   hence the explicit reset. */
@media (min-width:1080px) and (max-width:1300px){
  .nav{backdrop-filter:blur(14px);background:rgba(7,23,43,.92)}
  .nav .wrap{gap:var(--s4);min-height:72px;flex-wrap:nowrap;position:static}
  .nav-toggle{display:none}
  .theme-switch{margin-left:0}
  .nav-sheet{display:contents;visibility:visible}
  .nav-extra,.nav-more,.nav-sheet .nd{display:none}
  .nav-sheet ul{display:flex;align-items:center;gap:14px;margin-left:auto;flex-wrap:nowrap;
    padding:0;background:none;overflow:visible;counter-reset:none}
  .nav-sheet li{border-bottom:0;animation:none}
  /* :not(.nav-cta) matters -- .btn--primary paints its gradient with
     background, so resetting background on every link in the row flattened the
     header button to a plain dark pill. */
  .nav-sheet li:not(.nav-cta) a{display:inline;padding:0;border-radius:0;color:#B7CBE2;
    background:none;column-gap:0;white-space:nowrap}
  .nav-sheet li:not(.nav-cta) a::before,.nav-sheet li:not(.nav-cta) a::after{content:none}
  .nav-sheet .nl{display:inline;font-size:14px;font-weight:600;letter-spacing:normal;line-height:inherit}
  .nav-sheet li:not(.nav-cta) a:hover{background:none;color:#fff}
  .nav-sheet li:not(.nav-cta) a:hover .nl{color:#fff}
  .nav-sheet li:not(.nav-cta) a[aria-current]{background:none}
  .nav-sheet li a[aria-current] .nl{color:#fff}
  .nav-sheet .nav-cta{margin-top:0;margin-left:var(--s2)}
  .nav-sheet .nav-cta a.btn{display:inline-flex;align-items:center;justify-content:center;
    padding:11px 18px;font-size:13.5px;border-radius:var(--r-pill);color:#fff;white-space:nowrap}
  .brand--img img{height:42px}
}

/* Modal on small screens ---------------------------------------------------
   Below 560px the system gives the dialog height:100% and removes the body's
   max-height, but dialog.modal is overflow:hidden and nothing else scrolls --
   so on a phone the form could not be scrolled at all and the submit button
   was unreachable. The dialog becomes a flex column instead: head and foot
   hold their size, the body takes the rest and scrolls. The [open] guard
   matters -- an unconditional display would override the UA rule that keeps a
   closed dialog hidden. */
@media (max-width:560px){
  dialog.modal[open]{display:flex;flex-direction:column}
  .modal-head,.modal-foot{flex:0 0 auto}
  .modal-body{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding:var(--s5);overscroll-behavior:contain}
  .modal-ok{flex:1 1 auto;overflow-y:auto}
  .modal-foot{flex-wrap:wrap;gap:var(--s3);padding:var(--s4) var(--s5)
    calc(var(--s4) + env(safe-area-inset-bottom,0px))}
  .modal-foot .btn{width:100%;justify-content:center}
  .f2{grid-template-columns:1fr}
}

/* Mobile menu position ------------------------------------------------------
   The sheet is placed with top:calc(var(--probar) + var(--navh,66px)). Neither
   variable was ever defined -- an undefined var in a calc makes the whole
   declaration invalid, so top fell back to auto and the open menu sat over the
   header instead of below it. These are the defaults; app.js then keeps --navh
   equal to the real bar height. --probar is the reading-progress bar the
   prototype had and this theme does not, hence 0. */
:root{--probar:0px;--navh:66px}

/* Service cards: the tagline sits above the title as a caption, the way the
   team cards put a role above a name. */
/* Sentence case on purpose: these taglines run to a full sentence on some
   services, and uppercase with letter-spacing turns a sentence into a wall. */
.card:not(.card--ask) .cap{display:block;margin-bottom:6px;font-size:13px;font-weight:700;
  line-height:1.35;letter-spacing:0;text-transform:none;color:var(--ink-2)}
.card .cap + h3{margin-top:0}
.card--ask .cap{display:block;margin-bottom:6px}
.card .ic{margin-bottom:var(--s5)}

/* The approved FAQ summary carries a number, but the system sheet never styled
   .acc-num, so it rendered at question weight and read as part of the sentence.
   Same treatment as the other numeric markers on the page (.symptom-i, .ed-num). */
.acc-num{flex:none; min-width:1.2em; font-size:11px; font-weight:800; letter-spacing:.1em;
  color:var(--blue); margin-top:7px; font-variant-numeric:tabular-nums}

/* Case-study card screenshots.
   .case-media is a gradient panel with a 34px grid drawn by ::before and a
   centred browser mock. A record with a featured image shows the image instead:
   the grid comes off (it reads as a defect over a photograph) and becomes a
   bottom scrim so the industry pill stays legible over a light shot. */
.case-media .case-shot{position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; z-index:1; display:block}
.case-media:has(.case-shot)::before{z-index:2; background-image:none;
  background:linear-gradient(180deg,rgba(6,21,40,0) 48%,rgba(6,21,40,.62))}
.case-media b{z-index:3}

/* The record's opening band takes the same screenshot as its card.
   Same reasoning as .case-media: the 34px grid reads as a defect over a
   photograph, so where a shot is present it becomes an even scrim instead --
   even rather than bottom-weighted here, because this label pill sits in the
   middle of the band rather than in a corner. */
.figure-band .case-shot{position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; z-index:1; display:block}
.figure-band:has(.case-shot)::before{z-index:2; background-image:none;
  background:linear-gradient(180deg,rgba(6,21,40,.18),rgba(6,21,40,.5))}
.figure-band span{z-index:3}

/* Team member contact row.
   .socials is styled for the dark footer -- light text on a translucent white
   ground. On a light card that reads as an empty box, so the card variant takes
   its colours from the theme tokens instead and sits a size smaller. */
.socials--card{justify-content:center; gap:8px; margin-top:var(--s4)}
.socials--card a{width:34px; height:34px; border-radius:10px; color:var(--muted);
  background:var(--surface-2); border:1px solid var(--line)}
.socials--card a:hover{background:var(--grad); color:#fff; border-color:transparent}

/* --------------------------------------------------------------------------
   Two-card grids. The system sheet defines .g-3 and .g-4 but never .g-2, so
   every block that happened to render exactly two cards -- Mission & Vision
   among them -- fell back to one column and stacked.
   -------------------------------------------------------------------------- */
/* min(320px,100%) rather than a bare 320px: auto-fit still gives two columns
   wherever they fit, but a 320px track can no longer be wider than the column it
   sits in, which is what pushed a 320px-wide phone sideways. */
.g-2{grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr))}

/* Mission & Vision: the same card, given the weight a two-card band can carry. */
.card--duo{padding:var(--s7);
  background:radial-gradient(120% 110% at 100% 0%,rgba(122,63,208,.10),transparent 58%),
             radial-gradient(90% 90% at 0% 100%,rgba(2,105,208,.08),transparent 60%),
             var(--surface)}
.card--duo::before{content:"";position:absolute;inset:0 0 auto;height:3px;background:var(--grad)}
.card--duo .ic{width:58px;height:58px;border-radius:18px}
.card--duo .ic svg{width:27px;height:27px}
.card--duo h3{font-size:clamp(1.2rem,1.7vw,1.42rem);letter-spacing:-.02em}
.card--duo p{font-size:16px;color:var(--ink-2);margin-top:var(--s4);line-height:1.65}

/* Member hero portrait with a real photo. .spot-face is a gradient disc sized
   for initials; the photo rendered at its natural size and spilled square over
   the ring, so it is cropped to the disc the way the team card already is. */
.spot-face--photo{padding:3px;overflow:hidden;background:var(--grad);
  box-shadow:0 22px 48px rgba(2,105,208,.38)}
.spot-face--photo img{width:100%;height:100%;display:block;border-radius:50%;
  object-fit:cover;object-position:center 22%}
.spot-face--photo::after{content:"";position:absolute;inset:3px;border-radius:50%;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22)}

/* ...and the ring around it is the card's hairline, not a heavy rotating band,
   so the two portraits read as the same component at two sizes. */
.spot-ring{inset:-9%;border-radius:50%;background:none;animation:none;
  -webkit-mask:none;mask:none;border:1px dashed rgba(255,255,255,.30)}
.spot-glow{inset:-34%}

/* Nothing is pinned beside the portrait any more, so it stands on its own and is
   sized up to carry the column. */
.spotlight{--spot-d:clamp(160px,19vw,256px)}
.spot-face{width:var(--spot-d);font-size:clamp(44px,5.2vw,72px)}
/* Stacked, the portrait leads and the column centres under it. The portrait's
   caption goes, because the role eyebrow and the h1 directly beneath it say the
   same two things; so does the breadcrumb, which on a phone costs a whole row
   for a trail the back button already covers. The BreadcrumbList in the page's
   JSON-LD is untouched, so the search result keeps its trail either way. */
@media (max-width:600px){
  .phero-split{gap:var(--s5)}
  .phero-split .spotlight{order:-1;padding-top:0}
  .phero-split .spotlight figcaption{display:none}
  .phero-split>div{text-align:center}
  .phero-split h1,.phero-split .lead{margin-inline:auto}
  .phero-split .btn-row,.phero-split .socials{justify-content:center}
}

/* CEO brief on the about page. */
.split-media--ceo{grid-template-columns:minmax(220px,340px) minmax(0,1fr);align-items:center}
.split-media--ceo p+p{margin-top:var(--s4)}
.ceo-frame{position:relative;width:100%;max-width:340px;justify-self:start}
@media (max-width:900px){.ceo-frame{justify-self:center}}
.ceo-orb{position:absolute;z-index:0;left:-19%;top:-17%;width:70%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(2,105,208,.34),rgba(248,10,98,.16) 55%,transparent 72%);
  filter:blur(4px);pointer-events:none}
.ceo-grid{position:absolute;z-index:0;right:-20px;bottom:-20px;width:64%;height:58%;
  border-radius:var(--r-md);opacity:.75;pointer-events:none;
  background-image:linear-gradient(var(--line-strong) 1px,transparent 1px),
                   linear-gradient(90deg,var(--line-strong) 1px,transparent 1px);
  background-size:20px 20px;
  -webkit-mask:linear-gradient(135deg,transparent 34%,#000);
  mask:linear-gradient(135deg,transparent 34%,#000)}
/* The same 3px gradient rim the hero portrait carries, so the two faces on the
   site read as one treatment. */
.ceo-plate{position:relative;z-index:1;padding:3px;border-radius:calc(var(--r-lg) + 3px);
  background:var(--grad);box-shadow:0 18px 40px rgba(2,105,208,.20)}
@media (max-width:900px){.split-media--ceo{grid-template-columns:1fr}}
/* The source portraits are 300x250, so the plate is kept square and capped
   rather than stretched to a tall 4:5 crop that would upscale them to mush. */
.ceo-shot{position:relative;margin:0;aspect-ratio:1/1;width:100%;
  border-radius:var(--r-lg);overflow:hidden;
  background:linear-gradient(150deg,#0B2647,#0269D0 55%,#7A3FD0);isolation:isolate}
.ceo-shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 18%;display:block}
.ceo-initials{position:absolute;inset:0;display:grid;place-items:center;color:#fff;
  font-size:clamp(46px,6vw,72px);font-weight:900;letter-spacing:-.04em}
.ceo-shot figcaption{position:absolute;left:0;right:0;bottom:0;z-index:2;color:#fff;
  padding:var(--s8) var(--s5) var(--s5);
  background:linear-gradient(180deg,rgba(6,21,40,0),rgba(6,21,40,.55) 45%,rgba(6,21,40,.9))}
.ceo-shot figcaption b{display:block;font-size:1.08rem;font-weight:800;letter-spacing:-.02em}
.ceo-shot figcaption span{display:block;font-size:13px;color:#9CC8F5;margin-top:5px}
.ceo-quote{position:relative;margin-top:var(--s6);padding-left:var(--s5);color:var(--ink);
  font-size:clamp(1.02rem,1.4vw,1.14rem);font-weight:600;line-height:1.6}
.ceo-quote::before{content:"";position:absolute;left:0;top:5px;bottom:5px;width:3px;
  border-radius:3px;background:var(--grad)}

/* Contact sidebar tiles. The prototype styles each one inline; the same values,
   as classes, so the editor never has to hand-write a style attribute. */
.ctile{display:flex;gap:13px;align-items:flex-start}
.ctile .ic{width:38px;height:38px;border-radius:11px;margin:0;flex:none}
.ctile .ic svg{width:17px;height:17px}
.ctile b{display:block;font-size:11px;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);margin-bottom:4px}
.ctile a{font-weight:600;text-decoration:none;font-size:15px;color:var(--ink)}
.ctile a:hover{color:var(--blue)}
.ctile .ctile-value{font-weight:600;font-size:15px;color:var(--ink)}
.ctile .small{margin-top:4px}


/* ---------- standard packages (campaign page) ---------------------------
   The offer card already carries the badge, checklist, meta list and full-width
   button a pricing tier needs, so a package reuses it rather than inventing a
   second card. What it adds is the price line, and a three-column grid: with
   twelve packages in an auto-fit grid the tiers of one service would wrap into
   the next service's row, which reads as one twelve-tier ladder instead of four
   ladders of three. */
.offers--pack{grid-template-columns:repeat(auto-fit,minmax(min(100%,280px),1fr))}
@media (min-width:1000px){.offers--pack{grid-template-columns:repeat(3,minmax(0,1fr))}}
.offer--pack .offer-top{margin-bottom:calc(var(--s3) * -1)}
.pack-price{display:flex; align-items:baseline; gap:9px; flex-wrap:wrap;
  margin-top:var(--s2) !important; padding-bottom:var(--s3)}
.pack-price b{font-size:clamp(25px,2.8vw,31px); font-weight:800; line-height:1;
  letter-spacing:-.02em; color:var(--ink)}
.pack-price span{font-size:13px; font-weight:700; letter-spacing:.02em; color:var(--muted)}
.offer--pack p{margin-top:0}
.pack-note{margin-top:var(--s6); font-size:14px; line-height:1.65; color:var(--muted);
  text-align:center; max-width:62ch; margin-inline:auto}
.pack-empty{margin-top:var(--s6); text-align:center; color:var(--muted)}


/* ---------- packages filter bar ----------------------------------------
   .toolbar puts the count left and pushes the filter right with margin-left
   auto, which works for the blog's three short category names and breaks for
   five service names -- the last button ran off the edge and collided with the
   count. Under a centred heading the filter reads better as a centred pill
   group with the count as its caption. */
.packbar{display:flex; flex-direction:column; align-items:center; gap:var(--s4);
  margin:calc(var(--s7) * .8) 0 0}
.packbar .seg2{display:flex; flex-wrap:wrap; justify-content:center; gap:4px;
  padding:5px; border:1px solid var(--line); border-radius:var(--r-pill);
  background:var(--surface-2); max-width:100%}
.packbar .seg2 button{font:inherit; font-size:13.5px; font-weight:700; cursor:pointer;
  background:transparent; border:1px solid transparent; color:var(--ink-2);
  border-radius:var(--r-pill); padding:9px 18px; white-space:nowrap;
  transition:all var(--t-fast) var(--ease-out)}
.packbar .seg2 button:hover{color:var(--ink); background:var(--surface)}
.packbar .seg2 button[aria-pressed="true"]{background:var(--grad); color:#fff;
  border-color:transparent; box-shadow:0 4px 14px rgba(2,105,208,.30)}
.packbar-count{font-size:13.5px; color:var(--muted); letter-spacing:.01em}
.packbar-count b{color:var(--ink); font-weight:800}
@media (max-width:560px){.packbar .seg2 button{padding:8px 13px; font-size:12.5px}}

/* ---------- article extras ---------------------------------------------- */
/* The record hero already carries the title, the lead and the meta line, so a
   16/8 figure under it pushed the first paragraph below the fold. */
.figure-band--post{aspect-ratio:16/6}
@media (max-width:700px){.figure-band--post{aspect-ratio:16/8}}

.amet .avatar--photo{overflow:hidden; padding:0; display:block; flex:none}
.amet .avatar--photo img{width:100%; height:100%; border-radius:50%; object-fit:cover;
  object-position:center 22%; display:block}
.amet b a{color:inherit; text-decoration:none}
.amet b a:hover{color:var(--blue)}
.share.is-copied{border-color:var(--blue); color:var(--blue)}
.authorcard .portrait--shot{overflow:hidden; border-radius:50%}
.authorcard .portrait--shot::after{inset:-5px}
.authorcard .portrait--shot img{width:100%; height:100%; border-radius:50%;
  object-fit:cover; object-position:center 22%; display:block}

/* wp_link_pages() nests its links, so the row has to be laid out on the
   wrapper as well as on the nav. */
.pager .nav-links{display:flex; align-items:center; justify-content:center;
  gap:8px; flex-wrap:wrap}
.pager .screen-reader-text{position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; padding:0;
  background:none; min-width:0}

/* A blog featured image is a designed banner -- it carries its own headline,
   logo and artwork -- not a photograph that survives a crop. Cropping it to
   the band's ratio cut the logo off the top, and the centred category pill
   landed in the middle of the artwork. On a post the band therefore takes the
   image's own proportions and the pill moves out to a corner. Case study
   records keep the cropped treatment: those are photographs, and there the
   even band is the point. */
.figure-band--shot{aspect-ratio:auto; display:flex; justify-content:center;
  background:var(--surface-2)}
.figure-band--shot::before{display:none}
/* width:auto rather than 100%: an undersized upload then sits centred and
   sharp on the tinted band instead of being stretched into a blur, which
   also makes it obvious which posts still need a proper banner. */
.figure-band--shot .case-shot{position:static; inset:auto; width:auto; height:auto;
  max-width:100%; object-fit:contain; display:block}
.figure-band--shot span{position:absolute; left:var(--s4); bottom:var(--s4); z-index:3}

/* ---------- Contact Form 7 inside the design -----------------------------
   CF7 wraps every control in a span, which would become a grid item in
   .field (display:grid) and leave the input at its intrinsic width. Making
   the wrapper a block hands the row back to the input. Everything else here
   is CF7's own furniture -- validation tips, the response line, the spinner
   -- given the design's type and colour. */
.wpcf7-form-control-wrap{display:block}
.field .wpcf7-form-control{width:100%}
.wpcf7-not-valid-tip{margin-top:5px;font-size:12px;font-weight:600;color:var(--pink-text)}
.field input.wpcf7-not-valid,.field select.wpcf7-not-valid,.field textarea.wpcf7-not-valid{border-color:var(--pink-text)}
.wpcf7-response-output{margin:var(--s5) 0 0;padding:12px 15px;border:1px solid var(--line);
  border-radius:var(--r-md);font-size:14px;line-height:1.55;color:var(--ink-2);background:var(--surface)}
.wpcf7 form.invalid .wpcf7-response-output,.wpcf7 form.unaccepted .wpcf7-response-output{border-color:var(--pink-text);color:var(--pink-text)}
.wpcf7 form.sent .wpcf7-response-output{border-color:var(--ok);color:var(--ok)}
.wpcf7-spinner{margin-left:10px;vertical-align:middle}
.wpcf7 .ajax-loader{vertical-align:middle}
/* The modal keeps its submit in the footer bar, pointing at the form by id,
   so CF7's own button would be a second one. */
dialog.modal .ovh-cf7-submit{display:none}
dialog.modal .wpcf7-response-output{margin-bottom:var(--s5)}
/* The subscribe pill is a flex row, so CF7's wrapper has to flex too. */
.foot-sub-form .wpcf7-form-control-wrap{flex:1 1 auto;min-width:0}
.foot-sub-form .wpcf7-response-output{flex:1 0 100%;margin:var(--s3) 0 0;background:transparent;border:0;padding:0}
.foot-sub-form .wpcf7-spinner{flex:none}

/* Header mega menu -----------------------------------------------------------
   Any top-level menu item that has children in Appearance > Menus grows a
   panel. Nothing below names Services, so adding, renaming, reordering or
   removing items in the admin changes the menu and no template or rule has to
   follow. The desktop bar starts at 1080px (see the band further up), so the
   hover panel and the mobile accordion split at exactly that width. */
.nav-sub-toggle{display:none}
.nav-caret{flex:none}

@media (min-width:1080px){
  /* the panel is measured against the header row, never against one link, so a
     wide panel cannot be pushed off the right edge by a late menu item */
  .nav .wrap{position:relative}
  .nav-sheet li.has-sub{align-self:stretch;display:flex;align-items:center}
  .nav-sheet li.has-sub>a{display:inline-flex;align-items:center;gap:5px}
  .nav-sheet li.has-sub>a .nav-caret{opacity:.7;transition:transform var(--t-fast) var(--ease-out)}
  .nav-sheet li.has-sub:hover>a .nav-caret,
  .nav-sheet li.has-sub:focus-within>a .nav-caret{opacity:1;transform:rotate(180deg)}

  .nav .mega{position:absolute;left:50%;top:100%;width:min(880px,calc(100vw - 48px));z-index:70;
    padding-top:10px;opacity:0;visibility:hidden;pointer-events:none;
    transform:translateX(-50%) translateY(6px);
    transition:opacity var(--t-base) var(--ease-out),transform var(--t-base) var(--ease-out),
               visibility 0s linear var(--t-base)}
  .nav-sheet li.has-sub:hover>.mega,
  .nav-sheet li.has-sub:focus-within>.mega,
  .nav-sheet li.has-sub.open>.mega{opacity:1;visibility:visible;pointer-events:auto;
    transform:translateX(-50%) translateY(0);
    transition:opacity var(--t-base) var(--ease-out),transform var(--t-base) var(--ease-out),visibility 0s}

  .nav .mega-in{display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,2fr);gap:var(--s6);
    padding:var(--s6);border-radius:20px;
    background:radial-gradient(120% 120% at 0% 0%,rgba(2,105,208,.28),transparent 58%),#0A1B31;
    border:1px solid rgba(255,255,255,.13);
    box-shadow:0 34px 70px -28px rgba(0,0,0,.8)}
  .nav .mega-aside{display:flex;flex-direction:column;align-items:flex-start;
    padding-right:var(--s5);border-right:1px solid rgba(255,255,255,.09)}
  .nav .mega-eyebrow{font-size:10.5px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;
    color:#7FA8D8;margin:0}
  .nav .mega-note{margin:10px 0 0;font-size:13px;line-height:1.5;color:#9DB4CE}
  .nav .mega-all{display:inline-flex;align-items:center;gap:7px;margin-top:auto;padding-top:var(--s4);
    font-size:13.5px;font-weight:700;color:#fff;text-decoration:none}
  .nav .mega-all svg{transition:transform var(--t-fast) var(--ease-out)}
  .nav .mega-all:hover svg{transform:translateX(4px)}

  .nav .mega-list{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:0;padding:0;
    counter-reset:none;list-style:none}
  .nav .mega-list li{border-bottom:0;animation:none}
  .nav .mega-list li a{display:block;padding:12px 14px;border-radius:13px;color:#EAF2FC;
    text-decoration:none;background:transparent;
    transition:background var(--t-fast) var(--ease-out)}
  .nav .mega-list li a::before,.nav .mega-list li a::after{content:none;counter-increment:none}
  .nav .mega-list li a:hover,.nav .mega-list li a:focus-visible{background:rgba(255,255,255,.07)}
  .nav .mega-list li a[aria-current]{background:linear-gradient(90deg,rgba(2,105,208,.32),rgba(248,10,98,.16) 75%,transparent)}
  .nav .mega-list .mg-t{display:block;font-size:14.5px;font-weight:700;letter-spacing:-.01em;color:#fff}
  .nav .mega-list .mg-d{display:block;margin-top:3px;font-size:12.5px;line-height:1.45;color:#93AAC6}
}
@media (min-width:1080px) and (prefers-reduced-motion:reduce){
  .nav .mega{transition:none}
}

/* --- the sheet on phones and tablets --- */
@media (max-width:1079px){
  /* the chevron used to sit on every row, which read as though each one opened
     something; only a row that actually holds pages carries one now */
  .nav-sheet li a::after{content:none}
  .nav-sheet li.has-sub{position:relative}
  .nav-sheet li.has-sub>a{padding-right:56px}
  .nav-sheet li.has-sub>a .nav-caret{display:none}

  .nav-sub-toggle{display:grid;place-items:center;position:absolute;top:10px;right:2px;
    width:40px;height:40px;padding:0;cursor:pointer;color:#9DB4CE;
    border:1px solid rgba(255,255,255,.14);border-radius:12px;background:rgba(255,255,255,.06);
    transition:color var(--t-fast) var(--ease-out),border-color var(--t-fast) var(--ease-out)}
  .nav-sub-toggle .nav-caret{width:14px;height:14px;transition:transform var(--t-fast) var(--ease-out)}
  .nav-sub-toggle[aria-expanded="true"]{color:#fff;border-color:rgba(255,110,156,.55)}
  .nav-sub-toggle[aria-expanded="true"] .nav-caret{transform:rotate(180deg)}

  .nav .mega{display:none}
  .nav-sheet li.has-sub.open>.mega{display:block}
  .nav .mega-in{display:block;padding:0 0 var(--s4) 34px}
  .nav .mega-aside{display:none}
  .nav .mega-list{display:grid;gap:2px;margin:0;padding:0;counter-reset:none;list-style:none}
  .nav .mega-list li{border-bottom:0}
  .nav .mega-list li a{display:block;padding:11px 13px;border-radius:12px;color:#EAF2FC;
    background:rgba(255,255,255,.04);text-decoration:none}
  .nav .mega-list li a::before,.nav .mega-list li a::after{content:none;counter-increment:none}
  .nav .mega-list li a[aria-current]{background:linear-gradient(90deg,rgba(2,105,208,.30),rgba(248,10,98,.14) 75%,transparent)}
  .nav .mega-list .mg-t{display:block;font-size:15px;font-weight:700;color:#fff}
  .nav .mega-list .mg-d{display:block;margin-top:2px;font-size:12px;line-height:1.4;color:#8AA4C4}
}

/* The hero panel's two illustrative charts are decoration; on a phone they cost
   a screenful above the fold and shrink to the point of being unreadable, so the
   card keeps its figures and drops the drawings. */
@media (max-width:640px){
  .hero .panel-charts{display:none}
}

/* The menu row wraps in a flex line of its own, so a link only ever grows as
   tall as its text. Stretching the list to the bar's height removes the strip of
   dead space a pointer crosses on its way down to the panel -- crossing it left
   both the item and the panel, and the panel shut before it could be reached. */
@media (min-width:1080px){
  .nav-sheet ul{align-self:stretch}
}

/* The staggered entrance is for the sheet opening, not for a panel expanded
   later: replaying it left the service rows blank for a beat after the tap. */
@media (max-width:1079px){
  .nav.open .nav-sheet .mega-list li{animation:none}
}

/* Contact Form 7 renders every text input with size="40", which is an intrinsic
   width of about 376px. Inside .field the width:100% above already overrides it,
   but the footer subscribe pill has no .field wrapper, so on a 360px phone that
   one input stuck 41px past the viewport -- and because the footer is on every
   page, every page could be dragged sideways. min-width:0 matters as much as the
   width: a flex item refuses to shrink below its intrinsic size without it. */
.wpcf7 input[type="text"],.wpcf7 input[type="email"],.wpcf7 input[type="url"],
.wpcf7 input[type="tel"],.wpcf7 input[type="number"],.wpcf7 input[type="date"],
.wpcf7 select,.wpcf7 textarea{max-width:100%;min-width:0}
.foot-sub-form{position:relative}
/* Only the field, never the button: width:100% on every control stretched the
   Subscribe button across the whole pill. */
.foot-sub-form .wpcf7-form-control-wrap{flex:1 1 0%;min-width:0}
.foot-sub-form .wpcf7-form-control-wrap .wpcf7-form-control{width:100%;min-width:0}
/* The spinner is always in the DOM and only toggles visibility, so in a flex row
   it reserved 34px next to the button and pushed the pill past the screen. Lift
   it out of the flow and it still shows while a subscription is in flight. */
.foot-sub-form .wpcf7-spinner{position:absolute;top:50%;right:10px;margin:0;
  transform:translateY(-50%);pointer-events:none}

/* The grid motif behind the CEO portrait is hung 20px outside its frame. On a
   phone the frame already fills the column, so those 20px were the last of the
   page's sideways drift; tucked in, the about page stops overflowing. */
@media (max-width:600px){
  .ceo-grid{right:0;bottom:-14px}
}
