/*
 * Vortex overrides.
 *
 * Kept in a separate file loaded AFTER the template's style.css so the
 * template stylesheet stays exactly as shipped and can still be regenerated
 * from assets/scss without losing these.
 *
 * Everything here fixes a layout the template had no component for, not taste.
 */

/* ---- Hero trust strip -------------------------------------------------
   The template's .social-wrapper-one is an icon row, so its list items sit
   flush against each other. Text items need separators and breathing room,
   otherwise the strip reads as one run-on word:
   "15 years delivering200+ systems in productionUS-based engineering"      */
/* The trust strip and its stat-cell row are gone: the client-proof section now
   uses the supplied ringed-medallion design (.vx-proofring below), so every rule
   that styled .trust-strip / .vx-statrow styled nothing. Removed rather than left
   to rot — including the mb--40 shorthand fix and the section-gap override, which
   only existed to serve that markup. */

/* ---- CTA button pairs -------------------------------------------------
   Two buttons side by side wrapped and overlapped because .button-area had
   no layout of its own.

   SCOPED deliberately: the header's search / CTA / burger group also uses
   .button-area, and an unscoped flex rule stacks it vertically and breaks
   the header. Only content CTAs get this.                                  */
.title-area-left > .button-area,
.cta-inner-content-inner-page .button-area {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px;
}

.title-area-left > .button-area .ml--15,
.cta-inner-content-inner-page .button-area .ml--15 {
    margin-left: 0 !important;
}

/* ---- Services mega-menu footer row ------------------------------------
   Needs .mega-menu-item in the selector to outrank the template's own
   `.mega-menu-item.with-list li { display:block }`, otherwise the two links
   stack instead of sitting side by side.                                   */
.mega-menu-item.mega-menu-footer {
    display: flex !important;
    gap: 40px;
    padding-top: 18px;
    margin-top: 8px;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.mega-menu-item.mega-menu-footer li {
    display: inline-block !important;
    width: auto !important;
    background: none !important;
}

/* ---- Security checklist ----------------------------------------------- */
.security-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.security-list li {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 14px;
    line-height: 1.6;
}

.security-list li i {
    margin-top: 6px;
    flex-shrink: 0;
}

/* ---- Home page cards --------------------------------------------------
   The template has no generic content card, so the first pass reused
   .single-blog-area-one for six different section types. It is a blog
   teaser: wrong padding, wrong hierarchy, and it looked identical whether
   the block was a failure mode, a practice, or a pricing model.

   .vx-card is one honest component with variants instead.                  */
.vx-card {
    height: 100%;
    display: flex;
    flex-direction: column;
    padding: 34px 30px;
    background: #fff;
    border: 1px solid rgba(16, 24, 40, 0.08);
    border-radius: 10px;
    transition: border-color 0.2s ease, transform 0.2s ease;
}

.vx-card:hover {
    border-color: rgba(16, 24, 40, 0.2);
    transform: translateY(-3px);
}

.vx-card__num {
    display: inline-block;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    opacity: 0.45;
    margin-bottom: 14px;
}

.vx-card .title {
    font-size: 22px;
    line-height: 1.35;
    margin-bottom: 14px;
}

.vx-card .disc {
    line-height: 1.75;
    margin-bottom: 12px;
}

.vx-card .disc:last-of-type {
    margin-bottom: 0;
}

/* push a card's link to the bottom so a row of cards lines up */
.vx-card__link {
    margin-top: auto;
    padding-top: 20px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
}

/* dark variant, used on the mid-page bands */
.vx-card--dark {
    background: #131a26;
    border-color: rgba(255, 255, 255, 0.1);
    color: #d7dce4;
}

.vx-card--dark .title { color: #fff; }
.vx-card--dark .disc  { color: #aab3c0; }

/* numbered process step */
.vx-step {
    height: 100%;
    display: flex;
    flex-direction: column;
    padding: 32px 28px;
    border-left: 2px solid rgba(16, 24, 40, 0.1);
}

.vx-step__num {
    font-size: 40px;
    font-weight: 700;
    line-height: 1;
    opacity: 0.15;
    margin-bottom: 16px;
}

.vx-step .title {
    font-size: 20px;
    line-height: 1.35;
    margin-bottom: 12px;
}

.vx-step .disc {
    line-height: 1.75;
}

/* engagement model — label / value pairs */
.vx-model__row {
    display: flex;
    gap: 10px;
    padding: 12px 0;
    border-top: 1px solid rgba(16, 24, 40, 0.08);
    line-height: 1.6;
}

.vx-model__row:first-of-type {
    border-top: 0;
}

.vx-model__label {
    flex: 0 0 108px;
    font-weight: 600;
    opacity: 0.6;
    font-size: 14px;
}

/* section intro copy sits narrower than the grid below it */
.vx-lede {
    max-width: 820px;
}

.vx-lede .disc {
    line-height: 1.8;
    margin-bottom: 18px;
}

/* logo row */
.vx-logos img {
    max-height: 42px;
    width: auto;
    opacity: 0.55;
    filter: grayscale(1);
    transition: opacity 0.2s ease;
}

.vx-logos img:hover {
    opacity: 1;
}

/* ---- Global CTA block -------------------------------------------------
   Own component, not the template's .cta-inner-content-inner-page, which is
   a horizontal bar and shreds a long heading into mid-word line breaks.     */
.vx-cta__inner {
    text-align: center;
    max-width: 820px;
    margin: 0 auto;
}

.vx-cta__inner .title {
    margin-bottom: 20px;
    line-height: 1.2;
    overflow-wrap: normal;
    word-break: normal;
    hyphens: none;
}

.vx-cta__inner .disc {
    margin-bottom: 16px;
    line-height: 1.7;
}

.vx-cta__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 16px;
    margin-top: 28px;
}

.vx-cta__reassure {
    margin-top: 22px;
    opacity: 0.7;
}


/* ---- Counter rings (x-vx.counters :rings) --------------------------------
   The supplied design: a figure beside a circular ring with an icon inside it.
   The ring, its animation and the row layout are the template's own
   (.progress-circle-main-wrapper, style.css:18565; main.js:1553 tweens
   stroke-dashoffset when the row scrolls into view). Only the sizing below is
   ours, and it exists because two of the template's assumptions do not hold.

   First, the ring is sized `width: 100%; max-width: 140px`, so it takes
   whatever the flex row leaves it. The reference row holds three items; ours
   holds four, which squeezed the ring to 107px against an icon that is 98px
   wide — the icon covered the ring and the section rendered as four grey discs
   with no rings at all. The ring is pinned to 132px here so the row's item
   count cannot decide whether the design is visible.

   Second, the template's icons carry their own grey disc: fun/01-03.svg are a
   #F2F2F2 circle with a dark glyph, which is why they sit inside the ring so
   neatly. There are only three of them. fun/04-06.svg are solid #3E484E with
   no disc, so a fourth item drew a dark blob. Rather than repeat an icon or
   ship a mismatched one, the disc is drawn here and the glyph is one of the
   site's own line icons — the same family the step circles, notch cards and
   Industries menu use.                                                       */
/* One row, four equal columns.

   The template's row is `justify-content: space-between` with each item sized
   by its own content, which works for the reference's three short labels. Ours
   are four, and the fourth reads "Massachusetts entity, domestic contracting" —
   it does not wrap, so that item measured 561px against 255px for the first.
   The four came to 1525px inside a 1305px container and the last one wrapped
   to a second row.

   Equal columns with `flex: 1 1 0` and `min-width: 0` fixes the cause rather
   than the symptom: every item gets the same width whatever its label says, and
   the label wraps inside it instead of pushing the row. nowrap then holds the
   four together, and the wrap comes back below 1200 where four across stops
   fitting at any size. */
.vx-counter-rings {
    flex-wrap: nowrap;
    gap: 24px;
}

.vx-counter-rings .single-progress-area {
    flex: 1 1 0;
    min-width: 0;
    gap: 18px;
}

/* The divider sits at right: -86px, which was measured for the template's own
   spacing. With a 24px gap it would land inside the next item. */
.vx-counter-rings .single-progress-area::after {
    right: -12px;
}

.vx-counter-rings .single-progress-circle {
    flex: 0 0 auto;
    width: 116px;
    height: 116px;
    align-items: center;
}

.vx-counter-rings svg.radial-progress {
    width: 116px;
    height: 116px;
    max-width: none;
}

/* The disc the template's own icons carry baked in. */
.vx-counter-rings .single-progress-circle::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 84px;
    height: 84px;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: #f2f2f2;
}

.vx-counter-rings .single-progress-area img {
    width: 46px;
    height: 46px;
    object-fit: contain;
    z-index: 1;
}

.vx-counter-rings .right-counter .title {
    margin-bottom: 4px;
}

.vx-counter-rings .right-counter p {
    margin-bottom: 0;
    font-size: 15px;
    line-height: 1.5;
    color: #5D666F;
}

@media only screen and (max-width: 1199px) {
    .vx-counter-rings {
        flex-wrap: wrap;
        justify-content: flex-start;
        gap: 40px 28px;
    }

    .vx-counter-rings .single-progress-area {
        flex: 0 1 auto;
    }
}

/* ---- Industries card mega menu (partials/header/vortex-nav) --------------
   Per the supplied design: a grid of soft cards, each an icon, a name and the
   line of copy that says what the sector actually covers, with one dark card
   closing it.

   Scoped to .vx-megacards-menu so the Services mega menu beside it — four
   columns of plain links, a different shape for a longer list — is untouched.

   Four columns rather than the reference's two. The reference holds five cards;
   this holds ten plus the dark one, so the panel gets tall fast — two columns
   would be six rows. Three columns fits a 734px viewport and four is one row
   shorter again, which is the margin worth having: a 1440x700 laptop is common
   and a dropdown taller than the window it drops into has nowhere to go.

   That failure mode is the one to watch if the sector list grows past twelve,
   and no test can see it — the panel is collapsed to scaleY(0) until hover, so
   its height does not exist to measure until a real cursor is over the menu.
   Check it with a screenshot, not a getBoundingClientRect.                  */
.vx-megacards-menu .wrapper {
    padding: 30px 0;
}

.vx-megacards {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
}

.vx-megacard {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 20px 22px;
    border-radius: 10px;
    background: #f6f6f6;
    text-decoration: none;
    transition: background 0.2s ease, transform 0.2s ease;
}

.vx-megacard:hover {
    background: #eeeeee;
    transform: translateY(-2px);
}

.vx-megacard__head {
    display: flex;
    align-items: center;
    gap: 12px;
}

.vx-megacard__icon {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    object-fit: contain;
}

.vx-megacard__title {
    font-family: var(--font-primary, inherit);
    font-size: 17px;
    font-weight: 700;
    line-height: 1.3;
    color: #1C2639;
}

.vx-megacard__text {
    font-size: 13px;
    line-height: 1.55;
    color: #5D666F;
}

/* The dark card. It takes the two cells left over after ten cards have filled
   three columns, which is also what the reference does with its own — the
   promo is the width of the gap, not a card that happens to be dark. */
.vx-megacard--cta {
    grid-column: span 2;
    justify-content: center;
    background: #20282D;
}

.vx-megacard--cta:hover {
    background: #2b353b;
}

.vx-megacard--cta .vx-megacard__title,
.vx-megacard--cta .vx-megacard__text {
    color: #fff;
}

.vx-megacard--cta .vx-megacard__text {
    opacity: 0.75;
}

@media only screen and (max-width: 1399px) {
    .vx-megacards {
        grid-template-columns: repeat(3, 1fr);
    }
}

@media only screen and (max-width: 1199px) {
    .vx-megacards {
        grid-template-columns: repeat(2, 1fr);
    }

    .vx-megacard--cta {
        grid-column: span 2;
    }
}

/* ---- Closing CTA over a photograph (x-vx.cta :image) ---------------------
   Opt-in. Without :image the block is what it has always been — white section,
   dark centred copy — on the thirty-eight pages that pass nothing.

   The photograph goes on a child layer, not on the section, for two reasons:
   the scrim needs somewhere to sit between the picture and the copy, and
   parallax needs something to move independently of the box clipping it.     */
.vx-cta.vx-cta--photo {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    background: #0B0F12;
}

.vx-cta__bg {
    position: absolute;
    /* Oversized on purpose, and the size is what makes the parallax visible.
       The layer travels a percentage of ITS OWN height, and it can never
       travel further than the headroom or the section shows through — so the
       headroom is the ceiling on how much movement is possible at all.

       At the 12% this shipped with, the most the photograph could move across
       a whole screen of scrolling was 64px against 1163px of travel: a
       differential of 0.055, where even a gentle parallax is 0.2 and a fixed
       background is 1.0. The effect ran correctly and could not be seen,
       which on a deliberately featureless facade is indistinguishable from it
       not running.

       The relationship is linear and worth knowing before touching either
       number: the most a layer can move is its headroom, so the strongest
       differential available is roughly 0.74 x headroom on this section.
       12% could never have exceeded 0.089 no matter what the travel was set
       to. 75% each end reaches 0.54. Past about 136% it stops reading as a
       moving background and starts reading as a fixed one.

       It costs nothing when parallax is off. */
    left: 0;
    right: 0;
    top: -75%;
    height: 250%;
    background-size: cover;
    background-position: center;
    z-index: 0;
    will-change: transform;
}

.vx-cta__bg::after {
    content: '';
    position: absolute;
    inset: 0;
    /* Same ink as the hero and band scrims. */
    background: rgba(11, 15, 18, var(--vx-cta-scrim, 0.55));
}

.vx-cta.vx-cta--photo > .container {
    position: relative;
    z-index: 1;
}

.vx-cta.vx-cta--photo .vx-cta__inner *:not(.rts-btn) {
    color: #fff;
}

/* The buttons DO get restyled here, unlike the heroes, and the reason is
   measured. The primary is #20282D, and against this background — a night
   facade under a 0.30 scrim — its own boundary comes out at 1.05:1 at the
   lightest point of the surround and 1.36:1 at the darkest. WCAG 1.4.11 asks
   3:1 of a control's boundary, so the main call to action on the page was a
   dark rectangle dissolving into a dark photograph, while the white secondary
   beside it sat at 13.56:1 and took over the hierarchy.

   Leaving .rts-btn alone is right on the heroes because their photographs are
   lighter under the scrim. It is wrong here. So the pair inverts: the primary
   takes the light fill and the emphasis, the secondary becomes an outline. */
.vx-cta.vx-cta--photo .vx-cta__actions .rts-btn.btn-primary:not(.btn-white) {
    background: #F2F2F2;
    color: #20282D;
    border: 1px solid #F2F2F2;
}

.vx-cta.vx-cta--photo .vx-cta__actions .rts-btn.btn-primary:not(.btn-white):hover {
    background: #fff;
    border-color: #fff;
    color: #20282D;
}

.vx-cta.vx-cta--photo .vx-cta__actions .rts-btn.btn-white {
    background: transparent;
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.55);
}

.vx-cta.vx-cta--photo .vx-cta__actions .rts-btn.btn-white:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: #fff;
    color: #fff;
}

/* Motion is a preference, not a decoration. With reduced motion the layer
   stays where it is and the section is a still photograph — which is exactly
   what the non-parallax version already is, so nothing else has to change. */
@media (prefers-reduced-motion: reduce) {
    .vx-cta__bg--parallax {
        transform: none !important;
        will-change: auto;
    }
}

