/* =============================================================================
   VOCTOS BLOG — base
   tokens · the type scale · the motion set · masthead · folder tabs · the card
   · the close · the footer

   EVERY VALUE IN THE TOKEN BLOCK WAS MEASURED ON THE LIVE AGENCY SITE with
   getComputedStyle, not chosen here. That is what makes this a second site in
   one identity rather than a site that resembles it:

     ink        #0d172d      page  #eff0f4     rule  #e3e6ee
     hero band  linear-gradient(180deg,#091122 0%,#182748 88.24%,transparent)
     lime       #9bb725      lime accent gradient
                linear-gradient(180deg,#7B9412,#D4EE69 44.02%,#7B9412 88.04%)
     purple     #7616f4      lilac #BF79FF
     container  1350 / padding-inline 25
     radii      30 cards · 19 panels and buttons · 15 tags · 14 thumbnails
     body       16 / 300 / lh 24, Unbounded — the site ships no second face
     WEIGHT CEILING 400. Nothing in the live site's content runs above it, so
     nothing here does either. There is no bold anywhere in this theme.
============================================================================= */

*, *::before, *::after { box-sizing: border-box; }

body.vb {
  /* ── colour ──────────────────────────────────────────────── */
  --ink:#0d172d;
  --night:#0f172a;
  --page:#eff0f4;
  --muted:#5b6675;
  --faint:#878b95;
  --rule:#e3e6ee;
  --lime:#9bb725;
  --limesoft:#c6e36b;
  --limeink:#7e9c0a;
  --purple:#7616f4;
  --lilac:#BF79FF;
  /* the agency footer's second rule weight (their `border-fifth`): the fine
     rows are divided by this, the structural ones by --purple */
  --foot-rule:#37425b;
  /* the band where the page dissolves into the footer — one value, read by
     .vb-news's bottom padding and by .vb-foot::before's height */
  --foot-fade:118px;

  /* the desk in scope. Sections and <body> overwrite these inline from
     voctos_blog_desk_vars(), which is why this stylesheet contains not one
     desk-specific selector. */
  --desk:var(--ink);
  --desk-ink:#fff;
  --acc:var(--ink);
  --tint:#e7e9f0;
  --tone:var(--muted);

  --ease:cubic-bezier(.22,.7,.2,1);

  /* ── the type scale: six steps, nothing off-ladder ─────────
     Sizes drift the moment they are written per-component — 15.5 here, 17
     there, 19, 21, 30, 40 — and that drift is exactly what makes a page feel
     assembled rather than set. Six tokens, and every heading in the theme
     resolves to one of them.
       t1  the lead headline, and the case figures
       t2  a desk word · a full-bleed feature
       t3  EVERY section heading
       t4  a feature headline inside a section
       t5  a list item that is a question or a step
       t6  an item title — the live site's own card title is 16/20        */
  --t1:clamp(34px,5vw,62px);
  --t2:clamp(32px,4.4vw,56px);
  --t3:clamp(26px,2.6vw,34px);
  --t4:clamp(20px,2vw,26px);
  --t5:18px;
  --t6:16px;

  margin:0;
  font-family:'Unbounded', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size:16px;
  font-weight:300;
  line-height:24px;
  color:var(--ink);
  background:var(--page);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* THE RESETS CARRY ZERO SPECIFICITY — the WHOLE selector is inside :where(),
   not just its argument list.

   `body.vb :where(a)` looks like a zero-weight rule and is not: :where()
   zeroes what is INSIDE it, and `body.vb` outside it still scores 0-1-1.
   That beat every single-class rule in the theme, with two visible results:
   `.vb-tab { color:var(--desk-ink) }` (0-1-0) lost to `color:inherit`, so
   every folder tab printed its label in ink on its own colour; and
   `margin:0` on p/h2/h3 beat `.vb-deck { margin-top:20px }`, `.vb-rec-lede`
   and a dozen others, so the vertical rhythm collapsed wherever a rule used
   one class on a text element.

   Wrapped like this each reset is 0-0-0 and anything at all overrides it. */
:where(body.vb a) { color:inherit; text-decoration:none; }
:where(body.vb h1, body.vb h2, body.vb h3, body.vb h4, body.vb h5, body.vb h6) {
  font-weight:400;
}
:where(body.vb h1, body.vb h2, body.vb h3, body.vb h4, body.vb h5, body.vb h6,
       body.vb p, body.vb ul, body.vb ol, body.vb dl, body.vb figure,
       body.vb blockquote) { margin:0; padding:0; }
:where(body.vb .vb-plain) { list-style:none; }
:where(body.vb img) { max-width:100%; height:auto; }
:where(body.vb :focus-visible) { outline:2px solid var(--purple); outline-offset:3px; }

.vb-w { max-width:1350px; margin-inline:auto; padding-inline:25px; }

/* Screen-reader only, without pulling in a framework for one rule.
   font-size is pinned as well as the box: a visually hidden <h2> otherwise
   keeps the browser's own 1.5em heading size, which is off this page's
   six-step ladder and would flash at that size in the instant before the
   stylesheet applies. */
/* `.screen-reader-text` IS WORDPRESS'S NAME FOR THIS, AND IT WAS MISSING.
   The theme only ever styled its own `.vb-sr`, so anything CORE marked as
   screen-reader-only printed at full size. `the_posts_pagination()` wraps its
   `screen_reader_text` in `<h2 class="screen-reader-text">`, which is why
   every archive carried a heading reading "Archive pages" above the page
   numbers — not a stray string in a template, a class the stylesheet had
   never heard of.

   Core and plugins emit this class in a dozen places — search forms, comment
   navigation, "Posted in", skip links — so the fix belongs on the class, not
   on the pagination call. Both selectors share one rule so they can never
   drift apart. */
.vb-sr,
.screen-reader-text { position:absolute; width:1px; height:1px; margin:-1px; padding:0;
         overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
         font-size:var(--t6); line-height:1; }

/* a skip link has to be reachable when it is focused, or it is decoration */
.vb-sr:focus,
.screen-reader-text:focus { position:fixed; top:12px; inset-inline-start:12px;
         width:auto; height:auto; margin:0; padding:10px 16px; clip-path:none;
         z-index:1000; background:#fff; color:var(--ink); border-radius:10px;
         box-shadow:0 6px 24px rgba(0,0,0,.18); font-size:14px; line-height:1.2; }

/* the bracketed placeholder token: `[اسم الكاتب]`.
   ONLY THE ARABIC IS ISOLATED. Put the brackets inside the isolate and they
   mirror to `]…[`, because the isolate makes the Arabic its own bidi
   paragraph and the neutral brackets adopt that paragraph's direction. */
/* `isolate` and no `direction`, which is why this rule needed no change when
   the ask panel's token went from Arabic to English: it isolates whatever is
   inside it from the surrounding bidi run without forcing a direction, so it
   is correct for an English token in an English sentence and still correct
   for the Arabic one the writer cards fall back to. A `direction:rtl` here
   would have had to be unpicked. */
.vb-tok { unicode-bidi:isolate; }

/* =============================================================================
   THE MOTION SET — scroll-driven, no JavaScript

   The theme's doctrine, inherited from the agency build: everything is styled
   FINISHED and the keyframes run it backwards. A browser with no scroll
   timeline shows the completed page; a failed script cannot leave the site
   blank, because there is no script. Nothing is ever hidden by default, which
   is the opposite of the usual opacity:0 + .is-visible pattern.

   RANGES ARE LENGTHS, NEVER PERCENTAGES. A percentage range is measured
   against the element's OWN height, so a tall section sits half-faded across
   hundreds of pixels of scroll — visible as a ghosted panel in the middle of
   the viewport. A length range completes after a fixed distance whatever the
   element's height.
============================================================================= */
@keyframes vb-rise   { from { opacity:0; translate:0 26px; } }
@keyframes vb-settle { from { scale:1.06; } }
@keyframes vb-slide  { to   { translate:-50% 0; } }
@keyframes vb-mast   { to   { background:rgba(9,17,34,.92); backdrop-filter:blur(10px);
                              padding-block:11px; } }
@keyframes vb-wipe   { from { clip-path:inset(0 100% 0 0); } }

/* the count-up needs a registered custom property and a counter, and that is
   the whole mechanism — no script, and the number is real text in the DOM */
@property --vb-n { syntax:'<integer>'; initial-value:0; inherits:false; }
@keyframes vb-count { from { --vb-n:0; } }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* ── THE ENTRY RISE IS GONE ──────────────────────────────────────────
       `.vb-in` used to run `vb-rise` on a view() timeline — every card, row
       and section faded up from 26px as it entered the viewport, staggered
       across six children.

       IT WAS READ AS A FAULT, NOT AS MOTION, and the reason is what a
       scroll-driven timeline does when the reader stops scrolling: the
       animation stops with it. Mid-range, a card is parked at partial
       opacity and part-way through its rise — so a grid of four sits at four
       different heights with one of them nearly blank, which is exactly what
       a half-loaded page looks like. A load animation finishes whatever the
       reader does; this one only finished if they kept scrolling.

       The keyframes stay defined — `.vbf-slab-in > *` still uses vb-rise, and
       `vb-count`, `vb-wipe`, `vb-mast` and the marquee are untouched, because
       none of them can strand content half-drawn: they animate a number, a
       rule's clip-path, the masthead's own background, and a loop.

       `.vb-in` and `.vb-stagger` remain in the markup on purpose. They are on
       dozens of elements across a dozen templates, and they are what the
       `animation:none` overrides further down this file switch off; removing
       the classes would mean editing every template to gain nothing. With no
       rule here they simply cost nothing, and one rule brings the motion back
       if it is ever wanted. */

    .vb-mast { animation:vb-mast .01s linear both;
               animation-timeline:scroll(root); animation-range:0 120px; }
    /* NOTHING ANIMATES IN THE HERO ANY MORE, on purpose.
       The lead used to run a 1.6s Ken Burns settle on its background image
       and a four-step staggered rise on the type. The agency's own hero does
       neither: it is a still panel that is finished the moment it paints.
       Motion here also cost the page its LCP — the largest element on the
       front page spent the first second and a half arriving.
       `vb-settle` and `vb-rise` are both still defined and still used: rise
       by every `.vb-in` further down the page, settle by nothing now.

       The slab frame used to borrow `.vb-lead > *` for its own stagger. It
       gets its own rule rather than the shared one, because `.vb-stagger`
       only supplies the RANGES — the animation itself rides on `.vb-in`, and
       the slab's children are inline elements with no class to hang it on.
       Scroll-driven is also the right timeline for it: the slab sits
       mid-page, where a load animation has always finished before the
       reader arrives. */
    .vbf-slab-in > * { animation:vb-rise .01s var(--ease) both;
                       animation-timeline:view(); }
    .vb-track { animation:vb-slide 30s linear infinite; }
    .vb-count { animation:vb-count .01s linear both;
                animation-timeline:view(); animation-range:entry 40px entry 420px; }
    .vb-wipe  { animation:vb-wipe .01s linear both;
                animation-timeline:view(); animation-range:entry 0px entry 340px; }
  }
}