/* Print and forced-colors drop the photograph for the same reason the hero
   does: the picture survives while the text is forced to a system colour, and
   white-on-photograph becomes black-on-photograph. */
@media print {
    .vx-cta__bg { display: none; }
    .vx-cta.vx-cta--photo { background: none; }
    .vx-cta.vx-cta--photo .vx-cta__inner *:not(.rts-btn) { color: #000; }
}

@media (forced-colors: active) {
    .vx-cta__bg { display: none; }
}

/* ---- Footer contact block --------------------------------------------- */
.footer-contact-block .disc {
    margin-bottom: 12px;
    line-height: 1.7;
}

.footer-contact-block a {
    display: inline-block;
}

/* ---- Header top strip: trust items are text, not links ----------------- */
.header-top-one-wrapper .top-nav li span {
    opacity: 0.7;
    font-size: 14px;
}

/* ---- Transparent header (home) ----------------------------------------
   The Business Agency header is a 3-up flex row: logo / nav / CTA. It was
   laid out for the demo's short labels (Home, Pages, Service, Project, Blog,
   Contact). The real nav is "Services, Industries, Work, How We Work,
   Company, Insights" — noticeably wider, so the nav ran under the logo.

   Only the spacing is adjusted. Colours, sizes and the sticky behaviour are
   the template's.                                                          */
.header-transparent-main-wrapper {
    gap: 24px;
}

.header-transparent-main-wrapper > .logo {
    flex: 0 0 auto;
}

/* Bounded to ≥1200px. Unbounded, `display: flex` here (0,2,0) beat the
   theme's own `.nav-area { display: none }` at ≤1199px (0,1,0, style.css:6173)
   and the desktop menu stayed on a phone: six labels wrapping into a 158px
   block, inside a 182px header, with the burger that replaces it sitting
   alongside. Measured on / at 390px before this bound was added. */
@media (min-width: 1200px) {
    .header-transparent-main-wrapper .nav-area {
        flex: 1 1 auto;
        display: flex;
        justify-content: center;
        min-width: 0;
    }
}

.header-transparent-main-wrapper .nav-area > ul {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    margin: 0;
}

/* A menu label is one line or the row is wrong. `flex-wrap: nowrap` above only
   stops the ITEMS wrapping onto a second row; it does nothing about the text
   inside an item, and .nav-area carries `min-width: 0`, which lets flex shrink
   each item below its own content width. The result was silent: at 1280 the
   links were handed 26-60px and broke across two and three lines — "How We
   Work" set as three — while the header grew from 106px to 160px to hold them.

   Measured 19 widths, 320 to 1920, in the audit that found this. */
.header-transparent-main-wrapper .nav-area > ul > li.main-nav > a {
    white-space: nowrap;
}

.header-transparent-main-wrapper .action-area {
    flex: 0 0 auto;
}

/* The rule that used to live here shrank the type to 15px and ADDED 12px of
   side padding to each of the six links, to "tighten" this band. The arithmetic
   went the wrong way: 15px type saves about 30px of text across the row, and
   144px of new padding costs five times that. With the 35px flex gap on top,
   the row asked for 804px inside the 681px it has at 1200 — so it wrapped.

   Removing it is the fix. At the theme's own 16px with no extra padding the
   labels measure 485px; the gap below brings the row to 625px, which fits the
   681px available at 1200 with 56px to spare, and more at every width above.
   The approved CTA label keeps its full width — nothing about the copy had to
   change to make this fit. */
@media (min-width: 1200px) and (max-width: 1499px) {
    .header-transparent-main-wrapper .nav-area > ul {
        gap: 28px;
    }
}

/* ---- Home: engagement models -------------------------------------------
   The demo's pricing block has a Monthly / Yearly toggle. Engagement models
   are not billed on a monthly-vs-yearly switch — the commercial basis IS the
   difference between the three cards — so both tabs hold the same content and
   the switcher is hidden rather than left to imply a choice that isn't there.
   Scoped to .vx-home so /demo/business-agency keeps its toggle.             */
.vx-home .rts-pricing-area-one .nav-tabs,
.vx-home .rts-pricing-area-one #myTab {
    display: none !important;
}

.vx-home .rts-pricing-area-one .row.mt--100 {
    margin-top: 40px !important;
}

/* ---- vx section components ---------------------------------------------
   Two classes used by resources/views/components/vx/. Everything else those
   components render is the template's own markup and needs no CSS here.     */

/* Wide tables (team composition, engagement models) scroll inside their own
   box. Without this the page body scrolls sideways on mobile.               */
.vx-table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.vx-table-scroll .comparison-table {
    min-width: 640px;
}

/* The counter block was lifted out of the demo-eight banner, where the
   template gives it position:absolute; bottom:-180px; left:50% so it hangs off
   the banner's lower edge. Out of that context it left the document flow
   entirely and rendered last on the page regardless of source order. Scoped to
   .vx-counters so /demo/business-website keeps the original behaviour.       */
.vx-counters.rts-counter-up-area-eight {
    position: static;
    inset: auto;
    transform: none;
}

/* The banner version reserved height for the image sitting behind it. */
.vx-counters .counter-up-area-eight-banner-bottom {
    min-height: 0;
    height: auto;
}

.vx-counter-row {
    position: static;
    display: flex;
    flex-wrap: wrap;
    gap: 40px 60px;
    padding: 0;
    background: none;
}

.vx-counter-row .single-counter-area-eight {
    flex: 1 1 180px;
}

/* Demo eight puts this block on a dark banner, so the template colours the
   figure white and the label pale grey. On a light section both disappeared. */
.vx-counters .counter.title {
    color: #131a26;
    font-size: 44px;
    line-height: 1.1;
    margin-bottom: 6px;
}

.vx-counters .single-counter-area-eight .business {
    color: #5b6675;
    font-size: 15px;
    line-height: 1.5;
    text-transform: none;
    letter-spacing: 0;
}

/* ---- Home: hero opens full bleed ----------------------------------------
   Removed a 120px top pad that was added while home briefly ran the solid
   shared header, which occupies real space. The transparent header is
   position:absolute and overlaps the slide by design, so reserving room for
   it pushes the hero down and reintroduces the gap the demo never had.
   Left as a note rather than deleted silently: if home ever moves back to the
   solid header, the padding has to come back with it.                       */

/* ---- Brand lockup -------------------------------------------------------
   The real Vortex mark, supplied Aug 2026, replacing the interim text
   wordmark that stood in while every SVG in assets/images/logo/ was still the
   Invena template's own. Two files, not one: the white variant for the three
   dark grounds (hero photograph, #20282d header bar, #20282d footer) and the
   dark variant for .side-bar, which is background-color:white and is the one
   surface where the white mark would be invisible.

   Sized by height with width:auto, because the lockup is 3.34:1 and the bar
   constrains vertical space, not horizontal. The intrinsic width/height on
   each <img> matches the rendered size so the header does not reflow while
   the file loads.                                                           */
.vx-logo {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

.vx-logo img {
    display: block;
    height: 40px;
    width: auto;
}

/* The footer column and the blog contact widget both have the width the
   header bar does not, and the footer is the last place on the page the
   company is named. */
.vx-logo--footer img { height: 50px; }

@media (max-width: 575px) {
    .vx-logo img { height: 32px; }
    .vx-logo--footer img { height: 42px; }
}

/* ---- Contact form -------------------------------------------------------
   The template's contact form was three unlabelled inputs. The brief asks for
   twelve fields including selects and a checkbox, so labels, selects and the
   validation states need styling the template never had.                    */
/* Two columns. Eleven stacked full-width rows made a 1,100px-tall form where
   "Phone" and "Job title" were each 749px wide — a field ten times longer than
   anything anyone types into it. Short fields pair up; the message, the NDA
   question and the closing rows keep the full width. */
.vx-contact-form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 20px 24px;
    align-items: start;
}

.vx-field {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.vx-field--full,
.vx-contact-form .vx-checkbox,
.vx-contact-form > button,
.vx-contact-form > .vx-form-note {
    grid-column: 1 / -1;
}

/* The submit button sat flush against the field above it: the grid gap is the
   only separation, and a button is not a field. */
.vx-contact-form > button {
    justify-self: start;
    margin-top: 12px;
}

.vx-contact-form > .vx-form-note {
    margin: 0;
    max-width: 62ch;
}

@media only screen and (max-width: 767px) {
    .vx-contact-form {
        grid-template-columns: 1fr;
    }
}

.vx-contact-form label {
    display: block;
    /* margin-top removed: the grid gap now does the vertical rhythm, and the
       old 18px on top of a 20px row gap double-spaced every field. */
    margin: 0 0 6px;
    font-size: 14px;
    font-weight: 600;
    color: #131a26;
}

.vx-contact-form label .optional {
    font-weight: 400;
    color: #7a8593;
}

.vx-contact-form input[type="text"],
.vx-contact-form input[type="email"],
.vx-contact-form input[type="tel"],
.vx-contact-form select,
.vx-contact-form textarea {
    width: 100%;
    padding: 12px 16px;
    border: 1px solid rgba(16, 24, 40, 0.16);
    border-radius: 6px;
    background: #fff;
    font-size: 15px;
    line-height: 1.5;
}

.vx-contact-form select {
    appearance: none;
    background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b6675' stroke-width='2'%3e%3cpath d='M6 9l6 6 6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right 14px center;
    background-size: 18px;
    padding-right: 44px;
}

.vx-contact-form textarea { min-height: 150px; resize: vertical; }

.vx-contact-form input:focus,
.vx-contact-form select:focus,
.vx-contact-form textarea:focus {
    outline: 2px solid #131a26;
    outline-offset: 1px;
    border-color: transparent;
}

.vx-checkbox {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 20px 0 4px;
}

.vx-checkbox input {
    margin-top: 3px;
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    accent-color: #131a26;
}

/* The template draws its own 15px square on the label's ::before. Next to a
   real checkbox that reads as two controls, and only one of them responds to
   a click. The native box stays (it shows its own checked state and is
   keyboard accessible); the decoration goes. */
.vx-checkbox label::before,
.vx-checkbox label::after {
    content: none !important;
}

.vx-checkbox label {
    margin: 0;
    padding-left: 0;
    font-weight: 400;
    cursor: pointer;
}

.vx-form-status,
.vx-form-errors {
    padding: 14px 18px;
    border-radius: 6px;
    margin-bottom: 20px;
    font-size: 15px;
}

.vx-form-status {
    background: #e3f0e9;
    border: 1px solid rgba(47, 111, 78, 0.3);
    color: #2f6f4e;
}

.vx-form-errors {
    background: #f7e5e5;
    border: 1px solid rgba(155, 44, 44, 0.3);
    color: #9b2c2c;
}

.vx-form-errors ul { margin: 0; padding-left: 18px; }

/* Post-submission confirmation is a list, not a single line. */
.vx-form-status ul { margin: 10px 0 0; padding-left: 18px; }
.vx-form-status li { margin-bottom: 8px; line-height: 1.6; }
.vx-form-status li:last-child { margin-bottom: 0; }
.vx-form-status p { margin: 0; }

/* ---- Home vertical rhythm ------------------------------------------------
   Four spacing corrections from a 1440-wide review. All desktop only: the
   template already re-tunes every one of these numbers below 1200/992/767
   (section gaps drop to 80/60, the service card margin to 10, paragraphs to
   20), and those smaller values are not the ones being complained about.
   Overriding them unconditionally would fix a desktop gap by breaking a
   phone one.                                                                */
@media only screen and (min-width: 992px) {

    /* 1. Client-proof strip had no top gap at all. The template ships it as
          rts-section-gapBottom (padding-bottom only), which works when the
          section above it has bottom padding to donate. The hero has none —
          it is a fixed-height frame — so the "Client proof" badge sat flush
          against the bottom edge of the photograph, 0px. */
    .vx-home .rts-brand-area {
        padding-top: 90px;
    }

    /* 2. Failure modes / industries. Two separate paddings stacked under a
          single row of cards: the card's own 35px bottom margin (which the
          template itself drops to 10px under 1200px) plus the section's
          120px. 155px of dead space under three short cards. */
    .vx-home .rts-service-area {
        padding-bottom: 90px;
    }

    .vx-home .rts-service-area .single-feature-service-wrapper-8 {
        margin-bottom: 0;
    }

    /* 3. Process steps. Five steps at 197px each (40px padding top and
          bottom around ~117px of content) plus 40px between them ran the
          column to 1385px.

          Padding was the first cut, to 28px, and it was not enough. The
          bigger driver is the 110px gap the template puts between the icon
          and the card: it squeezed the text to 318px, which wrapped every
          step's copy onto a third line. Closing the gap to 56px widens the
          card to 472px and two of the five steps drop to two lines — the
          height comes off the text, not off the padding.

          The dashed connector is drawn as a 150px ::before on .number-main
          reaching back under the icon, so it has to come in with the gap or
          it hangs out past the icon on the left.

          Second pass took it further: gap 36px, which widens the card to
          518px and leaves only the longest step on three lines, plus a
          tighter type scale. The 110px icon came down to 84px with it —
          against a 104px card the original circle was taller than the card
          it labelled.

          Step 197 -> 104px (125px for the one three-line step; the spread
          is the copy). Column 1385 -> 861px, section 1625 -> 1101px. All
          five steps now read in a single 1440x732 screen. */
    .vx-home .working-process-one .single-working-process-one {
        gap: 36px;
        margin-bottom: 16px;
    }

    .vx-home .working-process-one .single-working-process-one .left .icon {
        height: 84px;
        width: 84px;
    }

    .vx-home .working-process-one .single-working-process-one .inner-content {
        /* Left padding stays wide: the numbered circle is absolutely
           positioned over that edge, 25px of it inside the card, and text
           running under it would collide. */
        padding: 16px 28px 16px 44px;
    }

    .vx-home .working-process-one .single-working-process-one .inner-content .title {
        font-size: 18px;
        line-height: 24px;
        margin-bottom: 6px;
    }

    /* 15/26 is a reading measure, loose for two lines in a card. 14/21 is
       1.5 and still comfortable at this width. */
    .vx-home .working-process-one .single-working-process-one .inner-content p {
        font-size: 14px;
        line-height: 21px;
    }

    .vx-home .working-process-one .single-working-process-one .inner-content .number-main::before {
        width: 50px;
        left: -50px;
    }

    /* 4. About / "The real problem". The empty band on the left is a symptom,
          not the cause: the row is align-items-center, so the 535px image
          pair floats in the middle of whatever the text column measures, and
          the text column drove the section to 1153px. Shrinking the images
          would widen the band, not close it. Tightening the text column
          closes it from both ends at once — the section gets shorter AND the
          centring slack shrinks with it.

          40px between paragraphs is the template's global p rhythm, right
          for standalone body copy, loose for three paragraphs that read as
          one argument. The container's pb--40 is demo padding on top of the
          section's own 120px. Section 1153 -> 1005px. */
    .vx-home .rts-about-area-6 {
        padding-top: 90px;
        padding-bottom: 90px;
    }

    .vx-home .rts-about-area-6 .container.pb--40 {
        padding-bottom: 0;
    }

    .vx-home .about-6-inner-content-content p.disc {
        margin-bottom: 24px;
    }
}

/* ---- Case studies -------------------------------------------------------
   /work and /work/{slug}. The template's project-details design carries a
   title, paragraphs and images; a case study also has headed sections and
   lists, which the template has no styles for. These are the missing pieces,
   nothing more.                                                             */

.vx-cs-subtitle {
    color: rgba(255, 255, 255, .78);
    margin: 12px 0 0;
}

.vx-case-study .vx-cs-heading {
    font-size: 26px;
    line-height: 1.3;
    margin: 44px 0 16px;
}

.vx-case-study .vx-cs-heading:first-child {
    margin-top: 0;
}

.vx-case-study .vx-cs-list {
    list-style: none;
    padding: 0;
    margin: 0 0 28px;
}

.vx-case-study .vx-cs-list li {
    position: relative;
    padding-left: 26px;
    margin-bottom: 14px;
    color: var(--color-body);
    line-height: 26px;
}

/* Marker drawn rather than a list bullet: the template resets list styling
   globally, so a real disc renders inconsistently across browsers here. */
.vx-case-study .vx-cs-list li::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 10px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--color-primary);
}

.vx-case-study .vx-cs-list li strong {
    color: #1C2539;
}

.vx-case-study .vx-cs-group-label {
    font-weight: 700;
    color: #1C2539;
    margin: 0 0 10px;
}

/* A study can carry 1 to 4 figures. The template's counter row assumes four
   and splits the width between them, which strands a single figure in a
   quarter-width column, so the row is left-packed instead. */
.vx-cs-counters {
    flex-wrap: wrap;
    justify-content: flex-start;
}

/* The template draws the divider as a right border on each figure, so a
   study with one figure gets a rule with nothing on the other side of it. */
.vx-cs-counters .single-counter-up-area:last-child {
    border-right: 0;
}

/* Listing cards -----------------------------------------------------------
   .vx-card with a full-bleed image on top. The template's .project-style-one
   was the obvious choice and the wrong one: its .inner-content is absolutely
   positioned over the photograph and sized for a title plus a one-word
   category, so a summary line and a figure spill across the image.           */

.vx-card--media {
    padding: 0;
    overflow: hidden;
}

.vx-card__media {
    display: block;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    background: #eef1f5;
}

.vx-card__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .4s ease;
}

.vx-card--media:hover .vx-card__media img {
    transform: scale(1.04);
}

.vx-card__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    padding: 26px 26px 28px;
}

.vx-card--media .title {
    font-size: 20px;
    line-height: 1.35;
    margin-bottom: 12px;
}

.vx-card--media .title a {
    color: inherit;
}

.vx-card--media .disc {
    font-size: 15px;
    line-height: 25px;
}

.vx-cs-card__metric {
    margin: 16px 0 0;
    font-weight: 700;
    font-size: 22px;
    color: #1C2539;
    line-height: 1.2;
}

.vx-cs-card__metric span {
    display: block;
    font-weight: 400;
    font-size: 14px;
    color: var(--color-body);
    margin-top: 2px;
}

/* Detail slider ------------------------------------------------------------
   The template's project-details slider hands the slide over to the image at
   its natural size. Its own stock is 1920 wide so it happens to fill; ours is
   1260 and 946, which left one slide short of the container and the next one
   shorter again — the frame changed size as the slider advanced.

   A fixed ratio on the slide with object-fit: cover makes every slide the same
   size whatever gets dropped in, which also means a replacement image cannot
   silently change the page height. 21:10 is close to the template's own
   1305x570 proportion.                                                       */
.vx-case-study .mySwiper-pd-slider .swiper-slide {
    aspect-ratio: 21 / 10;
    overflow: hidden;
    background: #eef1f5;
}

.vx-case-study .mySwiper-pd-slider .swiper-slide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* 21:10 across a 345px phone is a 164px letterbox — too thin to read a
   screenshot or a diagram in, which is what these images will be. */
@media only screen and (max-width: 767px) {
    .vx-case-study .mySwiper-pd-slider .swiper-slide {
        aspect-ratio: 3 / 2;
    }
}

/* The template sets white-space: nowrap on .comparison-table (style.css
   ~14693). Its own tables hold one or two words per cell. Ours hold prose —
   "Commercials, escalation, your single point of contact" — and nowrap made
   the How We Work table 4560px wide inside a 345px scroller. Reachable, since
   .vx-table-scroll scrolls, but thirteen screens of sideways scrolling to read
   one row is not reading.

   Cells wrap; the 640px min-width still stops the columns collapsing. */
.vx-table-scroll .comparison-table th,
.vx-table-scroll .comparison-table td {
    white-space: normal;
}

/* ---- Contact page layout ------------------------------------------------
   Two fixes to the two-column section.

   1. The details card is ~700px of content beside a ~1,500px form, so the
      right half of the page went empty for the whole lower form. Sticky keeps
      the address, email and phone in view while someone fills the form in,
      which is when they are most likely to want the phone number.
   2. The response-times block puts four lines against one icon, so its icon
      sat centred against the middle line instead of the first. Top-align it,
      as the single-line rows already read.                                  */
@media only screen and (min-width: 992px) {
    .rts-contact-area-in-page .contact-info-area-wrapper-p {
        position: sticky;
        top: 120px;
    }
}

.rts-contact-area-in-page .single-contact-info {
    align-items: flex-start;
}

.rts-contact-area-in-page .single-contact-info .icon {
    flex-shrink: 0;
}

/* "Before you get in touch" is a two-line heading beside a three-item list,
   and the checklist component centres its row, so the heading floated halfway
   down the list instead of starting level with it.

   Scoped to the contact page body class: the same component on home pairs the
   heading with a tall image stack, where centring is right. */
.contact-page .rts-about-area-6 .row.align-items-center {
    align-items: flex-start !important;
}

/* Details panel is dark (#20282d) but the heading and body copy inside it
   still carry the light-page colours. Measured against that background:

     "Vortex Web Innovate" h2   #1c2539   1.02:1   invisible
     eyebrow "Details"          #5d666f   2.56:1   fails AA
     address, response times    #5d666f   2.56:1   fails AA

   The email and phone were white and fine, which is why this read as a panel
   with two lines in it. AA wants 4.5:1 for body text and 3:1 for large. */
.rts-contact-area-in-page .contact-info-area-wrapper-p .title {
    color: #fff;
}

.rts-contact-area-in-page .contact-info-area-wrapper-p .pre {
    color: #c7cdd6;
}

.rts-contact-area-in-page .contact-info-area-wrapper-p .info-wrapper p {
    color: #c7cdd6;
}

/* A 90+90 override used to sit here, tightening the one seam between the
   trust list and the closing CTA on /contact because 240px of nothing read as
   a gap in the page. It is gone: the site now runs one rhythm on every page
   but home, and a single hand-tuned seam is exactly what that rhythm is for.
   See "Uniform section rhythm" at the end of this file. */

/* ---- Sticky statement rows ----------------------------------------------
   style.css ~11318 gives section.main-wrapper-sticky a fixed height: 735px,
   sized for the demo's four rows. Nothing clips the overflow, so any extra
   rows spill straight out of the section and paint over whatever follows.

   Every service page carries five to nine of these rows — /services/seo has
   nine, roughly 1,780px of content in a 735px box — so on all of them the
   pinned rows sat on top of the next section. Even the template's own four
   rows come to about 790px and overflow by ~57px.

   Auto height. The rows still pin at top:150px and stack; sticky just stops
   at a section boundary that now matches the content.                       */
section.main-wrapper-sticky {
    height: auto;
}

/* ---------------------------------------------------------------------------
   Why-us slider controls (homepage)

   The template styles these arrows via .title-area-between-wrapper
   .swiper-button-next/-prev, but this slider cannot use those class names:
   main.js binds them document-wide, and because these controls sit outside
   the swiper element they would drive the case-study slider too. Same look,
   our own selectors.
   --------------------------------------------------------------------------- */
.title-area-between-wrapper .vx-testi-nav {
    display: flex;
    align-items: center;
    gap: 15px;
}

/* Swiper adds .swiper-pagination-fraction to this element at runtime, which
   pulls in the template's absolute positioning (right: 50px, top: 40%) and
   drops the counter on top of the arrows. Reset it back into the flex row. */
.title-area-between-wrapper .vx-testi-nav .vx-testi-fraction,
.title-area-between-wrapper .vx-testi-nav .vx-testi-fraction.swiper-pagination-fraction {
    position: static;
    top: auto;
    right: auto;
    left: auto;
    bottom: auto;
    width: auto;
    max-width: none;
    transform: none;
}

.vx-testi-nav .vx-testi-fraction {
    font-weight: 700;
    color: #5D666F;
    white-space: nowrap;
}

.vx-testi-nav .vx-testi-fraction .swiper-pagination-current {
    color: #1b1b1b;
}

.vx-testi-nav .vx-testi-next,
.vx-testi-nav .vx-testi-prev {
    width: 40px;
    height: 35px;
    padding: 0;
    background: #F2F2F2;
    border-radius: 8px;
    border: 1px solid transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: var(--transition, 0.3s);
}

.vx-testi-nav .vx-testi-next i,
.vx-testi-nav .vx-testi-prev i {
    color: #1C2539;
    transition: var(--transition, 0.3s);
}

.vx-testi-nav .vx-testi-next:hover,
.vx-testi-nav .vx-testi-prev:hover {
    background: var(--color-primary);
    border-color: var(--color-primary);
}

.vx-testi-nav .vx-testi-next:hover i,
.vx-testi-nav .vx-testi-prev:hover i {
    color: #fff;
}

.vx-testi-nav .swiper-button-disabled {
    opacity: 0.5;
    cursor: default;
}

/* ---------------------------------------------------------------------------
   Failure-modes tab panes (homepage)

   The template's .thumbnail-area-wrapper-inner-6 carries only
   "padding-left: -150px", which is invalid and therefore does nothing, so the
   image renders at its natural width. That works on the template's own page
   because every pane there uses the same 913px-wide photo. Ours uses three,
   so the pane's ratio is pinned and the image fills it; without this the
   panes would jump height as you switch tabs.
   --------------------------------------------------------------------------- */
.rts-business-goal-area-wrapper-6 .tab-content,
.rts-business-goal-area-wrapper-6 .tab-pane.active,
.rts-business-goal-area-wrapper-6 .thumbnail-area-wrapper-inner-6 {
    height: 100%;
}

.rts-business-goal-area-wrapper-6 .thumbnail-area-wrapper-inner-6 {
    /* Was aspect-ratio: 913/625, which sized the pane from its own width and
       left it shorter than the card column beside it — the cards ran past the
       photograph. The row stretches now, so the pane takes the column's height
       and the photo crops to fit. min-height keeps it from collapsing on the
       narrow screens where the cards stack short. */
    min-height: 420px;
    overflow: hidden;
}

/* Stacked, there is no column to match, so the frame goes back to its ratio. */
@media only screen and (max-width: 991px) {
    .rts-business-goal-area-wrapper-6 .thumbnail-area-wrapper-inner-6 {
        aspect-ratio: 913 / 625;
        min-height: 0;
        height: auto;
    }
}

.rts-business-goal-area-wrapper-6 .thumbnail-area-wrapper-inner-6 img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ---------------------------------------------------------------------------
   Compact check row (homepage security section)

   .single-feature-service-wrapper-8 is a card: a 60px icon box beside a
   heading and a paragraph. The security column uses it for seven single-line
   points, where each row cost 95px and the column ran 665px against a 383px
   left column — the points drifted well below the copy they sit beside.

   Scoped to a modifier on purpose. x-vx.checklist renders this same class on
   25 other pages and 68 of those items carry a title, where the card sizing
   is the right call. Only rows tagged vx-checkrow get the tighter treatment.
   --------------------------------------------------------------------------- */
.vx-checkrow {
    align-items: flex-start;
    gap: 14px;
    margin-bottom: 20px;
}

.vx-checkrow:last-child {
    margin-bottom: 0;
}