/* The target lives on the ELEMENT, and the keyframes have only a `from`.
   Declaring the number inside the keyframes was the bug that printed 0 with
   motion disabled: style it finished, animate it backwards. */
.vb-count { counter-reset:vbn var(--vb-n); }
.vb-count::after { content:counter(vbn); }

/* =============================================================================
   MASTHEAD
============================================================================= */
/* EVERY VALUE HERE IS READ OUT OF THE AGENCY THEME, not guessed off a
   screenshot — firmwork/blocks/header.tpl.html and header.php:

     height       lg:h-[76px] / h-12 with lg:py-5 / py-2.5  → 76px and 49px
     nav link     text-xs (12px), white, hover → secondary #9bb725
     the bar      before: bg-secondary rounded-sm h-1 inset-x-0 top-[-27px],
                  scale 0 → 1 on hover. A 4px LIME BAR 27px ABOVE the link,
                  not an underline under it — which is the one detail that
                  makes this header recognisable at a glance.
     phone        text-xs white, the country code in secondary
     Request a Call  bg-primary #7616f4 · rounded-rc-2 (19) · py-2.5 px-5 ·
                  text-xs · hover bg-third
     social       w-9 h-9 (36px) · rounded-xl (12px) · #71A3FF · #00BB7E ·
                  and the meeting button at #9bb725
============================================================================= */
.vb-mast { position:fixed; inset-inline:0; top:0; z-index:40; padding-block:10px;
           background:transparent; }
/* 15, NOT 20, AND THE LOGO IS WHY. voctos.com's bar is 76px: 20 + 20 of
   padding around a 36px control. Ours now carries a 46px logo — the size that
   makes the MARK match theirs, because our file has 11-15% transparent
   padding baked in and theirs has under 3% (the arithmetic is on the logo
   rule above). 20 + 20 + 46 came to 86, so the bar grew by ten.

   15 + 15 + 46 is 76 again. The reference's bar is 76 at every scroll
   position — measured at scrollY 0 and 600, it does not condense — so this is
   the number that has to hold. */
@media (min-width:1024px){ .vb-mast { padding-block:15px; } }

/* THE ADMIN BAR IS 32px OF FIXED CHROME AND THE MASTHEAD IS FIXED TOO.
   Both at top:0 means the logo sits UNDER the admin bar for every logged-in
   user — which is every editor, on every page, every time they look at their
   own site. WordPress adds `admin-bar` to <body> precisely so a theme can
   offset for this; the 782px breakpoint is WordPress's own, where the bar
   grows to 46px. */
body.admin-bar .vb-mast { top:32px; }
@media screen and (max-width:782px){ body.admin-bar .vb-mast { top:46px; } }
/* BUT NOT ON A PHONE. Below 601px WordPress switches #wpadminbar to
   `position:absolute`, so the bar scrolls away with the document instead of
   staying pinned. The 46px offset above then reserves space for something
   that is not there any more, and an editor sees a permanent 46px strip of
   article showing ABOVE their own masthead. The bar overlaps the first 46px
   of the page at scroll 0 and is gone the moment you scroll, which is what
   WordPress itself intends at this width. */
@media screen and (max-width:600px){ body.admin-bar .vb-mast { top:0; } }
/* ── TRANSPARENT AT REST, SOLID ON SCROLL ─────────────────────────────────────
   This used to be `body.vb:not(.vb-is-front) .vb-mast { background:ink }` —
   every page except the front one got a solid masthead always, because an
   inner page has no dark lead behind it and white nav on a light-grey page is
   unreadable.

   The agency solves that without a solid bar: its header is transparent and
   the page carries a 134px black-to-transparent gradient at 60% opacity
   underneath it (`.vb-fade` below is that gradient, to its own measured
   values). So the masthead can be transparent everywhere.

   Where this DIVERGES from the agency, deliberately: their header is
   `position:absolute` and scrolls away for good — there is no sticky bar and
   no background-on-scroll anywhere on voctos.com. This one stays fixed and
   takes a solid background once the gradient has scrolled past, so the nav is
   still reachable half way down a 12-minute read.

   Two mechanisms, same end state: `.is-stuck` from assets/js/mast.js for
   every browser, and the scroll-timeline animation in the motion block for
   browsers that support it, which covers the no-JavaScript case. */
.vb-mast.is-stuck { background:rgba(9,17,34,.92); backdrop-filter:blur(10px);
                    padding-block:11px; }

/* the gradient the transparent masthead reads against, at the top of the
   document, scrolling away exactly as the agency's does */