.vx-checkrow .icon {
    width: 32px;
    height: 32px;
    min-width: 32px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.vx-checkrow .icon i {
    font-size: 15px;
}

/* The row is flex, but .inner-wrapper never grows (flex: 0 1 auto) and the
   template caps .disc at max-width: 85%. Together they squeezed the text box
   to 238px inside a 749px row, so every one-line point wrapped onto two and
   each row cost double its height. Let the text have the room it already has. */
.vx-checkrow .inner-wrapper {
    flex: 1 1 auto;
    min-width: 0;
}

.vx-checkrow .inner-wrapper .disc {
    max-width: none;
    margin-bottom: 0;
    line-height: 1.6;
}

/* ---- Page hero: stop the copy spilling out of a fixed-height band ---------
   .rts-breadcrumb-area (style.css:11021) is a FIXED height — 477px, 370px at
   ≤575px — with display:flex and align-items:center. Fixed, not min-height.

   Every service page's hero copy is taller than that on a phone. Measured at
   390px: /services/api-integrations-and-development renders 581px of copy into
   the 370px band, and centred overflow spills BOTH directions. The result on
   the live site today: the h1 starts 106px above the band, printing over the
   85px site header, and both CTAs hang below it into the next section.

   min-height keeps the roomy band on pages whose copy is short, and lets it
   grow on the ones whose copy is not. flex-start plus real padding means the
   copy can never travel upward again, whatever the headline length.

   Kept to ≤991px on purpose: at 1440 the copy measures 259-326px against 477px
   and the centred composition is doing hierarchy work worth keeping.          */
@media only screen and (max-width: 991px) {
    .rts-breadcrumb-area {
        height: auto;
        min-height: 477px;
        align-items: flex-start;
        padding-top: 60px;
        padding-bottom: 60px;
    }
}

@media only screen and (max-width: 575px) {
    .rts-breadcrumb-area {
        min-height: 370px;
        padding-top: 48px;
        padding-bottom: 48px;
    }
}

/* ---- Blog card thumbnail: reserve the box before the image arrives --------
   style.css:17169 gives .blog-single-post-listing .thumbnail overflow:hidden,
   and its img width:100% with no height and no aspect. Before the image loads
   its intrinsic size is unknown, so the box is 0px tall.

   That is fine for an eager image and fatal for a lazy one: a zero-height box
   never intersects the viewport, so the image never loads, so it never gets a
   height. Deadlock. Adding loading="lazy" to these cards blanked every one of
   them — caught on staging in a browser, because the file served 200 and only
   the rendered page showed nothing.

   Pinning the aspect fixes the deadlock and removes the layout shift the
   images caused when they landed. The seeded posts are all 1280x720.          */
.blog-single-post-listing .thumbnail {
    aspect-ratio: 16 / 9;
}

.blog-single-post-listing .thumbnail img {
    height: 100%;
    object-fit: cover;
}

/* ---- Pill rows (x-vx.industry-pills) -------------------------------------
   A centred, self-wrapping list. Each item is a fully rounded bar with a
   circular medallion overhanging its left edge, so the row reads as a track
   with a stop on it rather than as a card.

   The medallion is taller than the bar on purpose — that overhang is what
   makes the shape read. The bar carries left padding equal to the medallion's
   width so the copy clears it at every size.

   Widths are content-sized (inline-flex), not a grid, so rows stagger
   naturally: long titles take more room and the row breaks where it needs to.
   That staggering is the design, not an accident of the copy.                */
.vx-pillrow__head {
    text-align: center;
    margin-bottom: 60px;
}

.vx-pillrow__eyebrow {
    display: inline-block;
    padding: 12px 26px;
    margin-bottom: 24px;
    border: 1px solid #e4e6e8;
    border-radius: 8px;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #5D666F;
}

.vx-pillrow__head .title {
    margin-bottom: 0;
}

.vx-pillrow__head .disc {
    max-width: 720px;
    margin: 18px auto 0;
}

.vx-pillrow {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    /* Measured: 61px between pills at 2x, on both axes. */
    gap: 30px;
}

.vx-pill {
    /* Every number here is measured off the supplied design, not chosen.
       The reference was sampled at 2x DPR: bar 186px tall, medallion 188px
       across, 61px between pills on both axes, text starting 65px after the
       medallion and ending 60px before the bar. Halved, that is the block
       below.

       The medallion is the SAME height as the bar, capping its left end — it
       does not overhang. An earlier pass made it overhang by 36px, which was
       right for the pill this component used to render: that one carried a
       title plus a two-line blurb, so the bar was tall and the circle had to
       grow past it to read as a medallion at all. The blurb has moved to each
       industry's own page and the bar is a single line again, so the circle
       sits flush, as the reference has it. */
    --vx-pill-h: 93px;

    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: var(--vx-pill-h);
    /* Left clears the medallion and adds the measured 32px; right is the
       measured 30px from the bar's widest point. */
    padding: 0 30px 0 calc(var(--vx-pill-h) + 32px);
    border-radius: 999px;
    background: #f6f6f6;
    text-decoration: none;
    transition: background 0.25s ease, transform 0.25s ease;
}

a.vx-pill:hover {
    background: #eeeeee;
    transform: translateY(-2px);
}

.vx-pill__media {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: var(--vx-pill-h);
    height: var(--vx-pill-h);
    border-radius: 50%;
    overflow: hidden;
    background: #20282D;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.vx-pill__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.vx-pill__num {
    font-family: var(--font-primary, inherit);
    font-size: 26px;
    font-weight: 700;
    color: #fff;
    line-height: 1;
}

.vx-pill__title {
    font-family: var(--font-primary, inherit);
    /* 45px of ink from ascender to descender at 2x, which is a 24px face. */
    font-size: 24px;
    font-weight: 700;
    line-height: 1.2;
    color: #1C2639;
    /* The bar hugs its title. Wrapping would make every pill the width of the
       longest one and the rows would stop staggering, which is the whole
       shape of the reference: 2 pills, then 3, then 2, each row centred. */
    white-space: nowrap;
}

@media only screen and (max-width: 1199px) {
    .vx-pill {
        --vx-pill-h: 84px;
        padding: 0 26px 0 calc(var(--vx-pill-h) + 26px);
    }

    .vx-pill__title { font-size: 21px; }
}

@media only screen and (max-width: 767px) {
    .vx-pillrow {
        gap: 20px;
    }

    .vx-pill {
        --vx-pill-h: 72px;
        /* One per row on a phone. Below this width the longest titles cannot
           stay on one line, and a nowrap pill would overflow the viewport. */
        width: 100%;
        padding: 0 22px 0 calc(var(--vx-pill-h) + 20px);
    }

    .vx-pill__title {
        font-size: 18px;
        white-space: normal;
    }

    .vx-pill__num { font-size: 22px; }
}

/* ---- Notched card grid (x-vx.notch-cards) --------------------------------
   Four-up cards whose bottom-right corner is clipped, per the supplied design.

   The notch is a clip-path rather than a rotated pseudo-element sitting over
   the corner. A pseudo-element has to be painted the same colour as whatever
   is behind the card, which breaks the moment the section sits on anything but
   white. clip-path cuts the real box, so the border follows the diagonal and
   the card stays transparent underneath.                                     */
.vx-notchgrid__head {
    text-align: center;
    max-width: 760px;
    margin: 0 auto 60px;
}

.vx-notchgrid__eyebrow {
    display: block;
    margin-bottom: 14px;
    font-size: 16px;
    color: #5D666F;
}

.vx-notchgrid__head .title {
    margin-bottom: 0;
}

.vx-notchgrid__head .disc {
    margin: 18px 0 0;
}

.vx-notchgrid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 24px;
}

.vx-notchcard {
    --vx-notch: 42px;

    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 44px 28px 40px;
    border: 1px solid #e4e6e8;
    background: transparent;
    clip-path: polygon(
        0 0,
        100% 0,
        100% calc(100% - var(--vx-notch)),
        calc(100% - var(--vx-notch)) 100%,
        0 100%
    );
    transition: border-color 0.25s ease, transform 0.25s ease;
}

.vx-notchcard:hover {
    border-color: #c9ced3;
    transform: translateY(-3px);
}

/* The tile repeats the card's notch at a smaller radius, which is what ties
   the two shapes together in the reference. */
.vx-notchcard__tile {
    --vx-notch: 14px;

    display: flex;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 96px;
    margin-bottom: 28px;
    background: #f4f5f6;
    clip-path: polygon(
        0 0,
        100% 0,
        100% calc(100% - var(--vx-notch)),
        calc(100% - var(--vx-notch)) 100%,
        0 100%
    );
}

.vx-notchcard__tile img {
    width: 44px;
    height: 44px;
    object-fit: contain;
}

.vx-notchcard__num {
    font-size: 26px;
    font-weight: 700;
    color: #20282D;
    line-height: 1;
}

.vx-notchcard__title {
    font-size: 21px;
    font-weight: 700;
    line-height: 1.3;
    color: #141416;
    margin-bottom: 14px;
}

.vx-notchcard__text {
    font-size: 15px;
    line-height: 1.65;
    color: #5D666F;
    margin-bottom: 0;
}

.vx-notchcard__link {
    margin-top: 22px;
    font-weight: 600;
    color: #20282D;
}

@media only screen and (max-width: 1199px) {
    .vx-notchgrid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media only screen and (max-width: 575px) {
    .vx-notchgrid {
        grid-template-columns: 1fr;
        gap: 20px;
    }

    .vx-notchcard {
        --vx-notch: 32px;
        padding: 34px 24px 30px;
    }

    .vx-notchcard__tile {
        width: 76px;
        height: 76px;
        margin-bottom: 22px;
    }
}

/* ---- Circled steps (x-vx.steps) ------------------------------------------
   A row of items, each a filled disc inside a dashed ring, label beneath.
   Alternate items sit lower: that stagger is what stops the row reading as a
   table, and it is the only thing carrying rhythm when the items have no
   sub-text (these are plain strings, so there is nothing under the label).    */
.vx-steps__head {
    text-align: center;
    max-width: 780px;
    margin: 0 auto 70px;
}

.vx-steps__eyebrow {
    display: block;
    margin-bottom: 14px;
    font-size: 16px;
    color: #5D666F;
}

.vx-steps__head .title { margin-bottom: 0; }
.vx-steps__head .disc  { margin: 18px 0 0; }

/* The section sits on a light band, as in the reference. Without it the white
   discs are invisible against the page and only the dashed rings read. */
.rts-about-area-6:has(> .container > .vx-steps),
.rts-about-area-6:has(> .container > .row + .vx-steps) {
    background: #f7f8f9;
}

.vx-steps {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 48px 28px;
    align-items: start;
}

.vx-step-circle {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

/* The stagger. nth-child(even) rather than a fixed list so it survives any
   item count. */
.vx-steps .vx-step-circle:nth-child(even) {
    margin-top: 56px;
}

.vx-step-circle__ring {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 184px;
    height: 184px;
    margin-bottom: 26px;
    border: 2px dashed #d3d7db;
    border-radius: 50%;
}

.vx-step-circle__disc {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 140px;
    height: 140px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 10px 30px rgba(16, 24, 40, 0.06);
}

.vx-step-circle__disc img {
    width: 54px;
    height: 54px;
    object-fit: contain;
}

.vx-step-circle__num {
    font-size: 30px;
    font-weight: 700;
    color: #20282D;
    line-height: 1;
}

.vx-step-circle__label {
    font-size: 17px;
    font-weight: 700;
    line-height: 1.4;
    color: #141416;
    /* No max-width. The reference's labels are two or three words ("Make a
       Plan"); these are full capability names running to 60 characters, and a
       15rem cap broke "Telehealth platforms (scheduling, video, documentation,
       billing)" over six lines. The grid column is the right measure. */
}

.vx-step-circle__text {
    margin-top: 10px;
    font-size: 15px;
    line-height: 1.6;
    color: #5D666F;
    /* The grid column is the measure, the same conclusion the label above
       reached. This was max-width: 15rem, which looks like 240px and is not:
       this template sets a 10px root, so 15rem computes to 150px inside a
       305px column. Half the cell went unused and a 153-character body broke
       over nine lines at about seventeen characters each.

       That cap was invisible on the page this component was built for, whose
       items are plain strings with no body text at all. It only surfaced when
       the checklist blocks arrived carrying real sentences. */
    max-width: 100%;
}

@media only screen and (max-width: 1199px) {
    .vx-steps { grid-template-columns: repeat(2, 1fr); }
    .vx-steps .vx-step-circle:nth-child(even) { margin-top: 0; }
}

@media only screen and (max-width: 575px) {
    .vx-steps { grid-template-columns: 1fr; gap: 36px; }

    .vx-step-circle__ring { width: 148px; height: 148px; }
    .vx-step-circle__disc { width: 112px; height: 112px; }
}

/* ---- Split with check rows (x-vx.split-checks) ---------------------------
   Image left in an organic blob, heading and checks right.

   The blob is a border-radius with four different corner pairs rather than a
   clip-path or an SVG mask: it keeps the element a normal box for layout, and
   object-fit still governs the photo inside it.                              */
.vx-splitchecks {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 70px;
    align-items: center;
}

.vx-splitchecks__media {
    overflow: hidden;
    border-radius: 62% 38% 46% 54% / 46% 42% 58% 54%;
    aspect-ratio: 5 / 6;
}

.vx-splitchecks__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.vx-splitchecks__eyebrow {
    display: block;
    margin-bottom: 18px;
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #20282D;
}

.vx-splitchecks__body .title {
    margin-bottom: 22px;
    line-height: 1.15;
}

.vx-splitchecks__body .disc {
    margin-bottom: 0;
    line-height: 1.8;
}

.vx-splitchecks__grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 18px;
    margin-top: 36px;
}

.vx-splitchecks__item {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 20px 24px;
    border: 1px solid #e4e6e8;
    border-radius: 10px;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.45;
    color: #141416;
}

.vx-splitchecks__item i {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    margin-top: 1px;
    border-radius: 50%;
    background: #20282D;
    color: #fff;
    font-size: 12px;
}

@media only screen and (max-width: 991px) {
    .vx-splitchecks {
        grid-template-columns: 1fr;
        gap: 44px;
    }

    .vx-splitchecks__media {
        max-width: 460px;
        margin: 0 auto;
    }
}

@media only screen and (max-width: 575px) {
    .vx-splitchecks__grid {
        grid-template-columns: 1fr;
        gap: 14px;
    }
}

/* Section link under the steps. The checklist this design replaced rendered one
   in its left column; here the layout is centred, so it closes the section. */
.vx-steps__foot {
    margin-top: 64px;
    text-align: center;
}

/* ---- Page hero background image (x-vx.hero image="...") ------------------
   Extends the home hero's scrim rather than forking a second system: same
   --vx-scrim token, same rgba(11,16,22) base, same ≤991 flat-wash breakpoint,
   same "measure, don't guess" rule. Two scrim systems on one site grade
   differently and the seam shows when you click from one page to the next.

   NOT #20282D. That is --color-primary and the primary CTA's own fill; a dark
   button on a same-coloured wash loses its edge entirely.

   No overflow:hidden here. The band is flex-centred and its copy already
   overflows on a phone, so clipping it removes both CTAs — see the min-height
   fix earlier in this file.                                                  */
.vx-hero-media {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    /* Dark from the first frame. Covers the gap before the image decodes and,
       permanently, a path that 404s — the img is decorative with alt="", so
       without this a missing file is an invisible failure on a white band. */
    background: #0B1016;
}

.vx-hero-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 42%;
    display: block;
}

/* Flat across the copy column, then a fast reveal.
   The first shape here ramped down from 0% and was measured failing: on
   logistics the h1 sat at 42% of the band over the blown-out warehouse aisle
   at alpha 0.39 and came out at 2.58:1. Ramping from the very left spends the
   scrim where no glyph is and starves the middle, where the headline actually
   sits. Holding it flat to 46% - just past the 62ch measure below - passes the
   same page at a LOWER strength (0.57), so more of the photograph survives,
   not less. */
.vx-hero-media::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
        90deg,
        rgba(11, 16, 22, var(--vx-scrim, 0.62)) 0%,
        rgba(11, 16, 22, var(--vx-scrim, 0.62)) 46%,
        rgba(11, 16, 22, calc(var(--vx-scrim, 0.62) * 0.18)) 72%,
        rgba(11, 16, 22, 0) 100%
    );
}

.rts-breadcrumb-area.vx-hero-has-media > .container {
    position: relative;
    z-index: 1;
}

/* (0,3,0) to match the theme's own .rts-breadcrumb-area.bg_primary rule. A
   (0,2,0) selector loses to anything more specific in a 694KB stylesheet
   nobody has audited.

   :not(.rts-btn) because a button is not text on the photograph — it is text
   on its own fill, and the two travel together. Without this, .btn-white ("See
   Our Work") is forced to #fff on its own #F2F2F2 background and disappears;
   the theme's rule for it is also (0,3,0), so this one wins purely by coming
   later in the file. */
.rts-breadcrumb-area.vx-hero-has-media .title-area-left *:not(.rts-btn) {
    color: #fff;
}

/* The scrim protects the left of the frame, so the copy has to stay there.
   Without a measure the lead runs to 95% of the viewport and ends up over the
   transparent end of the gradient, where white text cannot reach 4.5:1. */
.rts-breadcrumb-area.vx-hero-has-media .title-area-left {
    max-width: 62ch;
}

/* Below 992 the copy sits over the middle of the frame, not the left, so the
   left-weighted gradient stops protecting it. Flat wash instead — the same
   move, and the same breakpoint, as the home hero. */
@media only screen and (max-width: 991px) {
    /* Below 992 the whole band is the gradient's plateau, so the flat wash is
       that plateau's own value - capped.

       An earlier version boosted it (scaled by 0.92, then offset upward by
       0.12): retail's 0.72 became 0.78
       and measured 9.96:1, twice the contrast the text needs, bought by turning
       the photograph into a smudge on the device where it fills most of the
       screen.

       The cap is measured, not picked. Minimum flat alpha for 4.5:1 across all
       four frames at 768 and 390: healthcare 0.52-0.55, financial 0.567-0.568,
       retail 0.566-0.567, logistics 0.558-0.567. So ~0.57 protects any of them
       and 0.66 leaves ~16% headroom. --vx-scrim is tuned for the DESKTOP crop,
       where a pale band under the headline can push it higher than mobile ever
       needs (retail: 0.72 desktop, 0.567 mobile); without the cap that page
       alone pays for a crop it is not showing. Re-derive with the solver if a
       frame changes. */
    .vx-hero-media::after {
        background: rgba(11, 16, 22, min(var(--vx-scrim, 0.62), 0.66));
    }

    .rts-breadcrumb-area.vx-hero-has-media .title-area-left {
        max-width: none;
    }
}

/* Physical 90deg gradient: in RTL the copy sits at the transparent end. */
[dir="rtl"] .vx-hero-media::after {
    transform: scaleX(-1);
}

@media (prefers-contrast: more) {
    .vx-hero-media::after {
        background: rgba(11, 16, 22, 0.85);
    }
}

/* The scrim is a background-image and does not print, which would leave white
   text on a raw photograph — a blank headline on paper. */
@media print {
    .vx-hero-media { display: none; }
    .rts-breadcrumb-area.vx-hero-has-media .title-area-left *:not(.rts-btn) { color: #000; }
}

/* In forced-colors the img survives while text is forced to CanvasText, so on
   a light high-contrast theme that is black type on a photograph. */
@media (forced-colors: active) {
    .vx-hero-media { display: none; }
}

/* ---- Icon cards (x-vx.icon-cards) ----------------------------------------
   Soft grey card, its icon in a white circular medallion, centred copy.

   Equal-height cards: the three titles on the page this was built for wrap to
   one, one and two lines, so without a stretch the row ends up with three
   different card heights and a ragged bottom edge. The column is the flex
   parent Bootstrap already gives us; the card fills it. */
.vx-iconcard {
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    padding: 44px 32px 40px;
    text-align: center;
    background: #f2f2f2;
    border-radius: 20px;
}

.vx-iconcard__medallion {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 112px;
    height: 112px;
    margin-bottom: 30px;
    border-radius: 50%;
    background: #fff;
}

/* Fixed box, not max-width: these icons are 64x64 with generous internal
   padding and their drawn content does not fill the frame evenly, so sizing by
   intrinsic width alone makes them optically different sizes side by side. */
.vx-iconcard__medallion img {
    width: 48px;
    height: 48px;
    object-fit: contain;
}

.vx-iconcard__title {
    margin: 0 0 14px;
    font-size: 24px;
    line-height: 1.3;
}

.vx-iconcard__text {
    margin: 0;
    /* Not auto margin-top: pushing the body down to fill a taller neighbour
       detaches it from its own title and the card reads as two fragments. */
    color: #5b6670;
}

@media only screen and (max-width: 991px) {
    .vx-iconcard { padding: 36px 26px 32px; }

    .vx-iconcard__medallion {
        width: 96px;
        height: 96px;
        margin-bottom: 24px;
    }

    .vx-iconcard__title { font-size: 21px; }
}

/* The medallion is white on a grey card; in forced-colors both collapse to the
   same system surface and the circle disappears, taking the icon's frame with
   it. A border keeps the shape when the fill is overridden. */
@media (forced-colors: active) {
    .vx-iconcard { border: 1px solid CanvasText; }
    .vx-iconcard__medallion { border: 1px solid CanvasText; }
}

/* ---- Icon rows, title-only variant (x-vx.icon-rows) ----------------------
   The template's .sticky-statement is a two-column row: .left-side at
   flex-basis 36%, .right at 65%. That is right when there is body copy. The
   eight items on /services/custom-software-development are bare capability
   names - the source lists them as bullets with no description - so the
   component emits no .right at all for them, and without this the title would
   sit in a 36% column with 64% of the row empty beside it.

   Two rows of the same section can differ, so this keys off the row, not the
   section: a list that mixes described and undescribed items still lays each
   row out correctly. */
.main-wrapper-sticky .sticky-statement.vx-row-titleonly .left-side {
    flex-basis: 100%;
}

/* Sticky is what makes the tall described rows slide over one another. These
   rows are a third of that height, so eight of them pinned at the same offset
   just stack into a pile at the top of the viewport and the last one is all
   you can read. */
/* The row also carries a photograph on hover, and padding alone left it 120px
   tall against the 164 the described rows get on the neighbouring service
   pages. At that height the picture is cropped to a band too thin to read, so
   the hover looked broken beside /services/saas-product-development rather
   than merely tighter. min-height rather than more padding: the base row is
   already flex/align-items:center, so the icon and title stay centred, and
   the 30px padding still sets the minimum breathing room for a title that
   wraps to two lines on a narrow viewport. */
.main-wrapper-sticky .sticky-statement.vx-row-titleonly {
    position: static;
    min-height: 164px;
    padding-top: 30px;
    padding-bottom: 30px;
    margin-bottom: 20px;
}

.main-wrapper-sticky .sticky-statement.vx-row-titleonly:last-child {
    margin-bottom: 0;
}

/* The pack draws on a 128 grid where the template's own icons use 48-64, so
   left alone these render visibly larger than the icons in the same position
   on the neighbouring service pages.

   flex-shrink: 0 on the wrapper is not tidiness. .left-side is a flex row and
   these titles run to ten words; without it the icon is the only flexible
   thing in the row, so each one gets squeezed by however long its own title
   is. Measured on a 390px viewport: 30px, 20px and 24px in the first three
   rows against the 44px they are set to - eight icons, eight different sizes,
   all of them distorted. */
.main-wrapper-sticky .sticky-statement.vx-row-titleonly .left-side .icon {
    flex-shrink: 0;
    line-height: 0;
}

.main-wrapper-sticky .sticky-statement.vx-row-titleonly .left-side .icon img {
    width: 60px;
    height: 60px;
    object-fit: contain;
}

@media only screen and (max-width: 767px) {
    /* The desktop min-height is there to give the hover photograph room to
       read. A phone shows eight of these stacked, so it takes the smaller
       frame the 44px icon is sized for. */
    .main-wrapper-sticky .sticky-statement.vx-row-titleonly {
        min-height: 110px;
        padding-top: 22px;
        padding-bottom: 22px;
    }

    .main-wrapper-sticky .sticky-statement.vx-row-titleonly .left-side .icon img {
        width: 44px;
        height: 44px;
    }
}

/* ---- Hero band: the same overflow, now visible ---------------------------
   The ≤991px rule earlier in this file explains why .rts-breadcrumb-area's
   FIXED 477px height spills its copy. It was deliberately left alone at
   desktop, on the measurement that "at 1440 the copy measures 259-326px
   against 477px".

   That sample did not include every page. /services/api-integrations-and-
   development measures 511px of copy at 1440 and overflows the band by 17px
   at each end.

   Without a background the spill is invisible — dark text landing on the white
   section below reads as spacing. With one it is not: the copy inside a media
   hero is white, so the 17px that escapes the photograph is white text on a
   white page. The overflow is older than these images; the images are what
   made it show.

   Scoped to media heroes: the other nineteen pages that render this band have
   copy well inside 477px, and their centred composition is doing hierarchy
   work worth keeping. min-height holds that composition here too and only
   yields on the pages that need the room. */
@media only screen and (min-width: 992px) {
    .rts-breadcrumb-area.vx-hero-has-media {
        height: auto;
        min-height: 477px;
        /* The header is absolute over this band now, so its height has to be
           given back as padding or the eyebrow sets underneath the nav. 106px
           of header plus the 40px the band already had. */
        padding-top: 146px;
        padding-bottom: 40px;
    }
}

/* ---- Contact's upward pull, against a photograph -------------------------
   style.css:21214 sets

       body.contact-page .rts-breadcrumb-area .title-area-left { margin-top: -70px }

   so the contact hero's copy is deliberately lifted out of its band; the form
   below it does the same with -120px. That reads fine while the band has no
   background — the copy simply sits higher on white.

   With a photograph it does not: measured at 390px the copy starts 22px above
   the frame, and the copy inside a media hero is white, so those 22px are
   white text on the white header. Same shape as the fixed-height overflow
   further up this file — the pull predates the image, the image is what made
   it show.

   The template rule is (0,3,1) and this is (0,3,1) too, so it wins on being
   loaded afterwards, which is the same footing every other override in this
   file stands on. Scoped to media heroes: /contact without one should keep
   the lift it was designed with. */
body .rts-breadcrumb-area.vx-hero-has-media .title-area-left {
    margin-top: 0;
}

/* ---- Photo cards (x-vx.photo-cards) --------------------------------------
   A photograph per card with the copy set over its lower half and a hexagonal
   medallion carrying the icon.

   The gradient is the whole legibility story here, so it is sized for the copy
   this actually carries: two to three hundred characters per card, not the
   sixty the reference design shows. It starts at the top of the card rather
   than partway down, because a paragraph that long reaches well past halfway
   and white text has to clear 4.5:1 over every part of the frame it covers. */
/* space-between, not flex-end: the medallion pins to the top of the card and
   the copy to the bottom. With flex-end both stack at the bottom, so the
   medallion rides on the height of its own paragraph and three cards in a row
   put their icons at three different heights. */
.vx-photocard {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    height: 100%;
    min-height: 460px;
    padding: 44px 30px 34px;
    overflow: hidden;
    border-radius: 14px;
    background: #0B1016;
}

.vx-photocard__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 38%;
}

.vx-photocard::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Measured, not chosen. The first pass ran 0.30/0.72/0.90 and measured
       9.1:1 to 14.3:1 across the nine cards at 1440 and 390 — twice what the
       copy needs, on a card whose whole point is the photograph. The worst
       frame (financial reconciliation at 390) reaches 4.5:1 at 0.739 of that
       ramp, so these are that ramp scaled to 0.80: enough headroom over the
       measurement, and the image survives. */
    background: linear-gradient(
        180deg,
        rgba(11, 16, 22, var(--vx-card-top, 0.24)) 0%,
        rgba(11, 16, 22, var(--vx-card-mid, 0.58)) 42%,
        rgba(11, 16, 22, var(--vx-card-btm, 0.72)) 100%
    );
}

/* The hexagon. clip-path rather than a rotated-squares trick: the icon inside
   must stay upright, and counter-rotating it leaves the edges soft. */
.vx-photocard__hex {
    position: relative;
    z-index: 1;
    align-self: center;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 74px;
    height: 82px;
    margin-bottom: 22px;
    background: #fff;
    clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}

.vx-photocard__hex img {
    width: 34px;
    height: 34px;
    object-fit: contain;
}

.vx-photocard__body {
    position: relative;
    z-index: 1;
    text-align: center;
}

.vx-photocard__title {
    margin: 0 0 12px;
    font-size: 24px;
    line-height: 1.3;
    color: #fff;
}

.vx-photocard__text {
    margin: 0;
    font-size: 15px;
    line-height: 1.65;
    color: #fff;
}

.vx-photocard__more {
    display: inline-block;
    margin-top: 18px;
    font-weight: 600;
    color: #fff;
}

.vx-photocard__more i {
    margin-left: 6px;
}

@media only screen and (max-width: 991px) {
    .vx-photocard {
        min-height: 420px;
        padding: 36px 24px 28px;
    }

    .vx-photocard__title { font-size: 21px; }
}

/* The gradient is a background-image and does not print, which would leave
   white text on a bare photograph. Same reasoning as the hero scrim. */
@media print {
    .vx-photocard__img { display: none; }
    .vx-photocard { background: none; }
    .vx-photocard__title,
    .vx-photocard__text,
    .vx-photocard__more { color: #000; }
}

/* In forced-colors the gradient is dropped and the photo survives, so the copy
   would sit on a raw image in whatever colour the theme forces. */
@media (forced-colors: active) {
    .vx-photocard__img { display: none; }
    .vx-photocard { border: 1px solid CanvasText; }
    .vx-photocard__hex { border: 1px solid CanvasText; }
}

/* ---- One navbar on every page --------------------------------------------
   / used to write its own transparent header inline while every other page
   included a solid one with a contact strip above it, so the nav changed as
   you moved through the site. Both now render partials/header/vortex.blade.php.

   .header-transparent is position:absolute (style.css:6120) so the bar can sit
   over a photograph. Only 32 of the site's pages open with one: the blog
   index, a blog post, a case study and the placeholder views either have a
   plain band or no band at all, and an absolute bar over those is white text
   on white.

   So those pages get the SAME bar, in flow, painted the colour it turns into
   on scroll (style.css:6127). One navbar, two backgrounds — not a second
   design. Nothing here changes the markup, the links or the sticky state. */
.header-transparent.vx-header-solid {
    position: relative;
    background: #20282d;
}

/* The band's own ::after scrim runs left-to-right, because it was measured
   against copy that sits in the left 62ch. The nav does not: "Company",
   "Insights" and the CTA sit in the right third, over the part of the frame
   the horizontal ramp deliberately leaves alone. This is the strip that gives
   those three something to sit on, and it is measured the same way — sampled
   under every nav line box at 1440 and 390, worst case over 4.5:1 against
   #fff.

   Its own token, not --vx-scrim: raising the copy scrim to cover the nav
   would darken the whole frame to fix its top 120px. */
.vx-hero-media::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 210px;
    pointer-events: none;
    background: linear-gradient(
        180deg,
        rgba(11, 16, 22, var(--vx-nav-scrim, 0.62)) 0%,
        rgba(11, 16, 22, calc(var(--vx-nav-scrim, 0.62) * 0.55)) 55%,
        rgba(11, 16, 22, 0) 100%
    );
}

/* Same reasoning as the desktop rule above: the bar overlays the band, so its
   height comes back as padding. 86px of header here, plus the 60px the band
   already had. Scoped to media heroes — every other page carries the solid
   bar in flow and needs no room made for it. */
@media only screen and (max-width: 991px) {
    .rts-breadcrumb-area.vx-hero-has-media {
        padding-top: 146px;
    }
}

@media only screen and (max-width: 575px) {
    .rts-breadcrumb-area.vx-hero-has-media {
        padding-top: 134px;
    }
}

/* ---- One gap, everywhere (every page but home) ---------------------------
   ONE number per viewport, used for every vertical join on the page: between
   any two sections, and between the hero band and the first section under it.
   160px at desktop, 120 at tablet, 96 on a phone.

   How it was before this block, measured across the 34 inner pages: the gap
   between two sections was 240px in 129 places and the space after the hero
   was 160, so the page ran two rhythms and the eye caught the change. On a
   phone the hero join measured 108 on 32 pages and 80 on two, because the hero
   band and the section below it were scaled by different rules.

   The mechanism is the theme's own pair of classes. .rts-section-gap is
   120px top and bottom and .rts-section-gapBottom is 120px bottom with nothing
   on top, and BOTH are overridden here to half the target gap, so two adjacent
   sections add up to exactly one gap and no section can be out of step with
   its neighbour.

   Half the gap is also all the internal padding a section gets, which is the
   real cost of a single number: the bands that carry their own background
   (the process strip, the closing CTA) breathe 80px instead of 120.

   The hero join needs its own number because the hero band brings its own
   bottom padding — 40px at desktop, 60 from 576 to 991, 48 below that — and
   the join has to come out at the same total as every other gap. Each value
   below is that subtraction, not a preference.

   Scoped off / by body class: the home page composes the template's own
   showcase sections and was not part of the brief.                          */
body:not(.vx-home) > .rts-section-gap,
body:not(.vx-home) > .rts-section-gapBottom {
    padding-bottom: 80px;
}

body:not(.vx-home) > .rts-section-gap,
body:not(.vx-home) > .rts-section-gapBottom:not(.mt-dec-blog-list) {
    padding-top: 80px;
}

/* The two "See all services" link rows are top-level blocks with no padding of
   their own, so the joins around them came out at half a gap each. They are
   part of the rhythm like anything else on the page. */
body:not(.vx-home) > .container {
    padding-top: 80px;
    padding-bottom: 80px;
}