.vb-fade { position:absolute; inset-inline:0; top:0; height:134px; z-index:1;
           opacity:.6; pointer-events:none;
           background:linear-gradient(180deg,#000 0%,rgba(137,137,137,0) 100%); }
body.admin-bar .vb-fade { top:32px; }
@media screen and (max-width:782px){ body.admin-bar .vb-fade { top:46px; } }
@media screen and (max-width:600px){ body.admin-bar .vb-fade { top:0; } }
.vb-mast-in { display:flex; align-items:center; justify-content:space-between;
              gap:8px; min-width:0; min-height:28px; }
@media (min-width:1024px){ .vb-mast-in { gap:20px; min-height:36px; } }

/* ── THE MOBILE BAR: BURGER LEFT · MARK CENTRED · CTA RIGHT ──────────────────
   The mark is centred against the VIEWPORT, not against what is left over
   between the two controls — which is why it is taken out of the flow rather
   than given `margin:auto`. The burger and the CTA are different widths, so
   an auto-margin logo lands off-centre by half that difference, and a mark
   that is nearly centred looks like a mistake in a way that an obviously
   left-aligned one does not.

   There is room for this: at 375px the burger ends at 64 and the CTA starts
   around 270, so a mark up to ~165px wide clears both. The 36px height below
   puts it at about 140. */
@media (max-width:1023px){
  .vb-mast-in { position:relative; min-height:44px; }
  .vb-burger { position:absolute; inset-inline-start:0; top:50%; translate:0 -50%;
               margin-inline-start:0; }
  /* SCOPED TO THE MASTHEAD, and it has to be. `.vb-logo` is also the FOOTER's
     mark; written unscoped, this rule took the footer logo out of the flow
     too and it printed on top of the phone and WhatsApp lines. */
  .vb-mast-in > .vb-logo,
  .vb-mast-in > .custom-logo-link { position:absolute; left:50%; translate:-50% 0; }
  [dir="rtl"] .vb-mast-in > .vb-logo,
  [dir="rtl"] .vb-mast-in > .custom-logo-link { left:auto; right:50%; translate:50% 0; }
  /* the CTA is then the only thing left in the row, and it sits at the end */
  .vb-mast-end { margin-inline-start:auto; }

  /* the agency's own hairline under its mobile bar, slate-300 at 20% */
  .vb-mast { border-bottom:1px solid rgba(203,213,225,.2); }
  /* an open menu makes the bar solid, exactly as the agency's does — a
     transparent bar over a full-screen panel shows the article through the
     one strip the reader is meant to be reading the menu from */
  /* `animation:none` IS LOAD-BEARING, and this is the subtlest thing in the
     file. The masthead runs a scroll-driven animation whose end state carries
     `backdrop-filter: blur(10px)`. A non-none backdrop-filter makes an element
     a CONTAINING BLOCK for `position:fixed` descendants — so the panel's
     `bottom:0` stopped meaning "the bottom of the screen" and started meaning
     "the bottom of the header", and the menu rendered 54px tall with the
     article showing through underneath it.

     Stopping the animation is what removes it: the blur is applied BY the
     animation, and an animated value beats a normal declaration in the
     cascade, so setting `backdrop-filter:none` here alone would lose.
     `animation-name` is not itself animated, so this rule does win.

     Anything added to .vb-mast later that creates a containing block —
     transform, filter, perspective, contain:paint, will-change on any of
     them — breaks the panel the same way. */
  .vb-mast.is-open { animation:none; background:#0d172d;
                     -webkit-backdrop-filter:none; backdrop-filter:none;
                     border-bottom-color:transparent; }
}

.vb-logo { flex:none; font-size:18px; letter-spacing:.06em; color:#fff; white-space:nowrap; }
@media (min-width:1024px){ .vb-logo { font-size:20px; } }
.vb-logo i { font-style:normal; color:var(--lime); }
/* the mark fills the 34px slot and never grows past it, whatever aspect the
   uploaded file has — a tall logo would otherwise push the masthead open */
/* `.custom-logo-link` IS LISTED BESIDE `.vb-logo` THROUGHOUT, as a belt to the
   PHP filter's braces. functions.php adds .vb-logo to the Customizer logo's
   markup, which is the tidy fix and makes every rule here apply; these extra
   selectors mean the SIZING still holds if that filter ever stops matching,
   because an unsized logo is not a small blemish — measured on voctos.net it
   rendered at 320x132 and opened the masthead to 132px, which is the entire
   "the home page is broken" report. */
.vb-logo img, .vb-logo picture,
.custom-logo-link img { display:block; }
.vb-logo img,
.custom-logo-link img { height:28px; width:auto; max-width:200px; object-fit:contain; }
/* 40 on a phone, 34 on a desktop — the mark is bigger on the SMALLER screen,
   which looks backwards written down and is right on the page: centred in the
   mobile bar it is the only thing identifying the publication, with no nav
   beside it to share the work. It has the room — at 375px the burger ends at
   46 and the CTA starts at 254, and 40px of height is about 93px of width. */
@media (max-width:1023px){ .vb-mast-in .vb-logo img,
                           .vb-mast-in .custom-logo-link img { height:40px; } }

/* THE PILL CANNOT REACH THE LOGO.
   The logo is absolutely centred on a phone, so nothing reflows around it:
   a CTA that grows simply covers it. At 375px the space to the right of the
   logo is about 116px, so the pill is capped below that and clips rather
   than overlaps. This is a guard, not the fix — the label itself is one
   word — but it means a future edit to that label cannot break the
   masthead silently. */
@media (max-width:520px){
  .vb-mast .vb-cta { max-width:104px; overflow:hidden; text-overflow:ellipsis;
                     white-space:nowrap; }
}
/* ── THE MASTHEAD MARK IS SIZED TO ITS INK, LIKE THE FOOTER'S ────────────────
   34px was this theme's own number and voctos.com's is 36, but neither is the
   right one to copy, because the two sites are not using the same file. The
   ink measurement is written out on the footer rule below; the short version
   is that our PNG carries 11-15% transparent padding on every side and theirs
   carries under 3%.

   At the old 34px box our visible mark was 34 x 101/132 = 26.0px against
   their 36 x 107/110 = 35.0px — a quarter smaller in a bar that is the same
   76px tall on both sites. 46px puts ours at 35.2px, which is theirs to within
   a fifth of a pixel, and matches the footer so the two marks on one page are
   finally the same size as each other.

   IT STILL FITS. The bar is 76px and the row is 46 + 15 above and below. */
@media (min-width:1024px){ .vb-logo img,
                           .custom-logo-link img { height:46px; } }
/* the footer's own height lives with the rest of the footer, below */

/* ── THE MENU: A PANEL BELOW 1024, THE INLINE ROW ABOVE IT ───────────────────
   The nav was an `overflow-x:auto` strip squeezed between the logo and the
   CTA. On a 400px phone that left roughly 100px for six desks plus Research,
   so the masthead read "SEO  GEO & AEO  Tec" and everything after it sat
   behind a scrollbar that is invisible by design. A menu that does not tell
   you it has more items is not a menu, so below 1024 it becomes a panel.

   `display:contents` above 1024 is what lets ONE <ul> serve both: the wrapper
   stops generating a box entirely and the list goes back to being a direct
   flex child of the masthead, with the exact rules it had before. Nothing is
   duplicated in the markup, so the two can never drift apart. */
.vb-menu { display:contents; }

@media (max-width:1023px){
  .vb-menu {
    display:block;
    /* FULL SCREEN BELOW THE BAR, not a dropdown. `bottom:0` rather than a
       max-height: the agency's own panel is
       `fixed ... h-[calc(100%-48px)] inset-x-0 bottom-0`, and a panel that
       ends half way down leaves the article showing through underneath it,
       which is what made the open menu read as a floating card. */
    position:fixed; inset-inline:0; top:var(--vb-mast-h,49px); bottom:0;
    overflow-y:auto; overscroll-behavior:contain;
    padding:14px 20px 40px;
    /* the agency's own menu gradient, #0D172D → #3C465D, top to bottom */
    background:linear-gradient(180deg,#0d172d 0%,#3c465d 100%);
    /* OPENS AND CLOSES INSTANTLY, WITH NO FADE, and that is a fix rather than
       a saving. A full-screen panel that fades in is semi-transparent for the
       length of the fade, so for about a fifth of a second the article shows
       straight through the menu — which is what a screenshot taken during the
       open actually catches, and it reads as a panel that failed to paint.
       The agency's own menu toggles with x-show and no transition at all, for
       the same reason. `display` stays block so the panel keeps its box.

       `visibility` is what takes the links out of the tab order while it is
       closed — `opacity:0` alone leaves every one of them focusable behind a
       panel nobody can see. */
    visibility:hidden;
  }
  .vb-mast.is-open .vb-menu { visibility:visible; }
}

/* THE BURGER. Hidden above 1024 with `display:none`, which also takes it out
   of the tab order there — a control for a panel that cannot open should not
   be reachable at all. */
/* 44, not 38: 38 is what the icon wants to look like and 44 is the smallest
   target a thumb reliably hits. The bars stay 19px wide — the extra six
   pixels are padding, so the button grows without the icon changing. */
.vb-burger { display:inline-flex; align-items:center; justify-content:center; flex:none;
             width:44px; height:44px; margin-inline-start:2px; padding:0;
             background:none; border:0; border-radius:11px; color:#fff; cursor:pointer;
             transition:background .2s var(--ease); }
.vb-burger:hover { background:rgba(255,255,255,.10); }
.vb-burger:focus-visible { outline:2px solid var(--lime); outline-offset:2px; }
/* the agency's own mark: three 1px rules, 28px wide, 4px apart. Heavier bars
   read as a UI control from another site next to this typography. */
.vb-burger > span { display:grid; gap:4px; width:28px; }
.vb-burger i { display:block; height:1px; background:currentColor;
               transition:translate .22s var(--ease), rotate .22s var(--ease),
                          opacity .15s var(--ease); }
/* the three bars become an X: the outer two meet in the middle and cross, the
   middle one goes. 5px is the real distance to the centre with these bars —
   1px tall on a 4px gap puts the outer centres exactly 5px out. */
.vb-mast.is-open .vb-burger i:nth-child(1) { translate:0 5px; rotate:45deg; }
.vb-mast.is-open .vb-burger i:nth-child(2) { opacity:0; }
.vb-mast.is-open .vb-burger i:nth-child(3) { translate:0 -5px; rotate:-45deg; }
@media (prefers-reduced-motion:reduce){ .vb-burger i { transition:none; } }
@media (min-width:1024px){ .vb-burger { display:none; } }

/* what the masthead drops below 1024, returned inside the panel */
.vb-menu-x { display:none; }
@media (max-width:1023px){
  .vb-menu-x { display:flex; flex-wrap:wrap; align-items:center; gap:16px;
               margin-top:18px; padding-top:18px;
               border-top:1px solid rgba(255,255,255,.10); }
  .vb-menu-tel { font-size:15px; color:#fff; white-space:nowrap; }
  .vb-menu-tel span { color:var(--lime); }
  .vb-menu-soc { display:inline-flex; align-items:center; gap:8px;
                 margin-inline-start:auto; }
}

/* min-width:0 on the flex CONTAINER as well as flex:1 1 0 on the list: without
   it the nav refused to shrink and pushed scrollWidth to 414/586/629 on a
   390px viewport */
.vb-nav { flex:1 1 0; min-width:0; display:flex; gap:20px; margin-inline:0 auto;
          overflow-x:auto; scrollbar-width:none; list-style:none; }

/* In the panel the list stops being a row and stops scrolling sideways. The
   lime bar that sits ABOVE a desktop link would float over the row below it
   here, so it becomes a bar on the leading edge instead. */
@media (max-width:1023px){
  .vb-nav { display:block; overflow:visible; margin:0; }
  /* THE RULE BETWEEN ITEMS IS THE AGENCY'S OWN: slate-300 at 20%, which is
     rgba(203,213,225,.2) — the same hairline it puts under its mobile bar and
     around every item in its own menu.

     It writes `border-y` on every item, so two adjacent items stack a bottom
     and a top border and the line between them is 2px. This is border-top on
     each plus a bottom on the last: one hairline everywhere, including above
     the first and below the last, with nothing doubled. */
  .vb-nav li { border-top:1px solid rgba(203,213,225,.2); }
  .vb-nav li:last-child { border-bottom:1px solid rgba(203,213,225,.2); }
  .vb-nav a { font-size:16px; line-height:1.3; padding-block:14px; white-space:normal; }
  .vb-nav a::before { inset-inline:auto auto; inset-block:50% auto;
                      translate:-14px -50%; width:3px; height:20px; top:auto;
                      border-radius:2px; scale:1 0; }
  .vb-nav a:hover::before,
  .vb-nav .current-menu-item > a::before,
  .vb-nav [aria-current]::before { scale:1 1; }
}

/* the body cannot scroll behind an open panel — without this a flick on the
   panel's own edge scrolls the article underneath it */
body.vb-menu-open { overflow:hidden; }
/* ── THE MASTHEAD ROW HAS TO HOLD WHATEVER SHE PUTS IN IT ────────────────
   THE NAVIGATION IS EDITOR-DRIVEN NOW. Appearance → Front page desks decides
   which categories appear here, so the row's contents are no longer something
   the stylesheet knows in advance — and with six of them the row had been
   overflowing at EVERY screen width, not just narrow ones. Measured on
   /research/ at a 1504px viewport: the list wanted 813px and had 781, so
   `overflow-x:auto` quietly took the difference and "Research" rendered as
   "Rese".

   WHY WIDTH DID NOT HELP. `.vb-w` caps the row at 1350px, and the trailing
   block — phone 133 + Latest 88 + social 124 — takes 381 of it whatever the
   monitor. So a wider screen gave the nav nothing: the shortfall was constant.

   THE ROW THEREFORE GIVES THINGS UP IN ORDER OF WHAT A READER CAME FOR, and
   the navigation is last to go. Three bands, each measured:

     >=1350   everything shown. gap 18 -> the list needs 754 of 797.
     1200..1349  the social icons leave the masthead. They are in the footer
                 too, and an icon row is the one thing here nobody navigates
                 by. At 1280 that leaves 105px spare.
     1024..1199  the phone number leaves as well and the gap tightens to 14.
                 At 1024 that leaves 38px spare. Below 1024 the whole list is
                 already the burger panel, where it is a vertical list and
                 nothing can be clipped.

   `overflow-x:auto` STAYS as the last resort. BE CLEAR ABOUT THE HEADROOM:
   the widest band is the TIGHTEST one, because `.vb-w` caps the row at 1350px
   while the trailing block keeps its full 381. With the six desks live on
   production — 646px of labels, measured — that band has 43px spare. That is
   one more SHORT label, not a long one. The 1200 and 1024 bands have 127 and
   34, so 1024 is the other edge worth watching.

   When a seventh long desk goes in, the fix is to drop the social icons from
   the masthead at every width, not to shave the gap further: 14px is already
   the floor at which two labels stop reading as two things. */
/* SCOPED TO .vb-mast, AND IT HAS TO BE. `.vb-tel { display:block }` and
   `.vb-social { display:inline-flex }` are declared further down this file, so
   an unscoped override here loses on source order at equal specificity — the
   band would have been written, shipped, and done nothing. The extra class
   also documents that this only ever applies to the header copies. */
/* ── THE GAP IS voctos.com's 28 AGAIN ────────────────────────────────────────
   THE THREE BANDS ABOVE WERE MEASURED WITH THE "Latest" PILL IN THE ROW, and
   14/16/18 were the largest gaps that fitted around it. The pill is gone by
   request, which hands the row back 88px plus its own 20px gap — and 28, the
   reference's number, now fits.

   THAT MATTERS MORE THAN IT SOUNDS. Rendering "Technical SEO" at each site's
   own nav font gives 99.99px on BOTH — the type was never the difference. The
   difference was air: their six labels total 408px with 28px between them,
   ours are seven totalling 646px with 18px. Same glyphs, a third less space,
   which reads as smaller type rather than as a tighter row.

   The hiding bands stay exactly as they were. They are what MAKES 28 fit at
   the narrow end — at 1024 the phone and the icons are already out of the
   row, which leaves 852px for a list that wants 814. */
@media (min-width:1024px) and (max-width:1199px){
  .vb-mast .vb-tel,
  .vb-mast .vb-social { display:none; }
}
@media (min-width:1200px) and (max-width:1349px){
  .vb-mast .vb-social { display:none; }
}
@media (min-width:1024px){ .vb-nav { gap:28px; } }
.vb-nav::-webkit-scrollbar { display:none; }
.vb-nav li { flex:none; }

/* ── MENU ITEMS TAKE voctos.com's WEIGHTS ───────────────────────────────
   THIS RULE USED TO SET EVERY MENU TO 700, and it no longer does.

   The bold came from a direct instruction — "any menu li bold" — and was
   applied everywhere for consistency. Two later instructions asked for the
   header and the footer to match voctos.com's size and weight, which the
   bold contradicted: the reference sets its masthead nav at 400 and its
   footer links at 500, and 700 in either place is visibly heavier side by
   side. I flagged the clash twice rather than silently picking, and the
   answer came back looking at the two screenshots: the difference was the
   weight.

   So the reference wins, and the two menus take ITS two values rather than
   one shared number — they are not the same weight on voctos.com and
   flattening them to one would be the same mistake in the other direction:

     .vb-nav        12 / 400   masthead, and the mobile panel it becomes
     .vb-foot-links 14 / 500   the three footer columns
     .vb-foot-legal 12 / 400   the fine-print row, which is fine print

   This also puts the whole theme back under the 400 ceiling except where a
   ported reference exceeds it — the footer's 500 and the FAQ title's 500 are
   both the reference's own numbers, inherited rather than chosen. */
.vb-nav li > a,
.vb-foot-legal li > a { font-weight:400; }
.vb-foot-links li > a { font-weight:500; }
.vb-nav a { position:relative; display:block; font-size:12px; line-height:24px;
            white-space:nowrap; color:#fff; transition:color .2s var(--ease); }
/* the lime bar, ABOVE the link */
.vb-nav a::before { content:''; position:absolute; inset-inline:0; top:-27px; height:4px;
                    border-radius:2px; background:var(--lime); scale:0 1;
                    transition:scale .25s var(--ease); }
.vb-nav a:hover { color:var(--lime); }
.vb-nav a:hover::before,
.vb-nav .current-menu-item > a::before,
.vb-nav [aria-current]::before { scale:1 1; }
.vb-nav .current-menu-item > a,
.vb-nav [aria-current] { color:var(--lime); }

.vb-mast-end { flex:none; display:flex; align-items:center; gap:6px; }
@media (min-width:1024px){ .vb-mast-end { gap:18px; } }

/* ── MASTHEAD TYPE, SET TO voctos.com's ────────────────────────────────
   The same finding as the footer, on the same evidence: every SIZE in this
   bar already matched the reference — 12px nav, 12px number, 12px pill — and
   every WEIGHT was the body's inherited 300 against the reference's 400.
   That is why the bar read lighter than the original rather than smaller.

     element          reference       here (before)   here (now)
     nav link         12 / 400 / 24   12 / 700 / 24   12 / 700 / 24  ·  note
     phone            12 / 400 / 16   12 / 300 / 24   12 / 400 / 16
     "+20" in lime    12 / 400        12 / 300        12 / 400
     pill             12 / 400 / 16   12 / 300 / 16   12 / 400 / 16

   The line-height on the number was 24 against the reference's 16. Changed
   with the weight, and safe to change: the bar is a centred flex row, so a
   shorter line box re-centres rather than shifting the baseline.

   THE NAV LINKS ARE THE SAME DISAGREEMENT AS THE FOOTER'S, and they are left
   the same way. The reference sets them at 400; the standing instruction on
   this theme is that every menu item is bold, and that instruction is later
   and explicit, so they stay at 700. The reference's number is recorded here
   so the choice stays visible and one edit reverses it — in both places at
   once, since the rule that makes them bold covers the masthead and the
   footer together. */
.vb-tel { display:none; font-size:12px; font-weight:400; line-height:16px;
          color:#fff; white-space:nowrap; }
@media (min-width:1024px){ .vb-tel { display:block; } }
.vb-tel span { color:var(--lime); }

/* line-height 16 and a tighter pad below 1024 are what land the bar on the
   agency's own 76px / 49px exactly: 20+20 padding + a 36px control is 76, and
   10+10 + a 28px control is 48. Left at 1.4 and 10/20 the header measured 77
   and 57 — close enough to look fine and wrong enough that the two sites
   would never line up if a reader opened them side by side. */
/* 12/400/16 — the reference's "Request a Call" pill, and the same cut its
   footer pill uses. Set on the shared rule rather than per-location: both
   masthead and footer pills measure identically on voctos.com, so a
   `.vb-foot .vb-cta` override would have been two places to keep in step for
   one value. */
.vb-cta { flex:none; font-size:12px; font-weight:400; line-height:16px;
          background:var(--purple); color:#fff;
          border-radius:19px; padding:6px 14px; white-space:nowrap;
          transition:background .2s var(--ease); }
@media (min-width:1024px){ .vb-cta { padding:10px 20px; } }
/* the agency's own hover is bg-third, a deeper purple — not the lime, which
   would flip the button's meaning mid-interaction */
.vb-cta:hover { background:#5b12bd; color:#fff; }

.vb-social { display:none; align-items:center; gap:8px; }
@media (min-width:700px){ .vb-social { display:inline-flex; } }
.vb-ico-btn { display:inline-flex; align-items:center; justify-content:center; flex:none;
              width:36px; height:36px; border-radius:12px; background:var(--btn,#71A3FF);
              color:#fff; transition:filter .18s var(--ease), translate .18s var(--ease); }
.vb-ico-btn:hover { filter:brightness(1.08); translate:0 -1px; }
.vb-ico-btn svg { display:block; fill:currentColor; }

/* =============================================================================
   THE LEAD — THE AGENCY HOME PAGE'S HERO

   Every number below was read off www.voctos.com's own `agency-hero` block
   with getComputedStyle, not eyeballed from a screenshot. The panel is a flat
   gradient, NOT a photograph with scrims: the old full-bleed treatment let
   whichever article happened to be newest print its own artwork behind the
   headline, and one of them carried the word "Technical" across the frame,
   which read as a ghost word nobody had authored. The lead's picture now sits
   in the right column, where the agency puts its reveal video.
============================================================================= */
.vb-hero { position:relative; overflow:hidden;
           padding-block:80px 32px;
           background:linear-gradient(180deg,#091122 0%,#182748 78.24%,rgba(24,39,72,0) 88.98%); }
@media (min-width:640px){ .vb-hero { padding-block:109px 32px; } }
/* the agency swaps the gradient's stops at lg so the fade lands lower on a
   taller panel — 88.24%/98.98% against 78.24%/88.98% */
@media (min-width:1024px){
  .vb-hero { background:linear-gradient(180deg,#091122 0%,#182748 88.24%,rgba(24,39,72,0) 98.98%); }
}

/* THE DECORATION IS VOCTOS'S OWN FILE, NOT A REDRAWN ONE.
   `bg-video.webp` / `xs-bg-video.webp` copied out of the agency theme
   (firmwork/assets/img) and placed on the same ::before the agency uses, at
   the same measured geometry: 390×382 pinned right/top 70px on a phone,
   839×707 from left 45% at lg. It sits behind everything at z-index 0. */
.vb-hero::before { content:''; position:absolute; z-index:0; pointer-events:none;
  inset-inline-end:0; top:70px; width:390px; height:382px;
  background:url('../img/hero-bg-sm.webp') no-repeat center / contain; }
@media (min-width:1024px){
  .vb-hero::before { inset-inline-end:auto; inset-inline-start:45%; top:0;
    width:839px; height:707px;
    background-image:url('../img/hero-bg.webp'); }
}
/* the artwork is directional, so it is mirrored rather than moved in RTL */
[dir="rtl"] .vb-hero::before { scale:-1 1; }

/* six columns, gap-x 16 / gap-y 96, items-center — the agency's own grid.
   Below lg everything stacks and the media column is not rendered at all. */
.vb-hero-grid { display:grid; grid-template-columns:repeat(6,minmax(0,1fr));
                column-gap:16px; row-gap:96px; align-items:center; }
.vb-hero-txt   { grid-column:1 / -1; position:relative; z-index:2; }
.vb-hero-media { grid-column:1 / -1; position:relative; z-index:2; }
@media (min-width:1024px){
  .vb-hero-txt   { grid-column:span 3; }
  .vb-hero-media { grid-column:span 3; }
}

/* ── THE LATEST-ARTICLE CARD ─────────────────────────────────────────────
   It fills the agency's media slot and borrows its 45px radius, but it is a
   card rather than a picture, so it carries its own surface.

   THE SURFACE IS NOT DECORATION. Measured, the live lead's cover averaged
   luminance 36 against the panel's 38 — a ratio of 0.95, which is to say a
   bare image sat in this column completely invisible. The plate, the
   hairline and the drop give the card an edge to be read against whatever
   artwork lands in it. */
.vb-hero-card { display:block; overflow:hidden; border-radius:45px;
                background:rgba(255,255,255,.045);
                border:1px solid rgba(255,255,255,.14);
                box-shadow:0 24px 60px -24px rgba(0,0,0,.55);
                transition:border-color .2s var(--ease), translate .2s var(--ease); }
.vb-hero-card:hover { border-color:rgba(255,255,255,.3); translate:0 -2px; }
.vb-hero-card-pic { display:block; }
/* 40/21 is the covers' own ratio — see the note in front-page.php */
.vb-hero-card-img { display:block; width:100%; height:auto; aspect-ratio:40/21;
                    object-fit:cover; }
.vb-hero-card-body { display:block; padding:22px 26px 26px; }
.vb-hero-card-eb { display:flex; align-items:center; gap:10px; margin-bottom:12px;
                   font-size:11px; letter-spacing:.15em; text-transform:uppercase;
                   color:rgba(255,255,255,.72); }
.vb-hero-card-eb i { width:22px; height:3px; border-radius:2px;
                     background:var(--desk,var(--lilac)); flex:none; }
/* weight 400, per the theme's ceiling — the hero h1 is the one exception */
.vb-hero-card-h { display:block; font-family:var(--font-display,Unbounded,sans-serif);
                  font-size:20px; line-height:1.3; font-weight:400; color:#fff;
                  margin-bottom:14px; }
@media (min-width:1280px){ .vb-hero-card-h { font-size:22px; } }
.vb-hero-card-m { display:flex; flex-wrap:wrap; align-items:center; gap:8px;
                  font-size:13px; color:rgba(255,255,255,.62); }
.vb-hero-card-m i { font-style:normal; color:rgba(255,255,255,.34); }

/* the rack spans the full grid as the hero's third row. The 96px row-gap
   above it and the panel's own 32px padding below are the agency's measured
   numbers: video bottom 588 → tiles top 684, tiles bottom 810 → panel end
   842. */
.vb-hero-rack { grid-column:1 / -1; position:relative; z-index:2; }
/* belt and braces: nothing inside the rack animates in, whatever classes a
   future edit puts on the tiles. Everything here is styled finished. */
.vb-hero-rack > *, .vb-hero-rack .vb-in { animation:none !important; opacity:1; }

.vb-kick { display:inline-flex; align-items:center; gap:12px; margin-bottom:20px;
           font-size:11px; letter-spacing:.15em; text-transform:uppercase; color:#fff; }
.vb-kick i { width:26px; height:3px; border-radius:2px; background:var(--lilac); }

/* THE ONE DOCUMENTED BREAK FROM THE 400 CEILING.
   The agency's h1 here is `font-bold uppercase` at 46/46 — weight 700. The
   ceiling exists so this theme does not invent emphasis the agency does not
   use; it is not a reason to render the ported hero at a different weight
   from the hero it is porting. 22px on a phone, 46px from sm, per the
   reference's own text-fs-7 → sm:text-fs-4 pair. */
.vb-h1 { font-family:var(--font-display,Unbounded,sans-serif);
         font-size:22px; line-height:1; font-weight:700; text-transform:uppercase;
         letter-spacing:normal; color:#fff; margin-bottom:8px; text-wrap:balance; }
@media (min-width:640px){ .vb-h1 { font-size:46px; line-height:46px; } }
.vb-h1 a { color:inherit; }
/* the reference's lime gradient, clipped to the closing phrase of the
   headline — measured off its own span, stop for stop */
.vb-h1-hl { background-image:linear-gradient(180deg,#7B9412 0%,#D4EE69 44.02%,#7B9412 88.04%);
            -webkit-background-clip:text; background-clip:text; color:transparent; }

/* main_before_line.svg as a gradient: 4px tall, fading out by 24.04%, which is
   what forces the deck onto its own line below it */
.vb-hero-rule { width:100%; max-width:525px; height:4px; border-radius:2px;
                margin-bottom:12px;
                background:linear-gradient(90deg,#A1CB3A 0%,rgba(161,203,58,0) 24.04%); }
[dir="rtl"] .vb-hero-rule {
  background:linear-gradient(270deg,#A1CB3A 0%,rgba(161,203,58,0) 24.04%); }

/* the reference lede is 20/30 at weight 500, full white — not a dimmed
   secondary. Kept at 500 because that is the measured value. */
.vb-deck { max-width:56ch; margin-bottom:30px;
           font-size:20px; line-height:30px; font-weight:500; color:#fff; }

.vb-by { display:flex; flex-wrap:wrap; align-items:center; gap:12px 20px;
         margin-bottom:26px; font-size:13px; color:rgba(255,255,255,.62); }
.vb-by img { width:44px; height:44px; border-radius:9999px; object-fit:cover; }
.vb-by b { font-weight:400; color:#fff; }
.vb-by s { text-decoration:none; }

/* the agency's outlined hero button: 2px white, radius 10, 16px padding all
   round, 16/24 at 500 — 60px tall in the reference */
.vb-hero-btn { display:inline-flex; align-items:center; justify-content:center;
               padding:16px; border:2px solid #fff; border-radius:10px;
               font-size:16px; line-height:24px; font-weight:500; color:#fff;
               background:transparent;
               transition:background .18s var(--ease), color .18s var(--ease); }
.vb-hero-btn:hover { background:#fff; color:#091122; }

/* =============================================================================
   THE FOLDER TABS

   Geometry measured off the live `.ahero-service-item`, to the half pixel,
   because this is the one component a reader would recognise instantly if it
   were wrong: 140×124, padding 6/8, radius 10 0 10 10, NO box-shadow on the
   original, and the nub is 48×10 with a BORDERED 12×7 joint — not a gradient,
   which is what made an earlier attempt render as a stray flag.
============================================================================= */
/* No negative pull any more. The rack moved inside the hero panel (see
   `.vb-hero-rack`); what is left in `.vb-tabs` is the figures strip, which
   has always sat on the light ground and now simply starts where it is. */
.vb-tabs { position:relative; z-index:4; margin-top:0; }
.vb-folder { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px 12px;
             list-style:none; }
@media (min-width:700px){ .vb-folder { grid-template-columns:repeat(3,minmax(0,1fr)); } }

/* ── ON A PHONE THE RACK IS ONE SCROLLING ROW ────────────────────────────────
   Six tiles two-up is three rows of folders — 460px of navigation before the
   first headline, on the screen with the least of it to spare. One row that
   scrolls keeps the rack reading as a rack, and the half-visible tile at the
   edge is what tells a reader there are more.

   THE PADDING IS NOT DECORATION. The nub sits 9px ABOVE its tile and the drop
   shadow falls 40px below it; a scroll container clips both, so the rail is
   padded to hold them and pulled back by exactly the same amount, which
   leaves every other element on the page where it was. The inline pair does
   the same job for the 25px gutter, so the row bleeds to both screen edges
   and no tile is ever cut off inside the margin. */
@media (max-width:699px){
  .vb-folder { display:flex; gap:12px;
               overflow-x:auto; overflow-y:hidden;
               scroll-snap-type:x proximity; scrollbar-width:none;
               padding:12px 25px 34px; margin:-12px -25px -34px;
               scroll-padding-inline:25px; }
  .vb-folder::-webkit-scrollbar { display:none; }
  .vb-folder > li { flex:0 0 150px; scroll-snap-align:start; }

  /* The entry animation has to come off in here, and this is the trap:
     `animation-timeline: view()` resolves against the nearest SCROLL
     CONTAINER, and the rail has just become one. The tiles would be measured
     against a box they never scroll through vertically and could sit at
     opacity 0 for good. Everything in this theme is styled finished and the
     keyframes run it backwards, so removing the animation shows the finished
     tile — there is nothing to restore. */
  .vb-folder > .vb-in { animation:none; }
}
/* THE RACK IS AS WIDE AS THERE ARE DESKS.
   A fixed repeat(6) left three dead columns on a site with three
   categories — and a plain repeat(var(--vb-tabs)) would have stretched
   three tabs to 430px each, which is worse. The count comes from PHP as
   --vb-tabs, and the max-width pins every tab at the measured 200px
   whatever the count, so the row reads as a rack with room to grow rather
   than as a grid with holes in it. */
@media (min-width:1024px){
  .vb-folder { grid-template-columns:repeat(var(--vb-tabs,6),minmax(0,1fr));
               gap:20px;
               max-width:calc(var(--vb-tabs,6) * 200px + (var(--vb-tabs,6) - 1) * 20px); }
}
.vb-tab { position:relative; display:flex; flex-direction:column; align-items:center;
          justify-content:center; gap:6px; min-height:124px; padding:6px 8px;
          background:var(--desk); color:var(--desk-ink);
          border-radius:10px 0 10px 10px; box-shadow:0 18px 40px rgba(9,17,34,.22);
          transition:translate .28s var(--ease), box-shadow .28s var(--ease), opacity .2s; }
.vb-nub { position:absolute; inset-inline-end:0; top:0; width:48px; height:10px;
          background:var(--desk); border-start-start-radius:5px;
          border-start-end-radius:10px; translate:0 -9px; }
.vb-nub::before { content:''; position:absolute; width:12px; height:7px;
  border-inline-end:2.5px solid var(--desk); border-block-end:2.5px solid var(--desk);
  border-end-end-radius:10px; inset-inline-end:calc(100% - 2.5px); bottom:-1.4px; }
/* the white Research tile needs a hairline or it disappears into the card */
.vb-tab--pale { box-shadow:0 18px 40px rgba(9,17,34,.22), inset 0 0 0 1px #d5d9e8; }
@media (hover:hover){
  .vb-folder:hover .vb-tab { opacity:.5; }
  .vb-folder .vb-tab:hover { opacity:1; translate:0 -8px;
                             box-shadow:0 26px 50px rgba(9,17,34,.3); }
}
/* THE ICON INHERITS THE TAB'S OWN COLOUR.
   The tiles are not all dark — Cases is #fde047 and Research is #ffffff, both
   with black labels — so a white <img> icon would be invisible on two of the
   six tabs. The theme's own marks are inlined SVG stroked with currentColor,
   which resolves to --desk-ink, so one file is correct on every tile. An icon
   an editor uploads is an <img> and keeps its own colours, which is right: it
   is their artwork, not ours to recolour. */
.vb-tab .vb-ico { display:block; width:38px; height:38px; color:inherit; }
.vb-tab img { display:block; max-height:44px; width:auto; }
.vb-tab s { text-decoration:none; font-size:12px; text-align:center; line-height:1.3; }
.vb-tab b { font-size:11px; font-weight:400; opacity:.72; }

/* =============================================================================
   THE COUNTED FIGURE STRIP
============================================================================= */
.vb-figs { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:48px;
           padding-top:30px; border-top:1px solid var(--rule); }
@media (min-width:760px){ .vb-figs { grid-template-columns:repeat(4,minmax(0,1fr)); } }
.vb-fig { padding-inline:24px; border-inline-start:1px solid var(--rule); }
.vb-fig:first-child { padding-inline-start:0; border-inline-start:0; }
.vb-fig b { display:block; font-size:38px; font-weight:400; line-height:1;
            unicode-bidi:plaintext; }
@media (min-width:1024px){ .vb-fig b { font-size:46px; } }
.vb-fig span { display:block; margin-top:10px; font-size:11px; letter-spacing:.12em;
               text-transform:uppercase; color:var(--faint); }

/* ── ON A PHONE THE STRIP IS A LIST, NOT A GRID ──────────────────────────────
   Third attempt at this block, after a row gap and then a centred rule both
   failed for the same reason: the problem was never the spacing, it was the
   shape. Four cells two-up with a rule down the middle and a rule across it
   draws a cross, and a cross reads as a spreadsheet. It also forced "Weekly"
   — a word, not a figure — to sit at 38px beside a single-digit "3", which is
   what made the block look broken rather than merely tight.

   One figure per row: number at the leading edge, label at the trailing one
   on the same baseline, a hairline between rows. The rules run in one
   direction, every row uses the full width, and the word gets a word's size.

   THIS BLOCK HAS TO COME AFTER `.vb-fig b` AND `.vb-fig span`, not before.
   It sat above them at first and its type rules simply lost — same
   specificity, earlier in the file — so the numbers stayed at 38px and the
   labels kept their 10px top margin and never joined the baseline. The block
   below is unchanged; only its position in the file is. */
@media (max-width:759px){
  .vb-figs { display:block; padding-top:0; }
  .vb-fig { display:flex; align-items:baseline; justify-content:space-between;
            gap:16px; padding:14px 0 15px; border:0; }
  .vb-fig + .vb-fig { border-top:1px solid var(--rule); }
  .vb-fig b { font-size:34px; }
  /* the one figure that is a word rather than a number */
  .vb-fig b:not(.vb-count) { font-size:20px; }
  .vb-fig span { margin-top:0; }

  /* AND THE COUNT-UP COMES OFF HERE.
     The figures animate from 0 to their real value across a scroll range, so
     the number on screen is only true once the range completes. On a desktop
     the strip crosses it in one flick. On a phone it is a tall list that a
     reader can very easily come to rest half way through the range — and the
     strip then sits there stating 145 articles and 2 studies when the site
     has 149 and 3, with nothing to suggest it is still counting. A figure
     that is wrong while you are reading it is worse than one that never
     moved, so on a phone it does not move. The real value is already in the
     markup as `--vb-n`; stopping the animation is what reveals it. */
  .vb-count { animation:none; }
}

/* =============================================================================
   THE TICKER
============================================================================= */
.vb-ticker { position:relative; margin-top:44px; padding-top:22px;
             border-top:1px solid var(--rule); overflow:hidden; }
.vb-ticker-in { display:flex; align-items:center; gap:22px; }
.vb-ticker-lbl { flex:none; padding:10px 0; font-size:10.5px; letter-spacing:.14em;
                 text-transform:uppercase; color:var(--limeink); }
.vb-ticker-mask { position:relative; flex:1 1 auto; min-width:0; overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 4%,#000 94%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 4%,#000 94%,transparent); }
.vb-track { display:flex; align-items:center; gap:44px; width:max-content; padding:10px 0;
            list-style:none; }
.vb-track a { display:inline-flex; align-items:center; gap:12px; white-space:nowrap;
              font-size:13.5px; color:var(--muted); transition:color .2s var(--ease); }
.vb-track a:hover { color:var(--ink); }
.vb-track i { flex:none; width:5px; height:5px; border-radius:50%; background:var(--lime); }
.vb-ticker:hover .vb-track { animation-play-state:paused; }

/* =============================================================================
   SHARED — the "see the desk" link, the heading row, the card
============================================================================= */
.vb-all { display:inline-flex; align-items:center; gap:12px; font-size:17px; color:#8a93a3;
          transition:color .2s var(--ease); }
@media (min-width:1024px){ .vb-all { font-size:20px; } }
.vb-all > span { display:grid; place-items:center; width:40px; height:40px; flex:none;
                 border-radius:50%; border:1px solid rgba(13,23,45,.22); color:var(--ink);
                 transition:background .22s var(--ease), border-color .22s var(--ease),
                            color .22s var(--ease); }
.vb-all svg { width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.5;
              stroke-linecap:round; stroke-linejoin:round;
              transition:translate .22s var(--ease); }
.vb-all:hover { color:var(--ink); }
.vb-all:hover > span { background:var(--ink); border-color:var(--ink); color:#fff; }
.vb-all:hover svg { translate:2px 0; }
[dir="rtl"] .vb-all svg { scale:-1 1; }
/* on ink */
.vb-all--on { color:rgba(255,255,255,.7); }
.vb-all--on > span { border-color:rgba(255,255,255,.3); color:#fff; }
.vb-all--on:hover { color:#fff; }
.vb-all--on:hover > span { background:#fff; border-color:#fff; color:var(--night); }

.vb-head { display:flex; align-items:center; justify-content:space-between; gap:20px;
           flex-wrap:wrap; margin-bottom:26px; }
.vb-head h2 { font-size:var(--t3); line-height:1.08; }
.vb-head h2 small { font-size:.38em; font-weight:400; color:var(--faint);
                    margin-inline-start:14px; }

/* ON A PHONE THE LINK IS THE ARROW, BESIDE THE HEADING.
   "See everything" plus its circle is about 200px, which will not share a row
   with a heading on a 375px screen — so the row wrapped and the link became a
   third line of its own under the count. The circle alone fits.

   `font-size:0` hides the LABEL rather than deleting it, and that matters:
   the words are a text node inside the <a>, so they are still the link's
   accessible name. A screen reader still hears "See everything"; only the eye
   loses it. An `aria-label` on an icon would have been the other way round —
   markup to maintain, and a name that can drift from the visible one. */
@media (max-width:639px){
  .vb-head { flex-wrap:nowrap; }
  .vb-head h2 { min-width:0; }
  .vb-all { font-size:0; gap:0; flex:none; }
  .vb-all > span { font-size:17px; }
}

/* THE CARD IS IDENTICAL EVERYWHERE, on purpose. The six desk sections differ
   by their FRAME, not by their card — if the card changed too, nothing would
   read as one publication. */
.vb-grid { display:grid; grid-template-columns:1fr; gap:24px 16px; list-style:none; }
@media (min-width:640px){ .vb-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1100px){ .vb-grid { grid-template-columns:repeat(4,minmax(0,1fr)); } }

/* ── LATEST IS ONE SCROLLING ROW ON A PHONE ──────────────────────────────────
   Four stacked cards is about 1,400px of scrolling to get past the newest
   four articles and reach the desks. A row shows the same four, admits that
   there are more by cutting the next one at the edge, and costs one screen.

   SCOPED TO `.vb-latest` ON PURPOSE. `.vb-grid` is also the archive listing,
   and an archive is a list you read down — turning /articles/ into a
   horizontal rail would hide eleven of its twelve posts behind a gesture
   nobody would think to make on a page that is supposed to BE the list.

   The padding pair holds the cards' drop shadow and lets the row bleed to
   both screen edges, and is pulled back by the same amount so nothing else
   moves. See .vb-folder for the same technique. */
@media (max-width:639px){
  .vb-latest .vb-grid { display:flex; gap:16px;
                        overflow-x:auto; overflow-y:hidden;
                        scroll-snap-type:x proximity; scrollbar-width:none;
                        padding:4px 25px 20px; margin:-4px -25px -20px;
                        scroll-padding-inline:25px; }
  .vb-latest .vb-grid::-webkit-scrollbar { display:none; }
  .vb-latest .vb-grid > li { flex:0 0 284px; scroll-snap-align:start; }
  /* the rail is now the nearest scroll container, so a view() timeline would
     measure these against a box they never scroll through — same trap as the
     folder tabs, same fix */
  .vb-latest .vb-grid > .vb-in { animation:none; }
}
.vb-card { display:flex; flex-direction:column; height:100%; background:#fff;
           border-radius:30px; overflow:hidden;
           box-shadow:0 8px 18px -10px rgba(13,23,45,.16);
           transition:translate .3s var(--ease), box-shadow .3s var(--ease); }
.vb-card:hover { translate:0 -5px; box-shadow:0 26px 44px -18px rgba(13,23,45,.26); }
.vb-media { position:relative; display:block; overflow:hidden; }
.vb-media img { display:block; width:100%; height:200px; object-fit:cover;
                transition:scale .55s var(--ease); }
.vb-card:hover .vb-media img { scale:1.06; }
.vb-tag { position:absolute; inset-inline-start:24px; bottom:12px; z-index:2;
          padding:6px 14px; border-radius:15px; font-size:13px; line-height:1.15;
          background:var(--tint); color:var(--tone); }
/* the Research tag is white on white; the inset hairline is what makes it a tag */
.vb-tag--pale { box-shadow:inset 0 0 0 1px #d5d9e8; }
.vb-cb { display:flex; flex-direction:column; flex:1; padding:18px 22px 20px; }
.vb-cb h3 { font-size:var(--t6); line-height:20px; transition:color .2s var(--ease); }
.vb-card:hover .vb-cb h3 { color:var(--purple); }
.vb-cb p { margin-top:10px; font-size:12px; line-height:16px; color:var(--faint); }
.vb-cm { margin-top:auto; padding-top:16px; display:flex; align-items:center; gap:9px;
         font-size:11px; color:var(--faint); }
.vb-cm img { width:24px; height:24px; border-radius:9999px; }

/* ── THE STUDY CARD'S LAST LINE ──────────────────────────────────────────────
   `.vb-cm` is built around a 24px avatar followed by author · date. A study has
   no author line — it has a date and what that date MEANS, which is the whole
   point of printing it — so the variant drops the avatar's gap and sets the
   label apart from the value instead.

   THE LABEL IS NOT BOLD. `<b>` is here for what the word is, not for how it
   looks: it marks which of the two dates this is, and the theme's ceiling on
   weight is 400. Colour carries the distinction. */
.vb-cm--ev { gap:7px; }
.vb-cm--ev b { font-weight:400; font-size:10.5px; letter-spacing:.07em;
               text-transform:uppercase; color:rgba(255,255,255,.34); }
.vb-cm--ev span { color:var(--limesoft); }
/* said, not implied — a study with no sample on file reads as one */
.vb-cm--ev s { text-decoration:none; margin-inline-start:auto;
               font-size:10.5px; color:rgba(255,255,255,.28); }

/* =============================================================================
   THE ENGINE MARQUEE
============================================================================= */
.vb-eng { margin-top:56px; background:var(--ink); border-radius:30px; overflow:hidden;
          padding-block:30px; }
.vb-eng-l { text-align:center; font-size:10.5px; letter-spacing:.14em;
            text-transform:uppercase; color:rgba(255,255,255,.45); margin-bottom:22px; }
.vb-eng-mask { overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent); }
.vb-eng-track { display:flex; align-items:center; gap:64px; width:max-content;
                list-style:none; padding:0; }
.vb-eng-track img { height:26px; width:auto; opacity:.62;
                    transition:opacity .25s var(--ease), scale .25s var(--ease); }
.vb-eng-track img:hover { opacity:1; scale:1.06; }
.vb-eng-track span { font-size:15px; color:rgba(255,255,255,.55); white-space:nowrap; }

/* =============================================================================
   THE CLOSE — a contained ink panel, clear of the footer

   It ran full-bleed into the footer in one revision and that was wrong: two
   ink masses touching read as one over-long footer, and the offer lost its
   own edges. A panel with real air before the footer reads as a piece of the
   page rather than site furniture.
============================================================================= */
/* The bottom padding IS the fade band — see .vb-foot::before. 78px was the
   old value and too short to dissolve in; 118px gives the gradient room to
   look like light losing to ink rather than a grey stripe. */
.vb-news { padding-block:70px var(--foot-fade); }
.vb-news-in { background:var(--ink); color:#fff; border-radius:30px; padding:38px 28px;
              display:grid; grid-template-columns:1fr; gap:30px; }
@media (min-width:900px){
  .vb-news-in { grid-template-columns:minmax(0,1fr) minmax(0,400px); gap:56px;
                align-items:end; padding:48px 44px; }
}
.vb-news .vb-eb { font-size:11.5px; letter-spacing:.17em; text-transform:uppercase;
                  color:var(--limesoft); margin-bottom:14px; }
.vb-news h2 { font-size:var(--t3); line-height:1.08; color:#fff; max-width:19ch; }
.vb-news p { margin-top:14px; font-size:16px; line-height:24px;
             color:rgba(255,255,255,.62); max-width:48ch; }
.vb-form { display:flex; gap:10px; flex-wrap:wrap; }
.vb-form input { flex:1 1 220px; min-width:0; border:1px solid rgba(255,255,255,.2);
                 border-radius:19px; padding:15px 20px; font-family:inherit;
                 font-size:14px; font-weight:300; color:#fff;
                 background:rgba(255,255,255,.08); outline:0;
                 transition:border-color .2s var(--ease), box-shadow .2s var(--ease); }
.vb-form input::placeholder { color:rgba(255,255,255,.4); }
.vb-form input:focus { border-color:var(--limesoft);
                       box-shadow:0 0 0 3px rgba(198,227,107,.18); }
.vb-form input:disabled { opacity:.55; cursor:not-allowed; }
.vb-form button { flex:none; border:0; border-radius:19px; padding:15px 30px;
                  background:var(--limesoft); color:var(--ink); font-family:inherit;
                  font-size:14px; font-weight:400; cursor:pointer;
                  transition:background .2s var(--ease), scale .2s var(--ease); }
.vb-form button:hover:not(:disabled) { background:#fff; scale:1.03; }
.vb-form button:disabled { opacity:.55; cursor:not-allowed; }
.vb-note { margin-top:14px; font-size:12px; color:rgba(255,255,255,.4); }

/* =============================================================================
   FOOTER
============================================================================= */
/* The agency footer's own system, measured off www.voctos.com rather than
   copied by eye. Two rule weights do the structural work: PURPLE #7616f4
   under the top bar and under the link grid, SLATE #37425b under the fine
   rows. That pair is most of what makes their footer read as theirs — a
   single grey hairline everywhere reads as a generic footer.

   The one deliberate divergence: their links run at weight 500 and this
   theme's ceiling is 400, so these are 400. Nothing else differs. */
/* ── THE FOOTER NOW BRINGS ITS OWN EMPTY BAND ───────────────────────────
   THE FADE WAS WASHING OVER THE FAQ, and the cause is a coupling that broke
   when something two files away changed.

   `.vb-foot::before` is a 118px gradient that reaches UP out of the footer to
   dissolve the seam between the light page and the ink footer. The comment
   below states the condition it was built on: the band is exactly
   `.vb-news`'s bottom padding, `--foot-fade` is used by both, "which is why
   this needs no z-index — the gradient stops where the newsletter panel
   begins and can never wash over it."

   Then the newsletter was gated behind `voctos_blog_newsletter_live()` and
   stopped rendering. Live HTML carries no `class="vb-news"` at all. So the
   118px of empty space the gradient was written to live in no longer exists,
   and the gradient sat on top of whatever ends the page instead — the last
   three or four FAQ questions, fading out as if they were decoration.

   Nothing was wrong with either change on its own. The fade assumed a
   spacer it did not own, and the panel that provided it left.

   So the footer owns the band now: its own top margin creates the empty
   space, and the gradient fills exactly that. When the newsletter IS live it
   still supplies the padding, so the adjacent-sibling rule takes the margin
   back off and the original geometry returns untouched. */
.vb-foot { background:var(--ink); color:#fff; position:relative;
           margin-top:var(--foot-fade); }
.vb-news + .vb-foot { margin-top:0; }

/* =============================================================================
   THE AGENCY BANNER

   IT INHERITS THE NEWSLETTER'S CONTRACT WITH THE FOOTER, and it has to. The
   footer creates a 118px empty band with its own top margin and fills it with
   a gradient that reaches UP out of itself; anything sitting in that band gets
   painted over. `.vb-news` avoided it by ending exactly where the band begins
   — its bottom padding IS `--foot-fade` — and by taking the footer's margin
   off with an adjacent-sibling rule.

   This panel does the same, and the sibling rule below covers BOTH orders:
   banner alone, or newsletter then banner. The `:has()` rule is the third
   case — newsletter live, banner suppressed on an article — and it is written
   as a progressive enhancement rather than a requirement, because the plain
   adjacent rules already handle every case a browser without :has() will see.
============================================================================= */
.vb-promo { padding-block:70px var(--foot-fade); }
.vb-promo + .vb-foot { margin-top:0; }

.vb-promo-in { position:relative; overflow:hidden; border-radius:30px;
               background:var(--ink); color:#fff;
               padding:34px 26px; display:grid; gap:22px; align-items:center; }
@media (min-width:860px){
  .vb-promo-in { grid-template-columns:minmax(0,1fr) auto; gap:40px;
                 padding:44px 48px; }
}

/* THE ONE PIECE OF DECORATION: a soft purple bloom in the corner the eye
   leaves the panel from, so an ink rectangle above an ink footer still reads
   as its own object. `radial-gradient` with a feathered stop — a hard stop
   aliases visibly on a curve at this size. */
.vb-promo-in::after { content:''; position:absolute; inset-block-start:-45%;
                      inset-inline-end:-8%; width:480px; height:480px;
                      background:radial-gradient(circle,
                        rgba(109,40,217,.55) 0%,
                        rgba(109,40,217,.22) 45%,
                        rgba(109,40,217,0) 70.5%);
                      pointer-events:none; }
.vb-promo-txt { position:relative; z-index:1; min-width:0; }

.vb-promo-eb { font-size:11px; letter-spacing:.09em; text-transform:uppercase;
               color:rgba(255,255,255,.45); }
.vb-promo-h { margin-top:10px; font-size:var(--t4); line-height:1.15; color:#fff; }
.vb-promo-p { margin-top:12px; max-width:58ch; font-size:14px; line-height:1.6;
              color:rgba(255,255,255,.66); }

/* THE SERVICE ROW. Chips rather than a comma list, because each one is its own
   link — a run of underlined words inside a sentence is a harder target and a
   worse read. They wrap, so a narrower panel drops them onto a second line
   instead of squeezing the labels. */
.vb-promo-svc { display:flex; flex-wrap:wrap; gap:8px; margin-top:20px;
                list-style:none; padding:0; }
.vb-promo-svc a { display:inline-flex; align-items:center; padding:7px 13px;
                  border-radius:9999px; font-size:12.5px; line-height:16px;
                  color:rgba(255,255,255,.78);
                  background:rgba(255,255,255,.07);
                  border:1px solid rgba(255,255,255,.14);
                  transition:background .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease); }
.vb-promo-svc a:hover { background:rgba(255,255,255,.14);
                        border-color:rgba(198,227,107,.55); color:#fff; }

/* the shared pill, plus the arrow and the room for it */
.vb-promo-cta { position:relative; z-index:1; display:inline-flex; align-items:center;
                gap:9px; justify-self:start; white-space:nowrap; }
@media (min-width:860px){ .vb-promo-cta { justify-self:end; } }
.vb-promo-arrow { display:block; flex:none; transition:translate .2s var(--ease); }
.vb-promo-cta:hover .vb-promo-arrow { translate:2px -2px; }
@media (prefers-reduced-motion:reduce){
  .vb-promo-arrow, .vb-promo-cta:hover .vb-promo-arrow { transition:none; translate:none; }
}
.vb-foot-in { padding-block:0 14px; }

/* THE SEAM. A light page meeting an ink footer drew one hard rule across the
   full width — the only edge on the site that reads as an accident rather
   than a decision. The footer now reaches UP into the empty band under the
   newsletter and fades in, so the two grounds dissolve instead of butting.

   The band is exactly .vb-news's bottom padding (--foot-fade is used by
   both), which is why this needs no z-index: the gradient stops where the
   newsletter panel begins and can never wash over it. Were the two ever to
   overlap, the pseudo-element — later in the DOM — would paint on top of the
   panel, so the two values are tied together on purpose.

   The stops are not linear. A straight ramp reads as a grey bar; front-
   loading the transparency keeps the top of the band looking like the page
   and puts the actual darkening in its lower third. */
.vb-foot::before {
  content:""; position:absolute; left:0; right:0; bottom:100%;
  height:var(--foot-fade); pointer-events:none;
  background:linear-gradient(to bottom,
    rgba(13,23,45,0)    0%,
    rgba(13,23,45,.10) 42%,
    rgba(13,23,45,.34) 68%,
    rgba(13,23,45,.72) 87%,
    rgba(13,23,45,1)  100%);
}
.vb-foot a { color:#fff; transition:color .2s var(--ease); }
.vb-foot a:hover { color:var(--lime); }

/* ── the top bar ── */
.vb-foot-top { display:flex; flex-wrap:wrap; align-items:center;
               justify-content:space-between; gap:20px 28px;
               padding-block:28px 20px; border-bottom:1px solid var(--purple); }
/* ── THE FOOTER MARK IS SIZED TO ITS INK, NOT TO ITS BOX ─────────────────────
   IT WAS 36px, WHICH IS voctos.com's NUMBER EXACTLY — and it still looked
   noticeably smaller than theirs, because the two sites are not using the same
   artwork.

   Measured by reading both files onto a canvas and finding the bounding box of
   every pixel with alpha > 12:

     voctos.com   Voctos-Logo-1.webp     256x110, ink 245x107 — under 3% clear
     this site    Voctos_Logo-Copy.png   320x132, ink 229x101 — 11-15% clear
                                          on every side

   So at a shared 36px box the visible mark came out 35.0px tall there and
   27.5px here: same CSS, 21% smaller logo. The box is therefore 46px, which
   puts our ink at 101/132 x 46 = 35.2px — their 35.0, to within a fifth of a
   pixel.

   IF THE FILE IS EVER REPLACED with a tightly cropped one, this number has to
   go back to 36 or the mark will be a fifth too large. That is the cost of
   correcting artwork in CSS, and it is why the measurement is written down
   here rather than left as a magic number. */
.vb-foot .vb-logo img,
.vb-foot .custom-logo-link img { height:46px; width:auto; }
.vb-foot-reach { display:flex; flex-wrap:wrap; gap:8px 28px; margin-inline-start:auto; }
.vb-foot-reach p { display:flex; gap:6px; align-items:baseline;
                   font-size:12px; line-height:16px; }
.vb-foot-reach span { color:#fff; }
.vb-foot-reach a { font-size:12px; line-height:16px; white-space:nowrap; }
.vb-foot-reach i { font-style:normal; color:var(--lime); }
/* THE AUTO MARGIN MOVED ONTO THE PILL. It used to sit on .vb-foot-reach,
   which pushed the numbers and everything after them to the right edge. With
   the numbers gone that rule had nothing to act on, and the pill collapsed
   back against the logo — the kind of breakage that follows a deletion two
   selectors away from it. */
.vb-foot-cta { flex:0 0 auto; margin-inline-start:auto; }
@media (max-width:1023px){ .vb-foot-cta { display:none; } }

/* ── the link columns: the agency's 12-column measure, 3 / 4 / 2 / 3 ── */
.vb-foot-cols { list-style:none; border-bottom:1px solid var(--purple); }
.vb-foot-col { padding-block:22px;
               /* a hairline between stacked columns on a phone, exactly as
                  the agency does it — without one the lists merge */
               border-bottom:1px solid rgba(118,22,244,.55); }
.vb-foot-col:last-child { border-bottom:0; }
/* TWO desktop steps, as the agency has them: 4/4/4 with the follow card on its
   own full-width row from 1024, then 3/4/2/3 from 1280. Held at 3 the whole way
   the card is ~200px inside 1024 and the five 40px squares wrap to two rows,
   which is what it did before this rule existed. */
@media (min-width:1024px){
  .vb-foot-cols { display:grid; grid-template-columns:repeat(12,minmax(0,1fr));
                  column-gap:56px; row-gap:40px; padding-block:44px 32px; }
  .vb-foot-col { padding-block:0; border-bottom:0; }
  .vb-foot-col--3, .vb-foot-col--4, .vb-foot-col--2 { grid-column:span 4; }
  .vb-foot-col:last-child { grid-column:span 12; }
}
@media (min-width:1280px){
  .vb-foot-col--3 { grid-column:span 3; }
  .vb-foot-col--4 { grid-column:span 4; }
  .vb-foot-col--2 { grid-column:span 2; }
  .vb-foot-col:last-child { grid-column:span 3; }
}
/* ── FOOTER TYPE, SET TO voctos.com's ──────────────────────────────────
   THE SIZES WERE ALREADY RIGHT. Measured side by side, every size in this
   footer already matched the reference — 14px links, 12px fine print, 12px
   pill. What did not match was the WEIGHT: this footer inherited the body's
   300 almost everywhere, and the agency's footer runs at 400 and 500. That
   is the whole of the difference, and it is why the text read thin next to
   the original rather than smaller.

     reference          here (before)      here (now)
     link      14 / 500   14 / 700           14 / 700  · see the note below
     "Follow us:" 14 / 500  14 / 300         14 / 500
     fine print 12 / 400   12 / 300          12 / 400
     pill      12 / 400   12 / 300           12 / 400

   THE LINKS ARE THE ONE DISAGREEMENT, and it is a real one rather than an
   oversight: the reference sets them at 500, and the standing instruction on
   this theme is that every menu item is bold. The later, explicit instruction
   wins, so they stay at 700 — but the reference's number is written here so
   the choice is visible and one edit undoes it. */
.vb-foot h2 { font-size:10.5px; font-weight:400; letter-spacing:.14em;
              text-transform:uppercase; color:rgba(255,255,255,.42);
              margin-bottom:18px; }
.vb-foot-links { display:grid; gap:20px; list-style:none; }
.vb-foot-links a { display:flex; align-items:baseline; font-size:14px; line-height:20px; }
/* the footer pill's weight now comes from the shared .vb-cta rule */
/* 10×10 lime arrow, 8px to its right — their measurement to the pixel */
.vb-foot-links svg { flex:0 0 auto; width:10px; height:10px;
                     margin-inline-end:8px; color:var(--lime); }
.vb-foot-links--plain a { display:block; }

/* ── the follow card ── */
.vb-foot-follow { background:rgba(255,255,255,.1); border:1px solid #fff;
                  border-radius:6px; padding:16px 20px; }
/* 500 is the reference's own weight for this label — "Follow us:" measures
   14/500 on voctos.com, the same cut as its footer links. */
.vb-foot-follow p { font-size:14px; font-weight:500; line-height:20px; color:#fff;
                    margin-bottom:12px; }
.vb-foot-follow ul { display:flex; flex-wrap:wrap; gap:10px; list-style:none; }
.vb-foot-follow a { display:grid; place-items:center; width:40px; height:40px;
                    border-radius:8px; transition:transform .2s var(--ease); }
.vb-foot-follow a:hover { transform:translateY(-2px); }
.vb-foot-follow svg { width:20px; height:20px; }
@media (min-width:1024px){ .vb-foot-follow p { text-align:start; } }
@media (max-width:1023px){
  .vb-foot-follow p { text-align:center; }
  .vb-foot-follow ul { justify-content:center; }
}

/* ── the contact row, then the fine print ── */
.vb-foot-meta { display:flex; flex-wrap:wrap; align-items:center; gap:16px;
                padding-block:14px; border-bottom:1px solid var(--foot-rule); }
.vb-foot-meta ul { display:flex; flex-wrap:wrap; gap:16px 40px;
                   margin-inline-end:auto; list-style:none; }
.vb-foot-meta li { display:flex; align-items:center; }
.vb-foot-meta svg { flex:0 0 auto; margin-inline-end:8px; color:var(--lime); }
/* 12/400, the reference's fine print — its "© 2017–2026, VOCTOS LLC" line
   measures 12px / 400 / 18px in rgb(148,163,184), which is this exact grey.
   Only the weight was off. */
.vb-foot-meta a, .vb-foot-meta span, .vb-foot-meta p { font-size:12px; font-weight:400;
                                                       line-height:18px; color:#94a3b8; }
.vb-foot-fine { padding-top:14px; font-size:12px; font-weight:400; line-height:18px;
                color:#94a3b8; }
.vb-foot-fine a { font-size:12px; color:#94a3b8; text-decoration:underline; }
.vb-foot-legal { display:inline-flex; flex-wrap:wrap; gap:4px 18px; list-style:none;
                 margin-inline-start:18px; vertical-align:baseline; }

/* =============================================================================
   PAGINATION — real pages, not decoration
============================================================================= */
.vb-pager { margin-top:48px; padding-top:26px; border-top:1px solid var(--rule); }
/* centred, not flush to the start edge. A pager is navigation for the whole
   listing above it, not a caption belonging to the first column — sitting on
   the left of a 1300px grid it read as an orphan. justify-content centres it
   in LTR and RTL alike. */
.vb-pager .nav-links { display:flex; flex-wrap:wrap; gap:8px; align-items:center;
                       justify-content:center; }
.vb-pager .page-numbers { display:grid; place-items:center; min-width:42px; height:42px;
                          padding-inline:14px; border-radius:19px; font-size:14px;
                          color:var(--muted); background:#fff;
                          box-shadow:inset 0 0 0 1px var(--rule);
                          transition:background .2s var(--ease), color .2s var(--ease),
                                     box-shadow .2s var(--ease); }
.vb-pager a.page-numbers:hover { color:var(--ink); box-shadow:inset 0 0 0 1px #c9ced9; }
.vb-pager .page-numbers.current { background:var(--ink); color:#fff;
                                  box-shadow:inset 0 0 0 1px var(--ink); }
.vb-pager .page-numbers.dots { background:none; box-shadow:none; }

/* =============================================================================
   PRINT — an article is a document before it is a web page
============================================================================= */
@media print {
  .vb-mast, .vb-ticker, .vb-news, .vb-foot, .vb-eng, .vb-tabs { display:none !important; }
  body.vb { background:#fff; color:#000; font-size:11pt; }
  .vb-w { max-width:none; padding-inline:0; }
}


/* =============================================================================
   BACK TO TOP

   THE MEASUREMENTS ARE voctos.com's, read off their live button with
   getComputedStyle: a 59.1px circle, white, `left:5%`, z-index 20, the arrow
   22x29 in #CAD63A, and Tailwind's `drop-shadow-sm` — which resolves to
   drop-shadow(rgba(0,0,0,.05) 0 1px 1px), a filter rather than a box-shadow.
   Kept as a filter here too: on a circle the two are not the same thing, since
   box-shadow follows the border-box and drop-shadow follows the painted shape.

   WHAT IS NOT COPIED IS THE POSITION IN THE COLUMN. Theirs sets `bottom` from
   JavaScript and, measured, lands on -10629px — see the note in footer.php.
   This one sits 28px off the bottom of the viewport, on the same 5% inset.

   `hidden` DOES THE HIDING, not opacity alone, so the button is out of the tab
   order until it works. The class only runs the fade; display is the
   attribute's job, and `[hidden]` has to be forced because a `display:flex`
   rule would otherwise beat the user-agent's `display:none`. */
.vb-top[hidden] { display:none; }
.vb-top { position:fixed; inset-inline-start:5%; bottom:28px; z-index:20;
          display:flex; align-items:center; justify-content:center;
          width:59.1px; height:59.1px; padding:0; border:0; cursor:pointer;
          background:#fff; border-radius:9999px;
          /* THE AGENCY'S LIME, NOT THIS THEME'S. `--lime` here is #9BB725, a
             darker green the publication uses throughout; the arrow on
             voctos.com is #CAD63A, read off their SVG. This control is a
             straight port of theirs, so it keeps their value — the one place
             in the stylesheet where a literal beats the token on purpose. */
          color:#CAD63A;
          filter:drop-shadow(rgba(0,0,0,.05) 0 1px 1px);
          opacity:0; transition:opacity .3s var(--ease,ease), translate .3s var(--ease,ease);
          translate:0 6px; }
.vb-top.is-on { opacity:1; translate:0 0; }
.vb-top:hover { background:#fff; filter:drop-shadow(rgba(0,0,0,.14) 0 3px 8px); }
.vb-top:focus-visible { outline:2px solid var(--lime,#CAD63A); outline-offset:3px; }
.vb-top svg { display:block; width:22px; height:29px; }

/* the lift on arrival is motion for its own sake — the fade still tells the
   reader the control appeared */
@media (prefers-reduced-motion:reduce){
  .vb-top { transition:opacity .3s ease; translate:none; }
  .vb-top.is-on { translate:none; }
}

/* ON A PHONE IT MOVES OUT OF THE THUMB'S WAY and shrinks: at 5% inset a 59px
   circle sits over the start of every line of body text on a 390px screen. */
@media (max-width:639px){
  .vb-top { width:48px; height:48px; inset-inline-start:auto;
            inset-inline-end:16px; bottom:16px; }
  .vb-top svg { width:17px; height:23px; }
}


/* =============================================================================
   THE FOOTER'S "ASK AI" BOX

   THE BOX IS voctos.com's, TO THE VALUE: background white at 7%, a 1px border
   at 20%, 16px corners, 20px of padding and 20px of separation from the
   "Follow us" card above it — all read off their footer with
   getComputedStyle. So are the type sizes: 16/22 for the question and 14/20
   for the line under it.

   THE 600 WEIGHT IS THEIRS AND IS THE ONE PLACE THIS FILE EXCEEDS THE THEME'S
   400 CEILING. It is kept because the ported original sets it and this box is
   a port, not a new component — the same exception, and for the same reason,
   as the one recorded on the masthead type.

   THE BUTTONS ARE NOT SKINNED IN BRAND COLOURS, and that is a deliberate
   departure. Theirs are a white button, an orange one and a near-black one,
   each carrying a single-colour glyph. This theme already ships VOCTOS's own
   engine WORDMARKS — full colour, 233x52 and friends — and a colour wordmark
   on an orange button is a clash, not a port. The buttons therefore take the
   skin the Ask panel on the front page already uses for exactly these files,
   so the two places on this site that offer the same engines look like each
   other. */
.vb-fask { margin-top:20px; padding:20px; border-radius:16px;
           background:rgba(255,255,255,.07);
           border:1px solid rgba(255,255,255,.2); }
.vb-fask-h { font-size:16px; line-height:22px; font-weight:600; color:#fff; }
.vb-fask-p { margin-top:4px; font-size:14px; line-height:20px;
             color:rgba(255,255,255,.5); }

.vb-fask-btns { display:grid; gap:10px; margin-top:16px; }

/* ONE RULE, TWO CUSTOM PROPERTIES. The three buttons differ only by their
   brand's background and foreground, and those arrive on the element from
   voctos_blog_footer_ask() — so a brand that changes its orange is one value
   in one array rather than a new selector here.

   THE 600 WEIGHT AND THE 14px ARE THE REFERENCE'S, as is the 12px radius and
   the 10px/16px padding. This is the second and last place in the stylesheet
   that exceeds the theme's 400 ceiling, and for the same documented reason as
   the box's heading above: it is a port. */
/* SCOPED TO .vb-foot, AND IT HAS TO BE. `.vb-foot a { color:#fff }` sits
   further up this file at specificity (0,1,1); an unscoped `.vb-fask-btn` is
   (0,1,0) and loses. Measured before this was caught, the ChatGPT button
   computed white text on its white background — an invisible label, not a
   subtle one. Two classes beat a class and an element, so the button wins on
   specificity rather than on source order. The hover rule needs the same
   scope for the same reason: `.vb-foot a:hover` would otherwise turn every
   label lime on its brand colour. */
.vb-foot .vb-fask-btn { display:flex; align-items:center; justify-content:center;
               gap:8px; min-height:40px; padding:10px 16px; border-radius:12px;
               background:var(--b,#fff); color:var(--f,#0a0d35);
               font-size:14px; font-weight:600; line-height:20px;
               transition:filter .15s var(--ease), translate .15s var(--ease); }
.vb-foot .vb-fask-btn:hover { color:var(--f,#0a0d35); filter:brightness(1.08);
                              translate:0 -1px; }
.vb-foot .vb-fask-btn:focus-visible { outline:2px solid var(--lime,#9BB725);
                                      outline-offset:2px; }
.vb-foot .vb-fask-btn svg { display:block; flex:none; width:18px; height:18px; }

@media (prefers-reduced-motion:reduce){
  .vb-foot .vb-fask-btn,
  .vb-foot .vb-fask-btn:hover { transition:none; translate:none; }
}