/* 40px of hero + 120 = 160. */
body:not(.vx-home) > .rts-breadcrumb-area + *:not(.mt-dec-blog-list) {
    padding-top: 120px;
}

/* These banners end flush — no bottom padding of their own — so their
   neighbour carries the whole gap. */
body:not(.vx-home) > .partner-breadcrumb + *,
body:not(.vx-home) > .blog-details-banner-large-image + * {
    padding-top: 160px;
}

/* Bands with a background and no gap class. Their padding is internal to the
   coloured box, so the gap goes outside it as margin — adding to the padding
   would grow the box and leave the whitespace where it was. Half the gap on
   each side, same as every section. .rts-cta-area-inner is the last block
   before the footer, which sets its own top padding, so only its leading edge
   is spaced here. */
body:not(.vx-home) > .rts-call-to-action-area-two.bg_image {
    margin: 80px 0;
}

/* ---- Band CTA with its own photograph (x-vx.band-cta :image) -------------
   The template's band paints assets/images/cta/02.webp and no overlay, which
   works only because that particular photograph is dark. Its text is white and
   there is nothing between the two. Point the band at a different picture and
   the white headline lands wherever the picture happens to be light.

   So the photograph and the scrim arrive together: a band that supplies an
   image gets an overlay whose alpha is measured for that image, and a band
   that does not keeps the template's background untouched. The four pages
   already rendering this component take neither.

   The overlay is a ::before rather than a gradient in background-image so the
   alpha can come from a custom property per instance.                        */
.rts-call-to-action-area-two.vx-band--photo {
    position: relative;
    background-image: var(--vx-band-img);
    background-size: cover;
    background-position: center;
    isolation: isolate;
}

.rts-call-to-action-area-two.vx-band--photo::before {
    content: '';
    position: absolute;
    inset: 0;
    /* Same ink as the hero scrims, so a page carrying both reads as one site. */
    background: rgba(11, 15, 18, var(--vx-band-scrim, 0.55));
    z-index: 0;
}

.rts-call-to-action-area-two.vx-band--photo > * {
    position: relative;
    z-index: 1;
}

body:not(.vx-home) > .rts-cta-area-inner.bg_image {
    margin-top: 80px;
}

/* The blog post body ends flush: .pt--80 sets a top and nothing else. */
body:not(.vx-home) > .blog-details-area-main-wrapper {
    padding-bottom: 80px;
}

@media only screen and (min-width: 768px) and (max-width: 991px) {
    body:not(.vx-home) > .rts-section-gap,
    body:not(.vx-home) > .rts-section-gapBottom {
        padding-bottom: 60px;
    }

    body:not(.vx-home) > .rts-section-gap,
    body:not(.vx-home) > .rts-section-gapBottom:not(.mt-dec-blog-list),
    body:not(.vx-home) > .container {
        padding-top: 60px;
    }

    body:not(.vx-home) > .container {
        padding-bottom: 60px;
    }

    /* 60px of hero + 60 = 120. */
    body:not(.vx-home) > .rts-breadcrumb-area + *:not(.mt-dec-blog-list) {
        padding-top: 60px;
    }

    body:not(.vx-home) > .partner-breadcrumb + *,
    body:not(.vx-home) > .blog-details-banner-large-image + * {
        padding-top: 120px;
    }

    body:not(.vx-home) > .rts-call-to-action-area-two.bg_image {
        margin: 60px 0;
    }

    body:not(.vx-home) > .rts-cta-area-inner.bg_image {
        margin-top: 60px;
    }

    body:not(.vx-home) > .blog-details-area-main-wrapper {
        padding-bottom: 60px;
    }
}

@media only screen and (max-width: 767px) {
    body:not(.vx-home) > .rts-section-gap,
    body:not(.vx-home) > .rts-section-gapBottom {
        padding-bottom: 48px;
    }

    body:not(.vx-home) > .rts-section-gap,
    body:not(.vx-home) > .rts-section-gapBottom:not(.mt-dec-blog-list),
    body:not(.vx-home) > .container {
        padding-top: 48px;
    }

    body:not(.vx-home) > .container {
        padding-bottom: 48px;
    }

    body:not(.vx-home) > .partner-breadcrumb + *,
    body:not(.vx-home) > .blog-details-banner-large-image + * {
        padding-top: 96px;
    }

    body:not(.vx-home) > .rts-call-to-action-area-two.bg_image {
        margin: 48px 0;
    }

    body:not(.vx-home) > .rts-cta-area-inner.bg_image {
        margin-top: 48px;
    }

    body:not(.vx-home) > .blog-details-area-main-wrapper {
        padding-bottom: 48px;
    }
}

/* The hero keeps 60px of bottom padding down to 576 and drops to 48 below it,
   while the section under it is on 48 from 767 down. Two different steps, so
   the join needs the subtraction done twice or the phone reads 108 where the
   tablet reads 96. */
@media only screen and (min-width: 576px) and (max-width: 767px) {
    body:not(.vx-home) > .rts-breadcrumb-area + *:not(.mt-dec-blog-list) {
        padding-top: 36px;
    }
}

@media only screen and (max-width: 575px) {
    body:not(.vx-home) > .rts-breadcrumb-area + *:not(.mt-dec-blog-list) {
        padding-top: 48px;
    }
}

/* style.css:21209 pulls .rts-contact-area-in-page up 120px. In the template
   the contact card sits directly under the breadcrumb and that pull is what
   makes it overlap the band. Here a full section always sits in between, so
   the pull lands in open page and simply eats the gap. */
body:not(.vx-home) > .rts-contact-area-in-page {
    margin-top: 0;
}

/* /blog is the one exception and it is deliberate: .mt-dec-blog-list pulls the
   listing 177px UP so the first row of cards overlaps the banner. That overlap
   is the design of the page. */

/* ---- Statement rows: a photograph per row ---------------------------------
   style.css:11358 gives EVERY .sticky-statement on the site the same hover
   image, ../images/service/06.webp. Ninety-seven rows across thirteen pages,
   one picture: hovering "Monitoring" on the API page showed exactly what
   hovering "Research" on the design page showed.

   Each row carries its own now, as --vx-row-img set inline by the component.
   The fallback keeps the template's image, so a row that has not been given a
   photograph yet looks exactly as it did before.                             */
section.main-wrapper-sticky .sticky-statement:hover {
    background-image: var(--vx-row-img, url(../images/service/06.webp));
}

/* On hover style.css:11364 turns every child white. That is safe over one
   known image and unsafe over ninety-seven unknown ones, so the row gets a
   measured wash between the photograph and the copy. Opacity animates with
   the same 0.3s the theme uses, so the row still fades rather than snapping.

   ::before rather than a darker crop: the photograph stays reusable, and the
   strength is one number to re-measure if a frame is ever swapped. */
section.main-wrapper-sticky .sticky-statement::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(11, 16, 22, var(--vx-row-scrim, 0.62));
    opacity: 0;
    transition: 0.3s;
    pointer-events: none;
}

section.main-wrapper-sticky .sticky-statement:hover::before {
    opacity: 1;
}

/* The wash is a child of the row, so the copy has to be lifted above it. */
section.main-wrapper-sticky .sticky-statement > * {
    position: relative;
    z-index: 1;
}

/* ---------------------------------------------------------------------------
   x-vx.narrative — the argument block between the hero and the Problem
   section on the six new industry pages. The template has no centred prose
   block: .disc inside .rts-about-area-6 is a left column beside a list, so the
   paragraphs need their own measure and rhythm rather than inheriting one
   meant for a two-column split.
   --------------------------------------------------------------------------- */
.vx-narrative__para {
    font-size: 18px;
    line-height: 1.72;
    margin-bottom: 20px;
}

.vx-narrative__para:last-child {
    margin-bottom: 0;
}

@media (max-width: 767px) {
    .vx-narrative__para {
        font-size: 16px;
        line-height: 1.68;
    }
}

/* -------------------------------------------------------------------------
   Client proof logo marquee

   Home (NO AI).docx asked for a logo row; 36 marks are carried that can sit
   on a white strip. Statically wrapped they take five rows at 1440 and read as
   a directory, so the row travels instead.

   SEAMLESS LOOP MATH. The track holds the set twice with the same gap between
   the two copies as inside each one. Travelling -50% of the track would stop
   half a gap short, because that 50% includes the joining gap; the extra
   --vx-logo-gap/2 lands the second copy exactly where the first started.

   Every mark is greyed and dimmed. They come from 36 different brands - the
   halal.ae crescent alone runs green through orange to red - and at full
   strength the row reads as a colour chart rather than one strip. Colour
   returns on hover for the ones that link to a case study.
   ------------------------------------------------------------------------- */
.vx-client-logos {
    --vx-logo-gap: 56px;
    overflow: hidden;
    /* Fade both ends so marks enter and leave rather than being cut off. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}

.vx-client-logos__track {
    display: flex;
    width: max-content;
    gap: var(--vx-logo-gap);
    animation: vx-logo-marquee 80s linear infinite;
}

.vx-client-logos:hover .vx-client-logos__track,
.vx-client-logos:focus-within .vx-client-logos__track {
    animation-play-state: paused;
}

.vx-client-logos__set {
    display: flex;
    align-items: center;
    gap: var(--vx-logo-gap);
    list-style: none;
    margin: 0;
    padding: 0;
}

.vx-client-logos img {
    /* A fixed box with contain is what makes 36 marks of wildly different
       proportions sit level: a 13:1 wordmark hits the width, a square badge
       hits the height, and neither is stretched. */
    width: 150px;
    height: 44px;
    object-fit: contain;
    opacity: 0.62;
    /* --vx-logo-dim is measured per mark in config/client-logos.php: 85.6 over
       the mark's own mean luminance, capped at 1. Without it the row's palest
       marks paint at 1.6:1 against white and its darkest at 4.6:1, which reads
       as some logos being broken rather than as one strip. */
    filter: grayscale(1) brightness(var(--vx-logo-dim, 1));
    transition: opacity 0.25s ease, filter 0.25s ease;
}



/* A mark supplied as light type on a solid dark ground. Greyed and dimmed like
   the rest it becomes a grey slab in the middle of the row — the ground, not
   the mark, is what reads. So it is set as a chip: its own dark ground becomes
   the badge, rounded and held at the row's weight.

   height:auto is what makes the rounding land. Every other mark sits in a fixed
   150x44 box with object-fit:contain, so its element box is taller than the
   pixels it paints and a border-radius would round empty space. Letting this
   one take its own height makes the box and the artwork the same rectangle. */
.vx-client-logos img.vx-client-logos__dark {
    height: auto;
    max-height: 44px;
    border-radius: 4px;
    /* Full strength, not dimmed. Fading a filled rectangle only turns it into a
       grey slab and washes out the type inside it; a crisp badge reads as a
       decision. Greyscale stays, so it keeps the row's monochrome. */
    opacity: 1;
}

.vx-client-logos a:hover img,
.vx-client-logos a:focus-visible img {
    opacity: 1;
    filter: none;
}

@keyframes vx-logo-marquee {
    to { transform: translateX(calc(-50% - (var(--vx-logo-gap) / 2))); }
}

@media (prefers-reduced-motion: reduce) {
    /* No travel. The row becomes a scrollable strip the reader drives. */
    .vx-client-logos { overflow-x: auto; }
    .vx-client-logos__track { animation: none; }
}

@media (max-width: 575px) {
    .vx-client-logos { --vx-logo-gap: 34px; }
    .vx-client-logos img { width: 112px; height: 34px; }
}

/* -------------------------------------------------------------------------
   /work sector filter
   The brief asks for a filter row. Its own taxonomy does not match the
   published engagements, so the controls are built from the sectors the
   studies actually carry. Script-driven; without JS the whole grid shows.
   ------------------------------------------------------------------------- */
.vx-work-filter {
    list-style: none;
    margin: 0 0 34px;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.vx-work-filter button {
    appearance: none;
    border: 1px solid var(--color-border, #e3e6ea);
    background: transparent;
    color: var(--color-body, #4b5563);
    font-size: 14px;
    line-height: 1.2;
    padding: 9px 16px;
    border-radius: 999px;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}

.vx-work-filter button:hover,
.vx-work-filter button:focus-visible {
    color: var(--color-heading-1, #10141b);
    border-color: var(--color-heading-1, #10141b);
}

.vx-work-filter button.is-active {
    background: var(--color-heading-1, #10141b);
    border-color: var(--color-heading-1, #10141b);
    color: #fff;
}

@media (max-width: 575px) {
    .vx-work-filter { gap: 8px; }
    .vx-work-filter button { font-size: 13px; padding: 8px 13px; }
}

/* -------------------------------------------------------------------------
   Offcanvas menu: tap targets
   Measured with the panel open at 390px: 52 controls, one link at 21px and
   four social icons at 40px. WCAG 2.5.8 (AA) sets a 24x24 floor and 2.5.5
   (AAA) sets 44x44; the 21px link failed the first, the icons missed the
   second. The panel itself was already right — 320px wide, background scroll
   locked, real close button, 55x55 burger — so only the targets move.
   ------------------------------------------------------------------------- */
.side-bar .vx-logo,
.side-bar .logo-1 {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

.side-bar .social-wrapper-two.menu a,
.side-bar .social-wrapper-one ul li a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
}

.side-bar .close-icon-menu {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* ---- Home hero: the index-six banner design -----------------------------
   The home hero moved from the theme's banner-seven (single-slide swiper,
   headline centred over a full-height photo) to banner-six: a two-column row
   anchored to the bottom of the frame, copy left, proof figure and CTAs right.

   The theme already ships every structural rule for banner-six, so what
   follows is only the four things it does not cover for this page. */

/* 1. The frame. Three candidates were looked at, not just listed:

        06.webp  the demo's own — bright office stock, and this layout sets
                 every string in the left column to #fff. White on white.
        07.webp  what this page used before. Office team, dark-tinted so the
                 type reads, but the file itself is flagged in this stylesheet
                 as inherited Invena stock awaiting replacement.
        30/32    brighter people-in-an-office frames. Same problem as 06.

      33.webp instead: a dark charcoal field with a faint node-and-wave mesh.
      No people, no literal screens, and it sits in the #20282D family the
      recorded art direction asks for, so the type has the whole frame to
      itself rather than competing with a face.

      It is a stand-in, not the finish. The art direction in
      docs/audit/PLAN-website-audit.md wants a licensed photograph with a
      single dominant subject and weight in the right 40%. This is the best
      frame already in the repository. */
/* The source frame is 3840x2014 and 108KB, for a background nobody looks at
   directly. Two right-sized copies instead: 16KB above the tablet breakpoint,
   2KB below it. The mesh is nearly flat, so it survives the resample. */
.vx-home .vx-hero-six.bg_banner-five {
    /* The colour before the picture.

       Lighthouse's filmstrip showed the hero painting as white for the first
       1.5s and the copy arriving as dark text on white, then the whole section
       turning near-black at 2.2s when the background finished downloading. The
       page reached its real appearance in one jump, which is exactly what Speed
       Index measures: 2.0s against a 0.9s Largest Contentful Paint.

       #20282e is the mesh's own average colour, sampled from the file. Painting
       it as a background-color means the section is the right colour from the
       first frame and the image only adds the texture. */
    background-color: #20282e;
    background-image: url('../images/banner/33-960.webp');
}

@media (min-width: 768px) {
    .vx-home .vx-hero-six.bg_banner-five {
        background-image: url('../images/banner/33-1920.webp');
    }
}

/* The mesh is nearly flat, which is what makes it a good ground for type and
   also what makes it read as a plain dark rectangle on its own. A bottom
   weighted wash gives the frame some depth and seats the copy, which in this
   layout sits along the BOTTOM edge across both columns — so the gradient runs
   vertically, unlike the left-weighted scrim the previous hero used.

   Same colour as every other scrim on the site: rgba(11, 16, 22). */
.vx-home .vx-hero-six {
    position: relative;
}

.vx-home .vx-hero-six::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
        180deg,
        rgba(11, 16, 22, 0) 0%,
        rgba(11, 16, 22, .18) 45%,
        rgba(11, 16, 22, .52) 100%
    );
}

/* Copy above the wash. The template gives .container no stacking context of
   its own, so without this the gradient paints over the headline. */
.vx-home .vx-hero-six > .container {
    position: relative;
    z-index: 2;
}

/* 2. The eyebrow. The theme styles .pre-title only inside
      .banner-seven-inner-content, so in the six layout it would render as
      unstyled inline text with the icon jammed against it. */
.vx-home .vx-hero-six .pre-title {
    display: flex;
    align-items: center;
    font-size: 20px;
    color: #fff;
    margin-bottom: 20px;
}

.vx-home .vx-hero-six .pre-title img {
    margin-right: 10px;
}

@media only screen and (max-width: 767px) {
    .vx-home .vx-hero-six .pre-title {
        font-size: 16px;
        margin-bottom: 15px;
    }
}

/* 3. The H1 lead sentence. The approved H1 is two sentences and both belong to
      the one H1; the first is set small so the display line still reads as the
      headline. Four classes so it beats .banner-content-six .left-side *. */
.vx-home .vx-hero-six .title .vx-hero-h1__lead {
    display: block;
    margin-bottom: 16px;
    font-size: clamp(17px, 1.5vw, 21px);
    line-height: 1.45;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    color: rgba(255, 255, 255, .88);
    max-width: 46ch;
}

/* 4. The secondary CTA. Two solid buttons side by side read as two primaries;
      the outlined one is the same treatment the previous hero used. */
.vx-home .vx-hero-six .vx-hero-btn-2 {
    background: transparent;
    border: 1px solid rgba(255, 255, 255, .55);
    color: #fff;
}

.vx-home .vx-hero-six .vx-hero-btn-2:hover {
    background: rgba(255, 255, 255, .12);
    border-color: #fff;
}

/* 5. The frame must grow when the copy is taller than it.
      bg_banner-five sets a FIXED height (900px, 730px under 575px) and
      .rts-banner-area-six aligns its content to flex-end. That combination is
      fine for the demo's two-line headline, but this page's hero carries an
      eyebrow, a two-sentence H1 and a four-line paragraph. At 390px the column
      stack measured 766px inside a 730px box, and because the content is
      bottom-aligned the overflow went off the TOP: the eyebrow and the first
      line of the H1 sat at y = -156, clipped out of the frame entirely.

      min-height keeps the tall cinematic frame the design wants; height:auto
      lets it grow rather than crop the headline.

      The cap at 100svh is the second half of the same problem, in the other
      direction. This layout bottom-aligns its content, so a frame TALLER than
      the viewport pushes the CTAs off the bottom of the screen. Measured at
      900px fixed:

        1440x730   "See How We Work" below the fold
        1366x768   "See How We Work" below the fold
        1280x720   BOTH buttons below the fold

      A hero whose call to action cannot be seen without scrolling is not a
      hero. min(900px, 100svh) keeps the full 900 on a tall display and shrinks
      to the viewport on a laptop, so the buttons sit just above the fold
      either way. svh, not vh, because mobile vh is the address-bar-expanded
      height and would reintroduce the same overflow on a phone. */
.vx-home .vx-hero-six.bg_banner-five {
    height: auto;
    min-height: min(900px, 100svh);
}

@media only screen and (max-width: 575px) {
    .vx-home .vx-hero-six.bg_banner-five {
        min-height: 730px;
    }
}

/* 6. Stack earlier than the theme does. .banner-content-six only goes to a
      column under 768px, so 768-991 kept two columns and squeezed the left one
      to 360px — a 60px headline in a 360px measure. The right column is a
      fixed 357px, so the space runs out well before the theme thinks it does. */
@media only screen and (max-width: 991px) {
    .vx-home .vx-hero-six .banner-content-six {
        flex-direction: column;
        align-items: flex-start;
        gap: 45px;
    }

    .vx-home .vx-hero-six .banner-content-six .left-side,
    .vx-home .vx-hero-six .banner-content-six .right-stars-area {
        max-width: 100%;
    }
}

/* 7. Phone: reclaim the last 27px so the CTA is on screen.
      At 390x844 the stack measured 871px against an 844px viewport — the
      primary CTA missed the fold by 27 pixels, which is the worst possible
      margin: close enough to look like a mistake, far enough to cost the tap.
      The theme's 120px top gap and 90px bottom gap are sized for a desktop
      band, not a phone, so trimming them and the column gap buys 43px and the
      buttons land above the fold on the common handset sizes.

      320x568 still overflows, and no amount of padding fixes that: an eyebrow,
      a two-sentence headline, a four-line paragraph, a counter and two buttons
      do not fit in 568px. That one scrolls, which is what phones do. */
@media only screen and (max-width: 575px) {
    .vx-home .vx-hero-six.rts-section-gapTop {
        padding-top: 96px;
    }


    .vx-home .vx-hero-six .banner-content-six {
        gap: 32px;
    }
}

/* ---- Home: findings from the section-by-section visual pass ---------------
   Four spacing/typography corrections. No copy changed by any of them. */

/* 1. The FAQ's closing paragraph is the FAQ's, not the CTA's.
      In Home (NO AI).docx it sits after the last procurement answer and before
      the "Final CTA" label. Rendered with a full section gap above it, it read
      as a second closing pitch stacked on the real one — two sections in a row
      saying "start with an architecture assessment". Pulling it up against the
      questions is the whole fix; both texts stay exactly as approved. */
.vx-home .vx-faq-closer {
    /* Calibrated against the section-rhythm block at the end of this file. The
       closer sits 60px under the last answer (40 tablet, 30 phone) whatever the
       page rhythm is, so this margin is the difference between the two: the FAQ
       above ends in 110px (80 tablet, 60 phone), and 110 - 60 = 50.

       It has to be recalculated whenever that rhythm changes. It was -20 when
       the FAQ ended in 80. */
    margin-top: -50px;
}

@media only screen and (min-width: 768px) and (max-width: 991px) {
    .vx-home .vx-faq-closer {
        margin-top: -40px;
    }
}

@media only screen and (max-width: 767px) {
    .vx-home .vx-faq-closer {
        margin-top: -30px;
    }
}

/* 2. Engagement models: room for the CTA the section was missing. */
.vx-home .vx-table-slot,
.vx-table-slot {
    margin-top: 34px;
    display: flex;
    justify-content: center;
}

/* 3. The client strip is a marquee, so it spans the row rather than being
      clamped to a measure the way the wrapped wordmark list was. */

/* 4. The reason cards no longer carry a portrait, so the copy should use the
      width the image used to take rather than sitting in a narrow column. */
.vx-home .testimonials-main-wrapper-two .right-content-testimonials {
    width: 100%;
}

/* ---- Home: "why choose us" ----------------------------------------------
   The heading is a full-width row above the two columns, so the row below is
   just the photograph and the four points and they align against each other.

   The nudge is what the feedback asked for — the image sitting slightly lower
   than the points rather than flush with them. It is a fixed 24px rather than
   the by-product of centring, because centring made the offset a function of
   the points column's height: 19px at 1440, 126px at 1200. */
.vx-home .rts-why-choose-area .thumbnail-why-choose-8 {
    margin-top: 24px;
}

/* Stacked, the nudge is just a gap between the image and the points. */
@media only screen and (max-width: 991px) {
    .vx-home .rts-why-choose-area .thumbnail-why-choose-8 {
        margin-top: 0;
        margin-bottom: 40px;
    }
}

/* The second half of the why-us header reads as a paragraph, not display type.

   It is still inside the h2 — the approved header is one sentence and lifting
   the second half into a <p> would leave the heading saying only "Why teams
   choose us" — so these overrides undo what .title and .uppercase do to it:
   the display size, the uppercasing, and the heading weight. */
.vx-home .vx-why-head__tail {
    display: block;
    max-width: 62ch;
    margin: 18px auto 0;
    font-size: clamp(16px, 1.35vw, 19px);
    line-height: 1.6;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    /* Was opacity: .72, which blended #5D666F into #F2F2F2 at 2.99:1 and
       failed WCAG AA. An explicit colour keeps the muted read at 4.91:1. */
    color: #616A74;
}

/* ---- Home: one eyebrow treatment across the page ------------------------
   The homepage section headings moved to title-style-four — a letter-spaced
   eyebrow over an uppercase heading whose trailing phrase is outlined.

   Two blocks keep title-style-one and would otherwise still draw the old
   bordered pill above them, leaving two eyebrow styles on one page:

     .eight (why us)  its heading span is the second half of the approved
                      header set at paragraph size, NOT an accent word, so
                      converting the block would outline a paragraph.
     .mb--20          the FAQ's closing sentence. It is a sentence, not a
                      section label; at 48px uppercase it would shout over
                      the CTA it introduces.

   So only their eyebrow is restyled to match. */
.vx-home .title-style-one.eight .pre,
.vx-home .title-style-one.mb--20 .pre {
    display: block;
    max-width: none;
    padding: 0;
    border: 0;
    border-radius: 0;
    margin-bottom: 20px;
    font-size: 16px;
    line-height: 21px;
    font-weight: 700;
    letter-spacing: .3em;
    text-transform: uppercase;
    color: #5D666F;
}

/* ---- Home: heading size follows its column, not the viewport -------------
   title-style-four is a fixed 48px (42/32/28 at the theme's breakpoints), which
   assumes every heading gets the full container. Two of these sit in a narrow
   column: "You don't have a technology problem. You have a finishing problem."
   measured SIX lines in a 511px column at 48px.

   cqi solves it where vw cannot. A viewport unit does not know the heading is
   in a half-width column, so it would shrink the full-width headings too;
   cqi resolves against the heading block itself, so a 1305px column still gets
   the full 48px and a 511px one drops to about 32px. Same technique the hero
   uses for its H1, and for the same reason.

   The span carries its own font-size in the template, so it has to inherit
   explicitly or the outlined half stays 48px while the solid half shrinks. */
.vx-home .title-style-four {
    container-type: inline-size;
}

.vx-home .title-style-four .title,
.vx-home .title-style-four .title span {
    font-size: clamp(24px, 6.2cqi, 48px);
}

/* ---- Home: joined to the site-wide section rhythm -------------------------
   The inner pages run one rhythm (vortex.css, "Section rhythm" block above):
   every section carries half the target gap on each side, so two neighbours
   always sum to 160px at desktop, 120 tablet, 96 phone. Home was excluded
   and kept the template's 120px paddings — 240px between gap-class sections —
   so crossing from / to any inner page visibly tightened the site.

   Same mechanism, rescoped: home's sections are children of .page-wrapper,
   not of body, so the inner-page selectors could never reach them. Three
   classes of section, same treatment as inner pages:

     gap / gapBottom sections   half-gap padding each side
     bg band with a gap class   (working-process strip) padding is internal
                                to the box; it breathes 80 instead of 120,
                                the same trade the inner pages accepted
     bg band with no gap class  (.rts-call-to-action-area-two) gap as margin,
                                so the box itself does not grow

   The hero is untouched on purpose: it is gapTop-only (no rule below matches
   it), its bottom padding is internal to the banner image, and its own ≤575
   trims above already set its numbers. The join under the hero is the next
   section's new top padding — the band-edge pattern the inner pages use.

   These selectors (0,3,0) also supersede the three ≥992 patches earlier in
   this file that trimmed brand/service/about paddings from 120 to 90 — those
   were spot repairs to the 240 rhythm and lose the specificity contest here. */
.vx-home .page-wrapper > .rts-section-gap,
.vx-home .page-wrapper > .rts-section-gapBottom {
    padding-bottom: 110px;
}

.vx-home .page-wrapper > .rts-section-gap,
.vx-home .page-wrapper > .rts-section-gapBottom:not(.vx-faq-closer) {
    padding-top: 110px;
}

.vx-home .page-wrapper > .rts-call-to-action-area-two.bg_image {
    margin: 110px 0;
}

/* The hero's own bottom padding is the top half of the first join, so it has to
   sit on the rhythm like every other section end. The template ships pb--90,
   which put the banner->brand join at 170 where every other join is 160. The
   inner pages make the same join out of their breadcrumb hero's pb40 and the
   next section's pt120 — a different split, the same total. */
.vx-home .page-wrapper > .rts-banner-area-six.pb--90 {
    padding-bottom: 110px;
}

/* Bands that carry a background AND a gap class get NO margin here, on purpose.
   An earlier pass gave .working-process-one.bg-main and .vx-cta a margin on the
   theory that an 80px join "made the home read tighter than the inner pages".
   Measuring both proved the reverse — every inner page sits at 80 from content
   to a band edge and 160 from content to content, so the margin stacked on the
   band's own padding and pushed the home's joins to 240. The bands space
   themselves with padding, exactly as they do on /about and /how-we-work.

   .rts-call-to-action-area-two.bg_image above is not an exception to this: it
   ships no padding at all, so its margins ARE its gap, and it measures 160 on
   the inner pages too. */


@media only screen and (min-width: 768px) and (max-width: 991px) {
    .vx-home .page-wrapper > .rts-section-gap,
    .vx-home .page-wrapper > .rts-section-gapBottom {
        padding-bottom: 80px;
    }

    .vx-home .page-wrapper > .rts-section-gap,
    .vx-home .page-wrapper > .rts-section-gapBottom:not(.vx-faq-closer) {
        padding-top: 80px;
    }

    .vx-home .page-wrapper > .rts-call-to-action-area-two.bg_image {
        margin: 80px 0;
    }

    .vx-home .page-wrapper > .rts-banner-area-six.pb--90 {
        padding-bottom: 80px;
    }


}

@media only screen and (max-width: 767px) {
    .vx-home .page-wrapper > .rts-section-gap,
    .vx-home .page-wrapper > .rts-section-gapBottom {
        padding-bottom: 60px;
    }

    .vx-home .page-wrapper > .rts-section-gap,
    .vx-home .page-wrapper > .rts-section-gapBottom:not(.vx-faq-closer) {
        padding-top: 60px;
    }

    .vx-home .page-wrapper > .rts-call-to-action-area-two.bg_image {
        margin: 60px 0;
    }

    .vx-home .page-wrapper > .rts-banner-area-six.pb--90 {
        padding-bottom: 60px;
    }


}

/* ---- Technology stack, spec-sheet rows (x-vx.stack-logos) ----------------
   A label rail on the left, the technologies flowing beside it, hairlines
   between rows. Replaced a seven-card grid: seven groups holding between 4
   and 9 items never balanced in a two-column grid of equal-height cards, so
   four cards carried dead space and the odd one stranded a column. Rows have
   no such constraint and the section came down from 1692px to about 940.

   The logos keep their brand colour — a monochrome treatment would cost the
   recognition that is the whole point of showing marks rather than names —
   but they lose the chip frame, which was a second border inside the card's
   own. A brand with no distributable logo gets a two-letter mark in the same
   21px box, so nothing breaks the line's rhythm. */
.vx-techstack__lead {
    text-align: center;
    max-width: 640px;
    margin: 0 auto 8px;
}

.vx-techstack__spec {
    margin: 44px 0 0;
}

.vx-techstack__row {
    display: grid;
    grid-template-columns: 210px 1fr;
    gap: 34px;
    align-items: start;
    padding: 26px 0;
    border-top: 1px solid #e4e6e8;
}

.vx-techstack__row:last-child {
    border-bottom: 1px solid #e4e6e8;
}

.vx-techstack__key {
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #5D666F;
    padding-top: 6px;
    margin: 0;
}

.vx-techstack__val {
    margin: 0;
}

.vx-techstack__items {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 10px 26px;
    padding: 0;
    margin: 0;
}

.vx-techstack__item {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 15px;
    font-weight: 500;
    color: #20282D;
    white-space: nowrap;
}

.vx-techstack__item img {
    width: 21px;
    height: 21px;
    object-fit: contain;
    display: block;
}

.vx-techstack__mono {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 21px;
    height: 21px;
    border-radius: 5px;
    background: #eef1f1;
    color: #20282D;
    font-size: 8.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
}

.vx-techstack__note {
    font-size: 13px;
    color: #5D666F;
    margin: 12px 0 0;
}

.vx-techstack .disc:last-child {
    text-align: center;
    max-width: 640px;
    margin: 34px auto 0;
}

/* The rail is sized for the longest label ("Identity Management") at desktop.
   On a tablet that same 210px is a quarter of the viewport, so it narrows and
   the label wraps to two lines rather than starving the list beside it. */
@media only screen and (min-width: 768px) and (max-width: 991px) {
    .vx-techstack__row {
        grid-template-columns: 150px 1fr;
        gap: 24px;
    }
}

/* ---- stack-logos: variant A, auto-height cards ---------------------------
   Grid rather than the theme's row/col: three equal tracks with
   align-items:start, so a card's height follows its own content and the
   seven groups (4 to 9 items each) stop forcing dead space on each other. */
.vx-techstack__cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 24px;
    align-items: start;
    margin-top: 44px;
}

.vx-techstack--cards .vx-card .title {
    margin-bottom: 16px;
}

.vx-techstack__chips {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 0;
    margin: 0;
}

.vx-techstack__chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 40px;
    padding: 0 13px;
    background: #fafcfb;
    border: 1px solid #e4e6e8;
    border-radius: 8px;
    font-size: 13.5px;
    font-weight: 500;
    color: #20282D;
    white-space: nowrap;
}

.vx-techstack__chip img {
    width: 22px;
    height: 22px;
    object-fit: contain;
    display: block;
}

/* ---- stack-logos: variant C, one panel ----------------------------------
   One object instead of seven. The count beside each label is the point of
   the layout: breadth reads at a glance without counting chips. */
.vx-techstack__panel {
    background: #f7f9f9;
    border: 1px solid #e4e6e8;
    border-radius: 14px;
    padding: 40px 38px;
    margin-top: 44px;
}

.vx-techstack__group + .vx-techstack__group {
    margin-top: 26px;
    padding-top: 26px;
    border-top: 1px solid #e4e6e8;
}

.vx-techstack__ghead {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 14px;
}

.vx-techstack__ghead h3 {
    font-size: 16px;
    margin: 0;
    letter-spacing: 0.01em;
}

.vx-techstack__ghead span {
    font-size: 12px;
    color: #5D666F;
}

.vx-techstack__pills {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 0;
    margin: 0;
}

.vx-techstack__pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 38px;
    padding: 0 12px;
    background: #fff;
    border: 1px solid #e4e6e8;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 500;
    color: #20282D;
    white-space: nowrap;
}

.vx-techstack__pill img {
    width: 19px;
    height: 19px;
    object-fit: contain;
    display: block;
}

.vx-techstack__mono--sm {
    width: 19px;
    height: 19px;
    font-size: 8px;
}

/* Temporary marker for the side-by-side comparison. Delete with the devLabel
   prop once a layout is chosen. */
.vx-techstack__devlabel {
    display: inline-block;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #fff;
    background: #20282D;
    border-radius: 4px;
    padding: 7px 12px;
    margin: 0 0 18px;
}

@media only screen and (max-width: 991px) {
    .vx-techstack__cards {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media only screen and (max-width: 767px) {
    .vx-techstack__cards {
        grid-template-columns: 1fr;
        gap: 18px;
    }

    .vx-techstack__panel {
        padding: 26px 20px;
    }
}

/* Stacked: the label becomes a heading over its own row. */
@media only screen and (max-width: 767px) {
    .vx-techstack__row {
        grid-template-columns: 1fr;
        gap: 12px;
        padding: 22px 0;
    }

    .vx-techstack__key {
        padding-top: 0;
    }

    .vx-techstack__items {
        gap: 10px 20px;
    }

    .vx-techstack__item {
        font-size: 14px;
    }
}


/* ==========================================================================
   RESTORED. The three groups below (client-proof rings, engagement-model
   cards, dark-band heading colour) were committed in 748d775 and b3e0b30 and
   were then lost when this file was rewritten from a base that predated them.
   The home page still renders .vx-proofring and .vx-modelcard markup, so
   without these rules those two sections render unstyled.
   ========================================================================== */

/* ---- Client proof: ringed medallions ------------------------------------
   The supplied design, minus the progress arc. The reference draws
   svg.radial-progress and animates its dash offset from data-countervalue —
   76%, 89%, 70%. This content has no percentages in it, so there is no ratio
   for an arc to represent and any number given to one would be invented. The
   ring is a closed CSS border instead: a frame around the icon, not a score.

   Dropping the SVG costs one thing the theme assumed. It positions the icon
   absolutely at the centre of .single-progress-area, sized by the svg beside
   it — with the svg gone the medallion has nothing to give it a size, so the
   ring carries its own. */
.vx-proofring .single-progress-circle {
    position: relative;
    flex: 0 0 auto;
    width: 104px;
    height: 104px;
    border: 2px solid #20282D;
    border-radius: 50%;
}

/* The icons ship at 98px with their own #F2F2F2 disc baked in, so they are
   sized down to sit inside the ring with the gap the reference shows. */
.vx-proofring .single-progress-circle img {
    width: 84px;
    height: 84px;
}

/* "US-Based" is a word in the slot the other two fill with a figure. At the
   48px the theme gives .title it overpowers the row and wraps; two steps down
   sits it optically level with the digits beside it. */
.vx-proofring__word {
    display: inline-block;
    font-size: 32px;
    line-height: 1.1;
    letter-spacing: -.01em;
}

/* Three cells across a 1305px container leaves the theme's -86px divider
   sitting far from the cell it divides. Distribute the row instead and let the
   rule sit midway. */
@media only screen and (min-width: 1200px) {
    .vx-proofring {
        justify-content: space-around;
    }
}

/* Below the theme's own 1199 cut the dividers are already hidden, so the cells
   just need room to breathe when they stack. */
@media only screen and (max-width: 1199px) {
    .vx-proofring {
        flex-wrap: wrap;
        justify-content: center;
        gap: 40px 64px;
    }
}

/* ---- Engagement models as cards -----------------------------------------
   The same three rows the comparison table carried, one card each, with the
   column headers kept as labels inside the card. A three-column table asks the
   reader to hold a header row in mind while scanning across; the labels put
   each value next to the word that names it.

   Nothing is dropped: Model becomes the card's heading, "Best for" and
   "Commercials" become labelled fields. */
.vx-modelcard {
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding: 38px 34px 34px;
    background: #FFFFFF;
    border: 1px solid var(--vx-rule, rgba(19, 24, 26, .12));
    border-radius: 10px;
}

.vx-modelcard__title {
    margin: 0;
    font-size: 22px;
    line-height: 1.3;
    font-weight: 700;
    color: var(--color-heading-1, #1C2539);
}

/* The label is the table header this card replaced, so it takes the same
   treatment the section eyebrows use: small, spaced, quiet. */
.vx-modelcard__label {
    display: block;
    margin-bottom: 6px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: #5D666F;
}

.vx-modelcard__value {
    margin: 0;
    font-size: 16px;
    line-height: 1.65;
}

/* The commercial terms are the line a reader is comparing across cards, so the
   last field sits apart from the rest rather than running on from "best for". */
.vx-modelcard__field:last-child {
    margin-top: auto;
    padding-top: 22px;
    border-top: 1px solid var(--vx-rule, rgba(19, 24, 26, .12));
}

/* ---- Dark sections: the heading has to change colour with them -----------
   REGRESSION FIX. The theme carries .bg-main .title-style-one .title { color:
   #fff } — its dark process band flips the heading to white. It has no such
   rule for title-style-four, because title-style-four never ran on a dark
   section in the template.

   Converting the site's headings to title-style-four dropped those blocks out
   of that override and onto title-style-four's own hard-coded #1C2539. Dark
   navy on #20282D measures 1.02:1 — the solid half was barely there and the
   outlined half, which is only a 1px stroke in the same colour, was gone
   completely. Six headings across six pages:

     /                                        How engagements actually run
     /how-we-work                             How the Collaboration Process Works
     /services/saas-product-development       How the Interaction Platform Works
     /services/ai-solutions                   How an AI engagement runs
     /services/custom-software-development    Legacy Modernization
     /services/product-design                 Design Inside the Engagement...

   Both the fill and the stroke have to move. Setting `color` alone is what the
   theme does for title-style-one, but title-style-four paints with
   -webkit-text-stroke and the accent sets -webkit-text-fill-color:transparent,
   so a heading here needs all three or half of it stays invisible. */
.bg-main .title-style-four .pre {
    color: #fff;
}

.bg-main .title-style-four .title {
    color: #fff;
    -webkit-text-fill-color: #fff;
    -webkit-text-stroke-color: #fff;
}

/* The accent keeps its outline; only the colour of that outline changes. */
.bg-main .title-style-four .title span {
    -webkit-text-fill-color: transparent;
    -webkit-text-stroke-color: #fff;
}

/* ---- Blog post banner ---------------------------------------------------
   The template shipped .blog-details-banner-large-image as a 703px box whose
   only picture was a CSS background: images/blog/18.webp, a stock photograph
   of three people in a meeting that belongs to the Invena demo. The <img> the
   view puts inside it was never styled, so a real featured image rendered at
   its own intrinsic size in the top-left corner — on production that was a
   450x360 grey "450X360" placeholder sitting on someone else's stock photo.

   So: drop the background, and make whichever element the view emits — <img>
   for a post with only a still, <video> for one with a banner clip — fill the
   box. object-fit:cover because the box is 2.05:1 and the artwork is 16:9;
   letterboxing a banner reads as a mistake, a 13% crop does not.            */
.blog-details-banner-large-image {
    background-image: none;
    background-color: #20282d;
    overflow: hidden;
}

.blog-details-banner-large-image > img,
.blog-details-banner-large-image > video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The clip carries an audio track, so the controls have to be reachable. They
   are the browser's own; this only keeps them off the very edge of the crop. */
.vx-post-banner-video {
    background-color: #20282d;
}

/* No prefers-reduced-motion rule: nothing moves until the reader presses play,
   which is already the behaviour that query asks for. */

/* The template's banner is a fixed height at every width: 703 / 450 / 350px.
   That was fine when the box only held a background photograph, where a crop
   costs nothing. These banners carry the post title as set type, and at 390px
   the 350px-tall box is 1.11:1 against 16:9 artwork — cover then crops
   horizontally and eats the first two words ("HOW TO GET" rendered as "O GET").

   Below 1250px the height becomes the aspect ratio instead, so the crop is
   always vertical and the type always survives. 1250 is where the template's
   own 703px height first reaches 16:9 on its own (1250/703 = 1.78); above it
   the fixed height already crops the safe way. The sibling padding rules above
   key off the element, not its height, so they are unaffected. */
@media (max-width: 1249px) {
    .blog-details-banner-large-image {
        height: auto;
        aspect-ratio: 16 / 9;
    }
}

/* ---- Blog author box ----------------------------------------------------
   No headshot is on file for the one author, and the template filled that slot
   with a stock photograph of someone else. An initial is the honest stand-in:
   it holds the layout without claiming to be a portrait of anybody.          */
.author-area-blog .thumbnail .vx-author-initial {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background: #20282d;
    color: #fff;
    font-size: 38px;
    font-weight: 600;
    line-height: 1;
    user-select: none;
}

.author-area-blog .author-details h5 a {
    color: inherit;
    text-decoration: none;
}

.author-area-blog .author-details h5 a:hover {
    text-decoration: underline;
}

/* ---- Estimator onboarding questions -------------------------------------
   The block of per-service questions between the contact fields and the free
   text box. Set apart with a rule and a heading so the form reads as two
   things — who you are, then what you need — rather than one long column of
   inputs the eye gives up on.                                               */
.vx-estimate-questions {
    border-top: 1px solid #e2e4da;
    padding-top: 28px;
    margin-top: 8px;
}

.vx-estimate-questions__title {
    font-size: 18px;
    font-weight: 600;
    margin: 0 0 4px;
}

.vx-field__help {
    font-size: 13px;
    line-height: 1.5;
    color: #6e7264;
    margin: 2px 0 8px;
}

/* Checkbox groups. The template had no multi-select control at all, so this is
   built rather than restyled: a real fieldset/legend (screen readers announce
   the question once, then each option), and 44px rows so the tap target clears
   WCAG 2.5.5 on a phone. */
.vx-checkgroup {
    border: 0;
    margin: 0;
    padding: 0;
    min-width: 0;
}

.vx-checkgroup legend {
    font-size: 15px;
    font-weight: 500;
    padding: 0;
    margin-bottom: 6px;
}

.vx-checkgroup__item {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    font-size: 15px;
    font-weight: 400;
    cursor: pointer;
    margin: 0;
}

.vx-checkgroup__item input[type="checkbox"] {
    width: 18px;
    height: 18px;
    flex: 0 0 18px;
    accent-color: #20282d;
    margin: 0;
}

@media (min-width: 576px) {
    /* Two columns once there is room; the lists are 3–6 items, so a single
       column wastes half the width and pushes the submit button off-screen.

       Capped rather than filling the field: at 865px an unbounded pair of
       columns leaves "iOS" and "Android" a third of the form apart, and the
       eye stops reading them as one question. */
    .vx-checkgroup {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        column-gap: 24px;
        max-width: 520px;
    }
    .vx-checkgroup legend {
        grid-column: 1 / -1;
        max-width: none;
    }
}

/* ---- Estimate form anchor -----------------------------------------------
   The service pages' hero button jumps to #estimate-form on the same page.
   The header is position:fixed and about 106px tall, so an unadjusted jump
   lands with the section heading underneath it — the reader arrives looking
   at the middle of the first field. scroll-margin-top parks the heading just
   below the bar instead.                                                    */
#estimate-form {
    scroll-margin-top: 130px;
}

@media (max-width: 767px) {
    #estimate-form { scroll-margin-top: 96px; }
}

/* Visually hidden, still read by screen readers and counted as link text.
   Ten "Read More" links to ten different industries are ten identical link
   names without it. clip-path over the old clip: same effect, not deprecated. */
.vx-sr {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
