/* ==========================================================================
   Vipulse — second theme
   ==========================================================================

   A Swiss-modernist reading of the same home page: one strict grid, one
   accent, hairlines instead of shadows, and type doing the work that
   decoration usually does.

   Three rules decide almost everything below.

   1. ONE ACCENT. #a32b2c is lifted from the existing navbar, so the new theme
      is recognisably the same company rather than a generic template. It
      appears on links, on the rule under a hovered item, and on exactly one
      button per screenful. Nothing else is coloured.

   2. NO SHADOWS, NO GRADIENTS, NO RADIUS. Separation is a 1px rule or a change
      of paper. This is the single biggest difference from the default
      "modern site" look, which reaches for a soft shadow and a rounded card
      every time it needs to say two things are different.

   3. THE GRID IS ASYMMETRIC. Headings sit in a 7-column well, images bleed
      past the right margin, the stats row is one line of type rather than four
      boxes. Centring everything is what makes a page look generated.

   Type is one superfamily in three cuts — IBM Plex Serif for display, Sans for
   reading, Mono for labels and figures. One family means the page has a single
   voice; the three cuts give it hierarchy without a second typeface.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tokens
   -------------------------------------------------------------------------- */

:root {
    /* Warm paper rather than #fff: pure white under a full-bleed photograph
       reads as unfinished, and every screen renders it slightly differently. */
    --v2-paper: #fbfaf7;
    --v2-paper-sunk: #f3f1ec;
    /* The dark ground carries blue now rather than being a neutral black. It is
       the footer as well as the testimonial, so this alone puts a good deal of
       the second colour on the page. */
    --v2-night: #111c2b;

    /* A blue-black rather than a neutral one, so the body text belongs to the
       same family as the grounds instead of sitting apart from them. */
    --v2-ink: #131a24;
    --v2-ink-soft: #444d59;
    --v2-ink-mute: #757e8a;
    --v2-ink-invert: #f6f4f0;

    --v2-rule: #dfdcd4;
    --v2-rule-strong: #131a24;
    --v2-rule-invert: #26344a;

    /* Red is what the page does — the calls to action, a link under the
       pointer, the one band that has to stop you. Blue is what the page stands
       on. Keeping the two jobs apart is what stops a second colour becoming
       decoration. */
    --v2-accent: #a32b2c;
    --v2-accent-deep: #7e1f20;

    --v2-blue: #1b3b5f;
    --v2-blue-deep: #12293f;

    /* Two faces, both ordinary on purpose. Montserrat sets the headings and
       Nunito Sans does the reading — the pair the category leader actually
       ships, and the pair a real company's site is built out of.
     *
     * There is no third font. The mono that used to label everything was the
     * single loudest tell that this page came out of a generator; labels get
     * their job done here with weight and letter-spacing instead, which is what
     * every measured demo does. */
    --v2-head-font: "Montserrat", "Segoe UI", Arial, sans-serif;
    --v2-sans: "Nunito Sans", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;

    /* An 8px base, doubling rather than drifting. Every margin below is one of
       these; nothing is nudged by 3px to make it "look right". */
    --v2-s1: 8px;
    --v2-s2: 16px;
    --v2-s3: 24px;
    --v2-s4: 40px;
    --v2-s5: 64px;
    --v2-s6: 96px;
    --v2-s7: 128px;

    --v2-gutter: clamp(20px, 4vw, 72px);
    /* The same value negated, written as its own clamp with the bounds
       mirrored rather than as `calc(var(--v2-gutter) * -1)`. Arithmetic on a
       clamp reads as a puzzle at the one place in this file where the number
       has to be obviously right. */
    --v2-gutter-neg: clamp(-72px, -4vw, -20px);
    /* The measure, capped so the page composes the same way on every screen
       wide enough to hold it.

       It was 2160 - effectively full bleed, on the argument that a cap under
       the width of a 1920 display leaves a visible band of empty paper either
       side. That argument was about the paper, not about the page. On a 2560
       monitor the shell opened to 2160 and everything in it went with it: the
       masthead photograph half a metre across, three figures spread over two
       thousand pixels, rows whose proportions no longer matched the ones they
       were drawn with. The site did not look wider, it looked like a different
       site - which is what somebody reading it on a large monitor reported.

       1440 is the width this layout was drawn and reviewed at, so at and above
       it every ruled row, every grid and every band keeps its proportions and
       the extra pixels become margin. Below it nothing changes: the shell is
       width:100% and this is only a ceiling. */
    --v2-shell: 1440px;
    --v2-gap: clamp(16px, 2vw, 32px);

    --v2-ease: cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* --------------------------------------------------------------------------
   Reset — narrow on purpose, this theme owns its own document
   -------------------------------------------------------------------------- */

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

html {
    -webkit-text-size-adjust: 100%;
}

body.v2 {
    margin: 0;
    background: var(--v2-paper);
    color: var(--v2-ink);
    font-family: var(--v2-sans);
    /* 17px: this page is read, not scanned, and 16px Plex Sans sets a touch
       small at the measure below. */
    font-size: 1.0625rem;
    line-height: 1.6;
    font-feature-settings: "kern" 1;
    -webkit-font-smoothing: antialiased;
    /* Belt and braces against the bleeding images: nothing may scroll the page
       sideways, on any width. */
    overflow-x: hidden;
}

/* Every reset below is wrapped in :where(), which contributes no specificity at
   all. That is not a style preference, it is the fix for a bug this file hit
   four separate times.
 *
 * Written plainly, `.v2 p` is one class plus one element — and that beats a
 * component class like `.v2-hero__lede`. So the reset's `margin: 0 0 24px` won,
 * the component's `margin-top: 40px` lost, and the gap under the headline
 * measured zero while the rule that was supposed to set it sat right there in
 * the file looking correct. The same collision hid a dark-on-dark button, a
 * white-on-white one, and the hero photograph's bleed.
 *
 * With :where() the resets have zero weight, so any class beats them and a
 * component rule means what it says. */

:where(.v2) :where(img) {
    max-width: 100%;
    height: auto;
    display: block;
}

:where(.v2) :where(a) {
    color: inherit;
    text-decoration: none;
}

:where(.v2) :where(p) {
    margin: 0 0 var(--v2-s3);
    /* 78 characters rather than 68. Still inside the range that stays
       comfortable to read, but it fills the wider shell instead of leaving a
       narrow ribbon of text down the left of it. */
    max-width: 78ch;
}

:where(.v2) :where(h1, h2, h3, h4) {
    margin: 0;
    font-weight: 500;
    letter-spacing: -0.015em;
    text-wrap: balance;
}

:where(.v2) :where(ul) {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The user-agent default here is `1em 40px`. */
:where(.v2) :where(figure, blockquote) {
    margin: 0;
}

.v2 :focus-visible {
    outline: 2px solid var(--v2-accent);
    outline-offset: 3px;
}

/* The keyboard user's way past the header, and invisible to everyone else
   until it is focused. */
.v2-skip {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 100;
    padding: var(--v2-s2) var(--v2-s3);
    background: var(--v2-ink);
    color: var(--v2-ink-invert);
    font: 600 0.875rem/1 var(--v2-sans);
    /* Clipped rather than parked at -9999px. Off-canvas positioning is still
       laid out, so it registers as content to the left of the viewport and
       turns up in any honest overflow audit — which makes the audit useless for
       finding the overflow that actually matters. */
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.v2-skip:focus {
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
}

/* --------------------------------------------------------------------------
   Shell and grid
   -------------------------------------------------------------------------- */

.v2-shell {
    width: 100%;
    max-width: var(--v2-shell);
    margin-inline: auto;
    padding-inline: var(--v2-gutter);
}

.v2-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--v2-gap);
}

.v2-section {
    padding-block: clamp(var(--v2-s5), 8vw, var(--v2-s7));
    border-top: 1px solid var(--v2-rule);
}

.v2-section--sunk {
    background: var(--v2-paper-sunk);
}

/* --------------------------------------------------------------------------
   The brand band
   --------------------------------------------------------------------------

   The alternating sections were a second, slightly darker cream. They are the
   brand red now, which makes the page paper / red / black — the three-ground
   palette Swiss work has always used, rather than two shades of the same
   off-white doing the job of one.

   Reversing a whole section out is not a background swap. Every token that
   named a grey has to name a tint of the red instead, and the three things that
   were drawn IN the accent — the link hover, the row cue, the focus ring — have
   to stop being red, because red on red is nothing at all. Each override below
   exists for one of those two reasons. */

/* Two reversed grounds, one set of rules.
 *
 * Everything structural is shared: white text, tinted rules, and the three
 * things normally drawn IN the accent — link hover, row cue, focus ring — taken
 * off it, because on the red band red is the ground and on the blue one it is
 * still the call to action sitting on top. Only the ground colour and its tints
 * differ per modifier. */
.v2-section--brand,
.v2-section--blue {
    color: #fff;
    --v2-ink: #ffffff;

    /* Board traces at the two margins, dissolving inwards.
     *
     * Cut off at a fixed column they ended on a hard vertical line, and a
     * pattern that stops dead in mid-air is the thing that reads as pasted on.
     * So the drawing tiles the whole ground as before and a gradient in the
     * band's own colour is laid over it — opaque across the middle, fading to
     * nothing at both edges. The traces now come out of the margins and thin
     * away as they approach the text, with no edge anywhere.
     *
     * The gradient needs to know which ground it is covering, so each modifier
     * declares `--v2-ground` and this rule reads it. Percentages rather than
     * pixels, so the clear middle keeps its proportion at every width and the
     * whole thing still works on a phone.
     */
    background-image:
        linear-gradient(to right,
            transparent 0,
            var(--v2-ground) 24%,
            var(--v2-ground) 76%,
            transparent 100%),
        url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='240'%20height='240'%20viewBox='0%200%20240%20240'><g%20opacity='0.09'%20fill='none'%20stroke='%23ffffff'%20stroke-width='1.5'%20stroke-linecap='square'><path%20d='M0%2060h240'/><path%20d='M180%200v240'/><path%20d='M30%20190h56l34-34h64'/><path%20d='M60%20240v-26l26-26'/><path%20d='M0%20120h44l22-22h40'/><rect%20x='120'%20y='20'%20width='34'%20height='20'/><rect%20x='16'%20y='84'%20width='22'%20height='30'/></g><g%20opacity='0.09'%20fill='%23ffffff'><circle%20cx='180'%20cy='60'%20r='4'/><circle%20cx='66'%20cy='60'%20r='3'/><circle%20cx='30'%20cy='190'%20r='3'/><circle%20cx='184'%20cy='156'%20r='3'/><circle%20cx='106'%20cy='98'%20r='3'/><circle%20cx='180'%20cy='120'%20r='3'/></g></svg>");
    background-position: 0 0, left top;
    background-repeat: no-repeat, repeat;
    background-size: 100% 100%, 240px 240px;
}

.v2-section--brand {
    --v2-ground: #a32b2c;
    background-color: var(--v2-accent);
    border-top-color: var(--v2-accent);
    --v2-ink-soft: #fbe4e4;
    --v2-ink-mute: #f0c9c9;
    --v2-rule: #c4696a;
}

.v2-section--blue {
    --v2-ground: #1b3b5f;
    background-color: var(--v2-blue);
    border-top-color: var(--v2-blue);
    --v2-ink-soft: #d6e2ef;
    --v2-ink-mute: #a9bed4;
    --v2-rule: #3d5f83;
}

.v2-section--brand .v2-eyebrow,
.v2-section--brand .v2-index,
.v2-section--brand .v2-row__index,
.v2-section--blue .v2-eyebrow,
.v2-section--blue .v2-index,
.v2-section--blue .v2-row__index {
    color: var(--v2-ink-mute);
}

.v2-section--brand .v2-link:hover,
.v2-section--brand a.v2-row:hover .v2-row__title,
.v2-section--blue .v2-link:hover,
.v2-section--blue a.v2-row:hover .v2-row__title {
    color: #fff;
    border-bottom-color: #fff;
}

.v2-section--brand .v2-row__cue,
.v2-section--blue .v2-row__cue {
    background: #fff;
}

.v2-section--brand :focus-visible,
.v2-section--blue :focus-visible {
    outline-color: #fff;
}

.v2-section--brand .v2-field input:focus,
.v2-section--brand .v2-field textarea:focus,
.v2-section--blue .v2-field input:focus,
.v2-section--blue .v2-field textarea:focus {
    border-bottom-color: #fff;
}

/* The solid button on a reversed band.
 *
 * On the red it has to stop being red: the accent is the ground there, and the
 * one element that must be unmissable would be the one that disappears. On the
 * blue it goes back to the accent, which is the argument for keeping red as the
 * action colour in the first place — the button is the only red thing on that
 * screenful and it is the thing to press.
 *
 * Selectors carry the `.v2` to beat `.v2 .v2-btn` further down the file on both
 * specificity and order. Without it the button keeps the base rule — whose
 * background is `var(--v2-ink)`, and `--v2-ink` is white inside these bands.
 * White on white: the button vanishes completely, which is exactly what
 * happened the first time. */
.v2 .v2-section--brand .v2-btn {
    background: #131a24;
    border-color: #131a24;
    color: var(--v2-paper);
}

.v2 .v2-section--brand .v2-btn:hover {
    background: var(--v2-paper);
    border-color: var(--v2-paper);
    color: #131a24;
}

.v2 .v2-section--blue .v2-btn {
    background: var(--v2-accent);
    border-color: var(--v2-accent);
    color: #fff;
}

.v2 .v2-section--blue .v2-btn:hover {
    background: var(--v2-accent-deep);
    border-color: var(--v2-accent-deep);
}

.v2 .v2-section--brand .v2-btn--ghost,
.v2 .v2-section--blue .v2-btn--ghost {
    background: transparent;
    border-color: var(--v2-rule);
    color: #fff;
}

.v2 .v2-section--brand .v2-btn--ghost:hover,
.v2 .v2-section--blue .v2-btn--ghost:hover {
    background: transparent;
    border-color: #fff;
    color: #fff;
}

/* The placeholder plate behind a case image, on this ground. */
.v2-section--brand .v2-cases__plate {
    background: var(--v2-accent-deep);
}

.v2-section--night {
    background: var(--v2-night);
    color: var(--v2-ink-invert);
    border-top-color: var(--v2-night);
}

/* Reversing out is not just swapping two variables. The greys that read as
   "secondary" on paper fall under 4.5:1 against a near-black ground, so each
   dark band gets its own, lighter set — tinted towards the ground it stands on
   so it reads as quieter rather than as dirty. Checked rather than eyeballed:
   dark text on a dark ground is the contrast failure that looks deliberate and
   therefore never gets reported. */
.v2-section--night {
    --v2-ink-soft: #c5cdd8;
    --v2-ink-mute: #98a4b4;
}

.v2-section--night .v2-eyebrow,
.v2-section--night .v2-index {
    color: var(--v2-ink-mute);
}

.v2-section--tight {
    padding-block: var(--v2-s4);
}

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

.v2-display {
    font-family: var(--v2-head-font);
    /* Mixed case at 700, the way every demo in this category sets it. The
       capitals came off with the condensed face: upper case suits a narrow cut
       and turns a normal-width one into a wall.
     *
     * Sized by counting lines, not by eye. Montserrat sets much wider than the
       condensed face it replaces, so the same clamp would have run the headline
       to five lines. */
    font-weight: 700;
    font-size: clamp(1.875rem, 0.8rem + 2.7vw, 3.75rem);
    line-height: 1.12;
    letter-spacing: -0.02em;
}

/* The same headline is longer in some languages than others. A notch down for
   the three that run long keeps the masthead the same shape in all six.
   Measured per language, not guessed. */
.v2-display:lang(ru),
.v2-display:lang(tr),
.v2-display:lang(hu) {
    font-size: clamp(1.75rem, 0.75rem + 2.3vw, 3.25rem);
}

.v2-h2 {
    font-family: var(--v2-head-font);
    font-weight: 600;
    font-size: clamp(1.625rem, 1.1rem + 1.9vw, 2.75rem);
    line-height: 1.12;
    letter-spacing: -0.01em;
}

.v2-h3 {
    font-family: var(--v2-sans);
    font-weight: 500;
    font-size: 1.1875rem;
    line-height: 1.35;
    letter-spacing: -0.01em;
}

.v2-lede {
    font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.375rem);
    line-height: 1.5;
    color: var(--v2-ink-soft);
    max-width: 68ch;
}

.v2-muted {
    color: var(--v2-ink-mute);
}

/* The mono label that runs above every section. Small, wide-tracked and
   uppercase — the one piece of styling repeated everywhere, which is what
   makes the page feel like a single document rather than a stack of blocks. */
.v2-eyebrow {
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
}

.v2-index {
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
    font-variant-numeric: tabular-nums;
}

/* Section header: a registration mark, a number, a name, and a rule that runs
   to the right margin where it drops a tick.

   The cross and the tick are borrowed from the drawing sheet — the marks a
   draughtsman puts down to say where a sheet is aligned and where a dimension
   ends. They carry no information here, which is the one thing that would make
   them decoration; what they do is set the register the rest of the page reads
   in, so a list of services looks like a parts list rather than a menu. Two
   marks, 9px each, in the muted ink. Anything more and it is a theme. */
.v2-head {
    position: relative;
    display: flex;
    align-items: baseline;
    gap: var(--v2-s2);
    padding-bottom: var(--v2-s2);
    border-bottom: 1px solid var(--v2-rule);
    margin-bottom: var(--v2-s4);
}

/* The double slash. Every best seller in this niche puts one in front of its
   section label — it is the closest thing the trade has to a signature, and two
   characters is the amount of accent that reads as a mark rather than as a
   coloured heading. */
.v2-head::before {
    content: "//";
    flex: none;
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    color: var(--v2-accent);
}

.v2-section--brand .v2-head::before {
    color: #fff;
}

/* The tick where the rule stops. */
.v2-head::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: -1px;
    width: 1px;
    height: 9px;
    background: var(--v2-ink-mute);
}

/* A measuring scale, used once: across the full width between the masthead and
   the figures, where the page changes gear. Minor ticks every 12px, a major one
   every sixth, exactly like a rule. */
.v2-section--night .v2-head {
    border-bottom-color: var(--v2-rule-invert);
}

.v2-head__rule {
    flex: 1;
}

/* --------------------------------------------------------------------------
   Links and buttons
   -------------------------------------------------------------------------- */

/* An underline that is part of the type rather than drawn under it: 1px, offset
   far enough to clear the descenders, and it changes colour rather than
   appearing from nowhere on hover. */
.v2-link {
    display: inline-block;
    border-bottom: 1px solid var(--v2-rule);
    padding-bottom: 2px;
    transition: border-color 180ms var(--v2-ease), color 180ms var(--v2-ease);
}

.v2-link:hover {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
}

.v2-more {
    font-family: var(--v2-sans);
    font-size: 0.8125rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* Written as `.v2 .v2-btn` rather than `.v2-btn`, because the reset above sets
   `color: inherit` on `.v2 a` — two selector components against one, so a bare
   `.v2-btn` loses and an anchor styled as a solid button renders dark ink on
   its own dark ground. Invisible, and only on the anchors: the submit button in
   the form is a <button> and was never affected, which is exactly how this kind
   of thing survives a quick look. */
.v2 .v2-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 48px tall at the default font size: comfortably past the 44px minimum
       without becoming a slab. */
    min-height: 48px;
    padding: 0 var(--v2-s3);
    border: 1px solid var(--v2-accent);
    background: var(--v2-accent);
    color: #fff;
    font: 500 0.9375rem/1 var(--v2-sans);
    cursor: pointer;
    transition: background-color 180ms var(--v2-ease), border-color 180ms var(--v2-ease);
}

.v2 .v2-btn:hover {
    background: var(--v2-accent-deep);
    border-color: var(--v2-accent-deep);
}

.v2 .v2-btn--ghost {
    background: transparent;
    color: var(--v2-ink);
    border-color: var(--v2-rule);
}

.v2 .v2-btn--ghost:hover {
    background: transparent;
    border-color: var(--v2-ink);
    color: var(--v2-ink);
}

.v2-section--night .v2-btn--ghost {
    color: var(--v2-ink-invert);
    border-color: var(--v2-rule-invert);
}

.v2-section--night .v2-btn--ghost:hover {
    border-color: var(--v2-ink-invert);
    color: var(--v2-ink-invert);
}

/* --------------------------------------------------------------------------
   Utility bar
   -------------------------------------------------------------------------- */

.v2-utility {
    background: var(--v2-night);
    color: var(--v2-ink-invert);
}

.v2-utility__bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--v2-s1) var(--v2-s3);
    min-height: 40px;
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.v2-utility__spacer {
    flex: 1;
}

.v2-utility__item {
    color: #9aa8bb;
    padding-block: var(--v2-s1);
    transition: color 160ms var(--v2-ease);
}

/* The email, in the case it is actually written in.

   The bar sets everything in small caps, which is right for a label and wrong
   for an address: an address is a string somebody may copy, and printing it in
   another case makes the bar disagree with the mailto: beside it. In Turkish
   it is worse than cosmetic - the locale uppercases "i" to "İ", so
   info@vipulse.com was displayed as İNFO@VİPULSE.COM, which is not the address
   at all. The tracking goes with it: an email is read as one token, not as a
   row of spaced-out capitals. */
.v2-utility__item[href^="mailto:"] {
    text-transform: none;
    letter-spacing: 0.02em;
}

.v2-utility__item:hover {
    color: #fff;
}

/* The second number is the first thing to go. Two of them plus an email fill a
   narrow bar before the call to action has had any room at all. */
@media (max-width: 767px) {
    .v2-utility__item--phone ~ .v2-utility__item--phone {
        display: none;
    }
}

.v2-utility__cta {
    align-self: stretch;
    display: inline-flex;
    align-items: center;
    padding-inline: var(--v2-s3);
    background: var(--v2-accent);
    color: #fff;
    font-weight: 500;
    letter-spacing: 0.1em;
    transition: background-color 180ms var(--v2-ease);
}

.v2-utility__cta:hover {
    background: var(--v2-accent-deep);
}

/* --------------------------------------------------------------------------
   Service strip — the masthead's last line
   -------------------------------------------------------------------------- */

.v2-strip {
    background: var(--v2-night);
    color: var(--v2-ink-invert);
    margin-top: clamp(var(--v2-s4), 4vw, var(--v2-s5));
}

.v2-strip__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}

.v2-strip__item {
    display: flex;
    align-items: baseline;
    gap: var(--v2-s2);
    padding: var(--v2-s3) 0;
    border-top: 1px solid var(--v2-rule-invert);
    transition: color 180ms var(--v2-ease);
}

.v2-strip__item:first-child {
    border-top: 0;
}

.v2-strip__index {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    color: #7d8ca1;
    font-variant-numeric: tabular-nums;
}

.v2-strip__name {
    font-family: var(--v2-head-font);
    font-weight: 700;
    font-size: 1.0625rem;
    letter-spacing: -0.01em;
}

a.v2-strip__item:hover {
    color: #fff;
}

a.v2-strip__item:hover .v2-strip__name {
    /* The accent underline the rest of the page uses for a live row. */
    box-shadow: inset 0 -1px 0 var(--v2-accent);
}

@media (min-width: 900px) {
    .v2-strip__row {
        /* One column per service, however many there turn out to be. */
        grid-auto-flow: column;
        grid-auto-columns: minmax(0, 1fr);
        grid-template-columns: none;
    }

    .v2-strip__item {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--v2-s1);
        padding: var(--v2-s3) var(--v2-s3) var(--v2-s3) 0;
        border-top: 0;
        border-left: 1px solid var(--v2-rule-invert);
        padding-left: var(--v2-s3);
    }

    .v2-strip__item:first-child {
        border-left: 0;
        padding-left: 0;
    }
}

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */

.v2-header {
    position: sticky;
    top: 0;
    z-index: 40;
    background: var(--v2-paper);
    border-bottom: 1px solid var(--v2-rule);
}

.v2-header__bar {
    display: flex;
    align-items: center;
    gap: var(--v2-s2);
    /* Wraps rather than overflows. Six language codes plus the wordmark do not
       fit on one 320px line, and a header that scrolls sideways takes the whole
       page with it. */
    flex-wrap: wrap;
    min-height: 64px;
    padding-block: var(--v2-s1);
}

.v2-logo {
    display: block;
    flex: none;
}

.v2-logo img {
    /* Sized by height, width auto: the mark is much wider than it is tall, and
       pinning the width would let a future logo of another proportion push the
       header out of its own bar. */
    height: 30px;
    width: auto;
}

@media (min-width: 1024px) {
    .v2-logo img {
        height: 36px;
    }
}

.v2-nav {
    display: none;
    margin-left: auto;
    gap: clamp(var(--v2-s2), 2vw, var(--v2-s4));
}

.v2-nav__item {
    position: relative;
}

.v2-nav__link {
    display: block;
    padding-block: var(--v2-s3);
    font-size: 0.9375rem;
    font-weight: 450;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    transition: border-color 180ms var(--v2-ease);
}

.v2-nav__item:hover > .v2-nav__link,
.v2-nav__item:focus-within > .v2-nav__link {
    border-bottom-color: var(--v2-accent);
}

/* The dropdown is a plain panel on the same paper, aligned to the item and
   held open by hover or focus — no script, so it works before the page has
   finished loading and for anyone driving it from the keyboard. */
.v2-nav__panel {
    position: absolute;
    top: 100%;
    left: calc(var(--v2-s3) * -1);
    min-width: 260px;
    padding: var(--v2-s3);
    background: var(--v2-paper);
    border: 1px solid var(--v2-rule);
    border-top: 1px solid var(--v2-ink);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 160ms var(--v2-ease), transform 160ms var(--v2-ease), visibility 160ms;
}

.v2-nav__item:hover > .v2-nav__panel,
.v2-nav__item:focus-within > .v2-nav__panel {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.v2-nav__panel a {
    display: block;
    padding-block: var(--v2-s1);
    font-size: 0.9375rem;
    color: var(--v2-ink-soft);
    transition: color 160ms var(--v2-ease);
}

.v2-nav__panel a:hover {
    color: var(--v2-accent);
}

.v2-nav__group {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    padding-top: var(--v2-s2);
}

.v2-header__aside {
    display: flex;
    align-items: center;
    gap: var(--v2-s2);
    margin-left: auto;
}

.v2-nav + .v2-header__aside {
    margin-left: 0;
}

/* The language switcher: a globe that opens a list.

   Built on <details>, so the open/closed state, the keyboard handling and the
   announcement to assistive tech are the browser's job. A scripted dropdown
   would have to reimplement all three and would be dead on a page whose script
   failed. */
/* LinkedIn, on the white bar beside the language globe.

   Sized to the same 44px target as the switcher next to it so the two read as
   one pair of controls rather than as a link that happens to sit near a
   button. The mark is drawn in the bar's own ink at rest and takes the accent
   on hover — the same two states every other control in this masthead has, so
   nothing about it announces that it leaves the site except the mark itself. */
.v2-social {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 44px;
    height: 44px;
    color: var(--v2-ink-soft, #55606f);
    transition: color 120ms ease;
}

.v2-social svg {
    width: 20px;
    height: 20px;
    fill: currentColor;
}

.v2-social:hover,
.v2-social:focus-visible {
    color: var(--v2-accent, #d5202a);
}

.v2-lang {
    position: relative;
    flex: none;
}

.v2-lang > summary {
    display: flex;
    align-items: center;
    gap: var(--v2-s1);
    /* 44px of target, which is the floor for a control anyone taps. */
    min-height: 44px;
    padding-inline: var(--v2-s1);
    cursor: pointer;
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    list-style: none;
    transition: color 160ms var(--v2-ease);
}

/* Both spellings of "no disclosure triangle": the pseudo-element is WebKit's,
   the list-style is everyone else's. */
.v2-lang > summary::-webkit-details-marker {
    display: none;
}

.v2-lang > summary:hover,
.v2-lang[open] > summary {
    color: var(--v2-ink);
}

.v2-globe {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.25;
    /* Round joins would soften a drawing that is meant to read as drawn. */
    stroke-linecap: square;
}

/* --------------------------------------------------------------------------
   The narrow-screen menu
   -------------------------------------------------------------------------- */

.v2-burger {
    display: flex;
    align-items: center;
    gap: var(--v2-s1);
    min-height: 44px;
    padding-inline: var(--v2-s1);
    border: 0;
    background: transparent;
    color: var(--v2-ink);
    cursor: pointer;
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

/* Three rules, drawn the way every other line on this page is drawn — as
   hairlines, not as a glyph or an icon font. */
.v2-burger__bars {
    width: 20px;
    height: 12px;
    background:
        linear-gradient(currentColor 0 0) top / 20px 1.5px no-repeat,
        linear-gradient(currentColor 0 0) center / 20px 1.5px no-repeat,
        linear-gradient(currentColor 0 0) bottom / 20px 1.5px no-repeat;
}

/* Open becomes a cross: the same three rules, the outer two rotated onto each
   other and the middle one taken away. */
.v2-burger[aria-expanded="true"] .v2-burger__bars {
    background:
        linear-gradient(currentColor 0 0) center / 20px 1.5px no-repeat,
        linear-gradient(currentColor 0 0) center / 20px 1.5px no-repeat;
    transform: rotate(45deg);
}

.v2-burger[aria-expanded="true"] .v2-burger__bars::before {
    content: "";
    display: block;
    width: 20px;
    height: 12px;
    background: linear-gradient(currentColor 0 0) center / 1.5px 20px no-repeat;
}

.v2-drawer {
    border-top: 1px solid var(--v2-rule);
    padding-block: var(--v2-s2) var(--v2-s4);
    /* Taller than the screen on a long menu, so it scrolls rather than pushing
       the page down under a header that is stuck to the top of it. */
    max-height: calc(100vh - 72px);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Only the script adds this. Before it runs the drawer is a plain list, which
   is the no-JS answer. */
.v2-drawer.is-collapsible {
    display: none;
}

.v2-drawer.is-collapsible.is-open {
    display: block;
}

.v2-drawer__link {
    display: block;
    padding-block: var(--v2-s2);
    border-bottom: 1px solid var(--v2-rule);
    font-size: 1.0625rem;
    font-weight: 500;
}

.v2-drawer__link--cta {
    color: var(--v2-accent);
}

.v2-drawer__group {
    display: grid;
    padding: var(--v2-s1) 0 var(--v2-s3) var(--v2-s3);
    border-bottom: 1px solid var(--v2-rule);
}

.v2-drawer__child {
    padding-block: 6px;
    font-size: 0.9375rem;
    color: var(--v2-ink-soft);
}

.v2-drawer .v2-nav__group {
    padding-top: var(--v2-s2);
}

@media (min-width: 1024px) {
    .v2-burger,
    .v2-drawer,
    /* The open state too, and spelled out at the same weight that set it. A
       media query adds no specificity, so a bare `.v2-drawer` here loses to
       `.v2-drawer.is-collapsible.is-open` and the drawer stays on screen beside
       the bar — which is what a phone left open and then rotated, or a window
       dragged wider, would have shown. */
    .v2-drawer.is-collapsible.is-open {
        display: none;
    }
}

/* Hung so that the language names land directly under the globe.
 *
 * Right-aligning it to the trigger put the names somewhere in the middle of the
 * panel, under nothing in particular. The offset is expressed in the same two
 * tokens that create it — the summary's own inline padding and the row's — so
 * the alignment is exact and stays exact if either changes, rather than being
 * a number that happened to look right once:
 *
 *     panel_left + row_padding  ==  trigger_left + summary_padding
 *
 * The panel is kept narrow enough that hanging it to the left of a trigger
 * sitting in the right gutter still cannot reach the edge of the window. */
.v2-lang__panel {
    position: absolute;
    top: 100%;
    /* Minus the border, which sits outside the row's padding box and would
       otherwise put the names one pixel off the globe. */
    left: calc(var(--v2-s1) - var(--v2-s3) - 1px);
    right: auto;
    z-index: 50;
    /* Sized by its longest name. A fixed width has to be wide enough for
       "Русский" at every viewport, and hanging that width to the left of a
       trigger that already sits in the right gutter pushed it off the screen. */
    width: max-content;
    padding: var(--v2-s1) 0;
    background: var(--v2-paper);
    border: 1px solid var(--v2-rule);
    border-top: 1px solid var(--v2-ink);
}

.v2-lang__panel a {
    display: flex;
    align-items: baseline;
    gap: var(--v2-s1);
    min-height: 40px;
    /* The left padding is load-bearing — it is half of the sum that puts the
       code column under the globe. The right one is only breathing room, so it
       is the one that gives way when the panel needs to be narrower. */
    padding: var(--v2-s1) var(--v2-s1) var(--v2-s1) var(--v2-s3);
    white-space: nowrap;
    color: var(--v2-ink-soft);
    transition: color 160ms var(--v2-ease), background-color 160ms var(--v2-ease);
}

.v2-lang__panel a:hover {
    color: var(--v2-accent);
    background: var(--v2-paper-sunk);
}

.v2-lang__panel [aria-current="true"] {
    color: var(--v2-ink);
}

/* The code column, back in front of the names.
 *
 * It is what the panel is hung from now: the codes run down the page directly
 * under the globe, so the column still reads as belonging to the control that
 * opened it. Narrow and tightly set, because every pixel it takes comes off the
 * clearance between the panel and the right edge of the window — the panel
 * hangs to the left of a trigger that already sits in the gutter. */
.v2-lang__code {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    width: 1.9em;
    flex: none;
}

.v2-lang__panel [aria-current="true"] .v2-lang__code {
    color: var(--v2-accent);
}

.v2-lang__name {
    font-size: 0.9375rem;
}

/* Centred on the screen below the header, not hung off the globe.
 *
 * On a phone the globe sits near the middle of a short header bar, and a panel
 * anchored to its right edge runs off one side or the other depending on how
 * wide the wordmark happens to be. Centred, it lands in the same place every
 * time and at every width. Fixed rather than absolute so it is measured against
 * the viewport instead of the header, which is what "centred" has to mean here.
 */
/* Centred on the screen wherever hanging it off the globe would put it against
   the edge of the window. At 1024 the gutter is at its narrowest and the panel
   came out exactly flush with the right edge — not overflowing, but reading as
   though the window had cut it off. Above 1280 there is real clearance and it
   hangs from the control, which is where it belongs. */
/* Below 1280 the panel hangs off the control, from the right.

   It used to be centred on the window here - fixed, 72px from the top, 320px
   wide - to stop it running flush with the right edge at around 1024, where
   the gutter is at its narrowest. It solved that, and it created a worse one:
   the list opened in the middle of the page, a long way from the globe that
   was tapped, reading as something that had opened by itself. On a phone it
   was 320px of a 375px screen.

   Anchoring it to the trigger's right edge solves the original problem
   instead of trading it. The globe sits in the right gutter, so a panel whose
   right edge is the trigger's cannot run off that side; at the width of its
   longest name - Русский - it cannot reach the left one either; and it stays
   attached to the control it belongs to at every width. */
@media (max-width: 1279px) {
    .v2-lang__panel {
        position: absolute;
        top: 100%;
        left: auto;
        right: 0;
        transform: none;
        width: max-content;
        max-width: calc(100vw - var(--v2-gutter) * 2);
        border-top: 1px solid var(--v2-ink);
    }
}

@media (min-width: 1024px) {
    .v2-nav {
        display: flex;
    }

    .v2-header__bar {
        flex-wrap: nowrap;
        min-height: 80px;
        gap: var(--v2-s3);
    }
}

/* --------------------------------------------------------------------------
   Hero — asymmetric on purpose
   -------------------------------------------------------------------------- */

.v2-hero {
    border-top: 0;
    /* Was clamp(64px, 7vw, 128px), which put 146px between the header and the
       first line at 1920 against 59px at the other end of the section — the
       masthead read as detached from the bar above it. Sized so the two ends
       stay in the same neighbourhood at every width. */
    padding-block: clamp(40px, 3.5vw, 72px) 0;
}

.v2-hero__text {
    grid-column: 1 / -1;
}

.v2-hero__meta {
    display: flex;
    align-items: center;
    gap: var(--v2-s2);
    margin-bottom: var(--v2-s3);
}

.v2-hero__meta::before {
    content: "//";
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    color: var(--v2-accent);
}

.v2-hero__meta::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--v2-rule);
}

.v2-hero__lede {
    /* A step more than the usual gap. The display face is set at a line height
       of 1.02, so its last line leaves almost no visual space beneath it and a
       24px margin reads as about eight. */
    margin-top: var(--v2-s4);
}

.v2-hero__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--v2-s2);
    margin-top: var(--v2-s4);
}

/* Carries the `.v2` so it outweighs the `.v2 figure { margin: 0 }` reset, which
   is element-scoped and therefore beats a bare class on margins. That reset was
   added to kill the user agent's 40px inset and quietly took the hero's bleed
   with it: grid-column and align-self kept applying, margin-right did not, and
   the photograph sat inside its column looking merely a little small. */
.v2 .v2-hero__figure {
    grid-column: 1 / -1;
    margin-top: var(--v2-s5);
}

/* Light washing in from the top left, the way the plate this replaced had it.

   The photograph it replaced carried the effect baked into the JPEG - a white
   glow over the top-left corner, fading diagonally, so the picture dissolved
   into the page instead of sitting on it as a hard rectangle. The new one is a
   clean photograph, so the wash is drawn here instead: reversible, and in the
   page's own paper colour rather than in white, which is what makes it read as
   the page continuing into the image rather than as fog over it.

   Two gradients. The linear one is the light itself, strongest along the top
   edge and gone by two fifths down. The radial one anchors it in the corner so
   it falls off diagonally rather than as a band across the top - a band reads
   as a scrim, a corner reads as a window.

   Pointer events off: nothing under it is clickable, but a full-size overlay
   that swallows a long-press on a photograph is the sort of thing nobody
   thinks to test. */
.v2 .v2-hero__figure {
    position: relative;
    isolation: isolate;
}

.v2 .v2-hero__figure::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        /* The light itself, running corner to corner rather than top to
           bottom. A vertical fade lands as a band along the top edge - the
           picture looks like it has a strip of fog on it. On the diagonal it
           reads as light entering the frame from outside the top-left, which is
           what the plate this replaced did. */
        linear-gradient(135deg, #fbfaf7 0%, rgba(251, 250, 247, 0.92) 12%, rgba(251, 250, 247, 0) 55%),
        /* The source, anchored in that corner and stretched along the same
           diagonal so the falloff is elliptical rather than circular. */
        radial-gradient(150% 130% at -8% -12%, #fbfaf7 0%, rgba(251, 250, 247, 0.6) 30%, rgba(251, 250, 247, 0) 68%);
}


.v2-hero__figure img {
    width: 100%;
    /* Capped, and cropped rather than shrunk. The photograph is a wide bench
       shot, so losing the top and bottom of it costs nothing — and without a
       ceiling its height tracks the column width, which means the hero grows
       taller the wider the screen gets and drags the whole masthead apart. */
    max-height: 440px;
    object-fit: cover;
    /* The engineer's head sits near the top of the frame; crop the bench below. */
    object-position: 50% 10%;
}

/* The photograph gets a plate number and its own caption, set in the mono cut
   under a hairline. It is the alt text, which had to be written anyway — so the
   caption costs nothing and the picture stops being wallpaper. */
.v2-hero__caption {
    display: flex;
    gap: var(--v2-s2);
    margin-top: var(--v2-s2);
    padding-top: var(--v2-s1);
    border-top: 1px solid var(--v2-rule);
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
}

.v2-hero__caption b {
    font-weight: 500;
    color: var(--v2-ink-soft);
}

@media (min-width: 900px) {
    .v2-hero__text {
        grid-column: 1 / 8;
    }

    .v2-hero__text {
        /* Centred against the photograph. Anchored to the top, every pixel the
           picture gained as the window widened turned into dead space between
           the buttons and the figures below — 41px at 1440 and 270px at 1920,
           which is the gap that got reported. */
        align-self: center;
    }

    .v2 .v2-hero__figure {
        grid-column: 8 / -1;
        margin-top: 0;
        align-self: end;
        /* Past the right gutter, so the photograph is cropped by the window
           rather than sitting politely inside a box. */
        margin-right: var(--v2-gutter-neg);
    }
}

/* --------------------------------------------------------------------------
   Stats — one line of type, not four cards
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Section intro — set on the same columns as the rows beneath it
   --------------------------------------------------------------------------

   The heading and its standfirst used to sit on the page's 12-column grid at
   1/7 and 8/-1, while the list below ran on its own 3rem / 5fr / 6fr. Two grids
   in one section, so the standfirst started 184px to the right of the sentences
   it introduced and the heading 80px to the left of the titles it named. Nobody
   would call that a bug; it just looked slightly wrong all the way down.
   One template for both fixes it, and the number gutter stays reserved for
   numbers. */

.v2-intro {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s3) var(--v2-gap);
}

/* The compact form of the intro, for a heading that is a label rather than a
   promise: the contacts page prints its title between the form and the
   addresses, and at the section's usual rhythm that one line took a whole
   screen of its own. Tighter row gap, and the standfirst set at reading size
   rather than as a lede. */
.v2-intro--tight {
    gap: var(--v2-s1) var(--v2-gap);
}

.v2-intro--tight .v2-intro__title {
    /* Smaller than an h2 as well. The .v2-h2 clamp still lands on 44px at
       1440, which is the size the page's real headings are set at — and this
       is not one of those. It names the page; the form above it and the
       addresses below it are what the page is for. */
    font-size: clamp(1.375rem, 1.1rem + 0.9vw, 1.875rem);
    line-height: 1.15;
}

.v2-intro--tight .v2-intro__text {
    font-size: 0.9375rem;
    color: var(--v2-ink-soft);
    max-width: 62ch;
}


.v2-intro__text {
    margin: 0;
}

@media (min-width: 768px) {
    /* Heading and standfirst stacked, not side by side.

       They were set on the same three tracks as the rows below - heading at
       5fr, standfirst at 6fr - which put a section's name and the sentence
       explaining it at opposite ends of a 1300px line, read as two separate
       things. Stacked, the sentence reads as what it is: the second line of
       the heading.

       The 3rem gutter stays. It is the column the row markers sit in, and it is
       the only reason a section's heading starts on the same vertical as the
       titles underneath it; drop it here and the heading hangs 48px to the left
       of its own list. */
    .v2-intro {
        grid-template-columns: 3rem minmax(0, 1fr);
        gap: var(--v2-s2) var(--v2-gap);
    }

    /* Both start on the page's own margin, not in the second track.

       They sat in the marker column's shadow - indented 3rem so the heading
       lined up with the row titles under it - and that put them out of line
       with the rule that opens the list, which starts at the margin like every
       other rule on the page. A heading three centimetres to the right of the
       line under it reads as a mistake, and the line is the stronger edge:
       there are four of them down a section and one heading. */
    .v2-intro__title,
    .v2-intro__text {
        grid-column: 1 / -1;
    }

    /* Held to a measure: under a heading the sentence has the whole shell
       available to it, and the whole shell is far past the width at which a
       line stops being readable. */
    .v2-intro__text {
        max-width: 62ch;
    }
}

/* --------------------------------------------------------------------------
   Editorial list — industries, services, ways of working
   -------------------------------------------------------------------------- */

.v2-list {
    border-top: 1px solid var(--v2-rule);
}

.v2-section--night .v2-list,
.v2-section--night .v2-row {
    border-color: var(--v2-rule-invert);
}

.v2-list--spaced {
    margin-top: var(--v2-s5);
}

.v2-after {
    margin-top: var(--v2-s4);
    margin-bottom: 0;
}

.v2-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s1) var(--v2-gap);
    padding-block: var(--v2-s3);
    border-bottom: 1px solid var(--v2-rule);
    transition: background-color 200ms var(--v2-ease);
}

.v2-row__index {
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    color: var(--v2-ink-mute);
    font-variant-numeric: tabular-nums;
}

/* The marker column: the industry's mark, with the row number under it.

   It goes in the column the number already had rather than in a new track of
   its own — a fifth track would push the sentence right on every row of every
   list, including the two lists that carry no icons at all. Fixed at 44px in
   both dimensions because the stored files run from 48 to 512 square, and a
   row of marks at their natural sizes is exactly what this list was drawn to
   avoid.

   Knocked back to the page's ink weight and brought up on hover, like every
   other part of the row - the mark is a label, not a logo. */
.v2-row__index {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
}

.v2-row__icon {
    width: 44px;
    height: 44px;
    object-fit: contain;
    opacity: 0.62;
    transition: opacity 200ms var(--v2-ease);
}

a.v2-row:hover .v2-row__icon,
.v2-row:has(.v2-row__link:hover) .v2-row__icon {
    opacity: 1;
}

.v2-row__title {
    font-family: var(--v2-sans);
    font-weight: 500;
    font-size: clamp(1.125rem, 1rem + 0.7vw, 1.5rem);
    letter-spacing: -0.015em;
    transition: color 200ms var(--v2-ease);
}

.v2-row__text {
    color: var(--v2-ink-soft);
    font-size: 0.9375rem;
    max-width: 68ch;
    margin: 0;
}

.v2-section--night .v2-row__text {
    color: var(--v2-ink-mute);
}

a.v2-row:hover .v2-row__title {
    color: var(--v2-accent);
}

/* A hairline that grows from the left on hover, in place of the usual
   background-colour wash. */
.v2-row__cue {
    grid-column: 1 / -1;
    height: 1px;
    background: var(--v2-accent);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 260ms var(--v2-ease);
    margin-bottom: calc(var(--v2-s3) * -1 - 1px);
    margin-top: var(--v2-s3);
}

a.v2-row:hover .v2-row__cue,
a.v2-row:focus-visible .v2-row__cue {
    transform: scaleX(1);
}

@media (min-width: 768px) {
    .v2-row {
        grid-template-columns: 3rem minmax(0, 5fr) minmax(0, 6fr);
        align-items: baseline;
        padding-block: var(--v2-s4);
    }

    /* A row carrying only a title — the case index, which puts its picture in
       the plate beside the list rather than in a third column. */
    .v2-row--two {
        grid-template-columns: 3rem minmax(0, 1fr);
        padding-block: var(--v2-s3);
    }

    .v2-row--two .v2-row__cue {
        margin-bottom: calc(var(--v2-s3) * -1 - 1px);
        margin-top: var(--v2-s3);
    }

    .v2-row__cue {
        grid-column: 1 / -1;
        margin-bottom: calc(var(--v2-s4) * -1 - 1px);
        margin-top: var(--v2-s4);
    }
}

/* --------------------------------------------------------------------------
   Prose block — about
   -------------------------------------------------------------------------- */

.v2-prose {
    grid-column: 1 / -1;
}

.v2-prose p {
    font-size: 1.0625rem;
    color: var(--v2-ink-soft);
}

/* A serif first paragraph, set larger: the oldest trick in editorial layout and
   still the cheapest way to make a wall of text look composed. */
/* The child combinator is load-bearing and was added after the fact.
 *
 * Written as `.v2-prose p:first-child` this matched any paragraph that happened
 * to be the first child of any container inside the prose — thirteen of them on
 * a single imported page, each set at clamp(1.1875rem … 1.5rem) as though it
 * were the standfirst. The About section puts its paragraphs directly inside
 * .v2-prose, so narrowing it costs that section nothing and stops the rule
 * reaching into the arbitrary markup the article and page views now put here. */
.v2-prose > p:first-child {
    /* Left in the reading face, only larger: this is a paragraph doing the job
       of a standfirst, not a heading. */
    font-family: var(--v2-sans);
    font-weight: 450;
    font-size: clamp(1.1875rem, 1.05rem + 0.7vw, 1.5rem);
    line-height: 1.45;
    color: var(--v2-ink);
    max-width: 38ch;
}

.v2-aside {
    grid-column: 1 / -1;
}

@media (min-width: 900px) {
    .v2-prose {
        grid-column: 1 / 7;
    }

    .v2-aside {
        grid-column: 8 / -1;
    }

    .v2-prose > p:first-child {
        max-width: none;
    }
}

/* --------------------------------------------------------------------------
   Case index — hover swaps one large plate, no card grid
   -------------------------------------------------------------------------- */

.v2-cases {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s4);
}

.v2-cases__plate {
    display: none;
    position: relative;
    aspect-ratio: 4 / 3;
    background: var(--v2-paper-sunk);
    overflow: hidden;
}

.v2-cases__plate img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 320ms var(--v2-ease);
}

.v2-cases__plate img.is-shown {
    opacity: 1;
}

@media (min-width: 900px) {
    .v2-cases {
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
        gap: var(--v2-s5);
        align-items: start;
    }

    .v2-cases__plate {
        display: block;
        position: sticky;
        top: 120px;
    }
}

/* --------------------------------------------------------------------------
   Testimonial — one long quote, set as a quote
   -------------------------------------------------------------------------- */

.v2-quote {
    grid-column: 1 / -1;
}

.v2-quote__body {
    font-family: var(--v2-head-font);
    font-weight: 600;
    font-size: clamp(1.375rem, 1.05rem + 1.4vw, 2.125rem);
    line-height: 1.28;
    letter-spacing: -0.01em;
    margin: 0 0 var(--v2-s4);
    max-width: 30ch;
}

/* The short pull quote is a headline and gets the condensed cut. This is nine
   lines of prose wearing the same clothes, and a condensed face over that
   distance stops being emphatic and starts being hard work. */
.v2-quote__body--long {
    font-family: var(--v2-sans);
    font-weight: 400;
    font-size: clamp(1.0625rem, 1rem + 0.5vw, 1.3125rem);
    line-height: 1.5;
    letter-spacing: 0;
    max-width: 56ch;
}

.v2-quote__by {
    display: flex;
    align-items: center;
    gap: var(--v2-s2);
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule-invert);
}

.v2-quote__name {
    font-weight: 500;
}

.v2-quote__role {
    font-size: 0.875rem;
    color: var(--v2-ink-mute);
}

.v2-quote__logo {
    margin-left: auto;
    max-height: 28px;
    width: auto;
    /* The client's logo is evidence, not decoration: knocked back to the page's
       own ink so thirteen different brand colours do not fight the layout.
       Inverted rather than brightened — these are dark marks on a white plate,
       and brightening one turns the plate into a glowing white rectangle with
       the mark washed out of it. Inverting drops the plate into the band and
       leaves the mark light. */
    filter: grayscale(1) invert(1);
    opacity: 0.75;
    /* Inverting turns the logo's white plate pure black, which is darker than
       the band and shows as a rectangle around the mark. Lighten drops every
       pixel darker than the ground, so only the mark itself survives. */
    mix-blend-mode: lighten;
}

@media (min-width: 900px) {
    .v2-quote {
        grid-column: 1 / 9;
    }
}

/* --------------------------------------------------------------------------
   Cards used only where a picture is the content — news, team
   -------------------------------------------------------------------------- */

.v2-plates {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    gap: var(--v2-gap);
}

.v2-plate__frame {
    aspect-ratio: 3 / 2;
    overflow: hidden;
    background: var(--v2-paper-sunk);
    margin-bottom: var(--v2-s2);
}

.v2-plate__frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 500ms var(--v2-ease);
}

.v2-plate:hover .v2-plate__frame img {
    transform: scale(1.03);
}

.v2-plate__title {
    font-size: 1rem;
    font-weight: 500;
    line-height: 1.35;
    transition: color 180ms var(--v2-ease);
}

.v2-plate:hover .v2-plate__title {
    color: var(--v2-accent);
}

/* --------------------------------------------------------------------------
   Team — a portrait beside a sentence, not a row of avatars
   -------------------------------------------------------------------------- */

.v2-team {
    display: grid;
    gap: var(--v2-s4);
}

.v2-member {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s3);
    margin: 0;
    padding-top: var(--v2-s4);
    border-top: 1px solid var(--v2-rule);
}

.v2-member__frame {
    /* Squared off, not a circle: a circle crops a face at the jaw and the
       hairline, which is most of what a portrait is for. */
    max-width: 220px;
    background: var(--v2-paper-sunk);
    overflow: hidden;
}

.v2-member__quote {
    font-family: var(--v2-head-font);
    font-weight: 600;
    font-size: clamp(1.25rem, 1.05rem + 1vw, 1.875rem);
    line-height: 1.25;
    letter-spacing: -0.01em;
    margin-bottom: var(--v2-s3);
    max-width: 38ch;
}

.v2-member__by {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s1) var(--v2-s3);
    padding-top: var(--v2-s2);
    border-top: 1px solid var(--v2-rule);
}

@media (min-width: 768px) {
    .v2-member {
        grid-template-columns: 220px minmax(0, 1fr);
        gap: var(--v2-s5);
        align-items: start;
    }
}

/* --------------------------------------------------------------------------
   Partners and certifications
   -------------------------------------------------------------------------- */

/* Dark, because that is what these files were drawn for.
 *
 * The live theme prints them in a footer whose ground is #111, and it shows in
 * the assets: ISO 9001 and NXP are white marks on transparent, so on the page's
 * paper they disappear entirely. White was tried, then multiply — multiply hides
 * the plates a couple of the others carry but erases the white marks completely.
 * Neither is a fix; the ground the artwork was made for is. */
.v2-partners {
    /* Inside the footer, so it already stands on the ground the artwork wants.
       It only has to separate itself from the link columns below. */
    padding-block: 0 var(--v2-s5);
    margin-bottom: var(--v2-s5);
    border-bottom: 1px solid var(--v2-rule-invert);
    --v2-ink-mute: #98a4b4;
    --v2-rule: var(--v2-rule-invert);
}


/* A grid rather than a wrapping flex row: with space-between, whatever is left
   over on the last line hangs off the left margin on its own, which is exactly
   how the tenth logo landed. Equal cells put every logo on the same footing and
   the last row lines up with the ones above it. */
.v2-partners__label {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    margin-bottom: var(--v2-s4);
}

.v2-partners__row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    align-items: center;
    justify-items: center;
    gap: var(--v2-s4) var(--v2-s3);
}

.v2-partners__logo {
    /* Sized by height and never by width: these ten files run from 152×67 to
       406×312, and matching their widths would print one of them at four times
       the presence of another for no reason but its export settings. */
    height: clamp(38px, 4vw, 52px);
    width: auto;
    /* Knocked back to the page's own ink, and only coloured under the pointer.
       Ten brand palettes at full strength would be the loudest thing here, and
       what this row says is "these names", not "these colours". */
    filter: grayscale(1);
    opacity: 0.55;
    transition: opacity 200ms var(--v2-ease), filter 200ms var(--v2-ease);
}

/* The imported partner carousels, under the same panel switch as the footer's
   own strip.

   The footer partial can test the setting directly; these bands are stored
   HTML inside a page's content column and cannot. content:gate-partners marks
   them, the layout puts .v2-no-partners on <body> when the switch is off, and
   this is the one rule that joins the two. Marked-but-shown is the normal
   state, so nothing here fires while the switch is on. */
.v2-no-partners [data-site-block="partners"],
.v2-no-partners .v2-block-partners {
    display: none !important;
}

.v2-partners__logo:hover {
    filter: none;
    opacity: 1;
}

/* --------------------------------------------------------------------------
   Contact
   -------------------------------------------------------------------------- */

.v2-contact__intro {
    grid-column: 1 / -1;
}

.v2-form {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s3);
    margin-top: var(--v2-s4);
}

@media (min-width: 900px) {
    .v2-contact__intro {
        grid-column: 1 / 7;
    }

    .v2-contact__intro .v2-h2 {
        font-size: clamp(1.75rem, 2.3vw, 2.25rem);
    }

    .v2-form {
        grid-column: 8 / -1;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        margin-top: 0;
    }
}

.v2-field {
    display: flex;
    flex-direction: column;
    gap: var(--v2-s1);
}

.v2-field--wide {
    grid-column: 1 / -1;
}

/* The label is visible and stays visible. A placeholder that doubles as the
   label disappears the moment anyone types, which is when they most need it. */
.v2-field label {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
}

.v2-field input,
.v2-field textarea {
    min-height: 48px;
    padding: var(--v2-s2) 0;
    border: 0;
    border-bottom: 1px solid var(--v2-rule);
    background: transparent;
    color: inherit;
    font: 400 1rem/1.5 var(--v2-sans);
    transition: border-color 180ms var(--v2-ease);
}

.v2-field textarea {
    resize: vertical;
    min-height: 120px;
}

.v2-field input::placeholder,
.v2-field textarea::placeholder {
    color: var(--v2-ink-mute);
    opacity: 0.7;
}

.v2-field input:focus,
.v2-field textarea:focus {
    outline: 0;
    border-bottom-color: var(--v2-accent);
}

/* Chrome paints an autofilled field with its own opaque background and its own
   text colour, and no amount of `background: transparent` touches it — the
   fields turned into solid white slabs on the red band the moment the browser
   filled them in.
 *
 * Two workarounds, because either alone leaves a case uncovered: the inset
 * shadow repaints the field in the ground it is standing on, and the absurd
 * transition delay stops the highlight ever animating in on top of it. The
 * text colour has to be set through -webkit-text-fill-color; `color` is
 * ignored here. */
.v2-field input:-webkit-autofill,
.v2-field input:-webkit-autofill:hover,
.v2-field input:-webkit-autofill:focus,
.v2-field textarea:-webkit-autofill {
    -webkit-text-fill-color: var(--v2-ink);
    -webkit-box-shadow: 0 0 0 1000px var(--v2-paper-sunk) inset;
    caret-color: var(--v2-ink);
    transition: background-color 600000s 0s;
}

/* On a reversed band the ground is the band, not the sunk paper. */
.v2-section--brand .v2-field input:-webkit-autofill,
.v2-section--brand .v2-field input:-webkit-autofill:hover,
.v2-section--brand .v2-field input:-webkit-autofill:focus,
.v2-section--brand .v2-field textarea:-webkit-autofill {
    -webkit-text-fill-color: #fff;
    -webkit-box-shadow: 0 0 0 1000px var(--v2-accent) inset;
    caret-color: #fff;
}

.v2-section--blue .v2-field input:-webkit-autofill,
.v2-section--blue .v2-field input:-webkit-autofill:hover,
.v2-section--blue .v2-field input:-webkit-autofill:focus,
.v2-section--blue .v2-field textarea:-webkit-autofill {
    -webkit-text-fill-color: #fff;
    -webkit-box-shadow: 0 0 0 1000px var(--v2-blue) inset;
    caret-color: #fff;
}

/* The button, and under it the sentence about what pressing it agrees to.
   Beside the button that sentence read as an unrelated caption floating in the
   middle of the form; under it, it is plainly the small print belonging to the
   thing directly above. */
.v2-form__foot {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--v2-s2);
    margin-top: var(--v2-s2);
}

.v2-terms {
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--v2-ink-mute);
    margin: 0;
    max-width: 46ch;
}

.v2-terms a {
    border-bottom: 1px solid var(--v2-rule);
}

.v2-badges {
    display: grid;
    /* Counted rather than fitted.
     *
     * auto-fit with a 220px floor gave five tracks at 1440, and there are six
     * proof points: five across and one alone underneath, which reads as a
     * mistake rather than as a row. A fixed three splits them 3+3 on a desktop
     * and 2+2+2 on a tablet — even rows at every width, which is the whole
     * claim this theme's grid makes. */
    grid-template-columns: 1fr;
    gap: var(--v2-s3);
    margin-top: var(--v2-s5);
    padding-top: var(--v2-s4);
    border-top: 1px solid var(--v2-rule);
    grid-column: 1 / -1;
}

@media (min-width: 560px) {
    .v2-badges {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (min-width: 900px) {
    /* Was a fixed three, which split the original six into 3+3. There are four
       now — the projects count and the trade-show line went with the claims
       they made — and three tracks leave the fourth alone on a second row,
       which is the exact fault the fixed three was chosen to avoid. Four
       across at this width keeps the row even again, and the two-column rule
       below 900px still splits four into 2+2. */
    .v2-badges {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        column-gap: var(--v2-s4);
    }
}
.v2-badge {
    display: flex;
    gap: var(--v2-s2);
    align-items: flex-start;
    font-size: 0.875rem;
    color: var(--v2-ink-soft);
}

.v2-badge img {
    width: 24px;
    flex: none;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.v2-footer {
    background: var(--v2-night);
    color: var(--v2-ink-invert);
    padding-block: var(--v2-s5) var(--v2-s4);
}

.v2-footer a {
    color: var(--v2-ink-mute);
    transition: color 160ms var(--v2-ease);
}

.v2-footer a:hover {
    color: var(--v2-ink-invert);
}

.v2-footer__cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
    gap: var(--v2-s4);
}

.v2-footer__col h3 {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    margin-bottom: var(--v2-s2);
}

.v2-footer__col li {
    margin-bottom: var(--v2-s1);
    font-size: 0.9375rem;
}

.v2-footer__base {
    display: flex;
    flex-wrap: wrap;
    gap: var(--v2-s2) var(--v2-s3);
    align-items: center;
    margin-top: var(--v2-s5);
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule-invert);
    font-size: 0.8125rem;
    color: var(--v2-ink-mute);
}

.v2-footer__base .v2-eyebrow {
    margin-right: auto;
}

/* --------------------------------------------------------------------------
   Reveal on scroll
   --------------------------------------------------------------------------

   Deliberately small: opacity and 12px of travel, once, on the section as a
   whole. Anything more — staggered cards, scaling, sliding from the side — is
   the tell of a page that was decorated rather than designed.

   The default state is visible. If the script never runs, the page reads
   normally instead of staying blank, which is the failure mode that matters. */

.v2-reveal.is-armed {
    opacity: 0;
    transform: translateY(12px);
}

.v2-reveal.is-in {
    opacity: 1;
    transform: none;
    transition: opacity 420ms var(--v2-ease), transform 420ms var(--v2-ease);
}

@media (prefers-reduced-motion: reduce) {
    .v2 *,
    .v2 *::before,
    .v2 *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    .v2-reveal.is-armed {
        opacity: 1;
        transform: none;
    }
}

/* --------------------------------------------------------------------------
   Print — the one place a B2B page still gets read on paper
   -------------------------------------------------------------------------- */

@media print {
    .v2-header,
    .v2-footer,
    .v2-form {
        display: none;
    }

    body.v2 {
        background: #fff;
        color: #000;
    }
}

/* --------------------------------------------------------------------------
   The phone

   Everything below only applies under 768px, and all of it is about the same
   thing: a phone screen is 812 tall and the masthead was taking 182 of it
   before a word of the page had been read. The header was three stacked rows
   because a bar that wraps rather than overflows is right on a desktop and
   wrong here, where there is nothing to wrap onto.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
    /* One line, not two. The email is the row's job — a B2B site should keep
       one way to reach it visible — and the second phone number, the LinkedIn
       link and the spacer are all in the footer already. */
    .v2-utility__bar {
        flex-wrap: nowrap;
        gap: var(--v2-s2);
        min-height: 36px;
        font-size: 0.625rem;
    }

    .v2-utility__item--phone,
    .v2-utility__item[rel='noopener'] {
        display: none;
    }

    .v2-utility__item {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .v2-utility__cta {
        flex: none;
        padding-inline: var(--v2-s2);
    }

    /* The logo, the burger and the globe fit on one 375px line with room to
       spare once the bar stops wrapping. Measured, not guessed: 145 + 160 + the
       gutters is under 335. */
    .v2-header__bar {
        flex-wrap: nowrap;
        min-height: 60px;
    }

    .v2-logo img {
        height: 26px;
    }

    /* Both buttons the same width, side by side. Two buttons of different
       widths stacked on the left is the single thing that most makes a layout
       look unconsidered on a phone, and at this size they are the first
       instruction the page gives. */
    .v2-hero__actions {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--v2-s1);
    }

    .v2 .v2-hero__actions .v2-btn {
        width: 100%;
        padding-inline: var(--v2-s2);
        font-size: 0.875rem;
    }
}

/* The stats grid is two columns on a phone and four above it, and the rule that
   drew the dividers assumed four: `.v2-stat + .v2-stat` gave a left border and
   a left padding to every figure after the first, so the third — which starts
   the second row — was indented past the first. The divider belongs to the
   column, not to the order. */
.v2-stat + .v2-stat {
    border-left: 0;
    padding-left: 0;
}

.v2-stat:nth-child(even) {
    border-left: 1px solid var(--v2-rule);
    padding-left: var(--v2-s3);
}

@media (min-width: 768px) {
    .v2-stat:nth-child(odd) {
        border-left: 1px solid var(--v2-rule);
        padding-left: var(--v2-s3);
    }

    .v2-stat:first-child {
        border-left: 0;
        padding-left: 0;
    }
}

/* Every link in the drawer is a thumb target.

   Measured before this: 27 of the 33 were 36px tall, against a 44px minimum
   that exists because a thumb is about that wide. In a list of 33 entries that
   is not a nicety — it is the difference between opening the page you meant and
   the one under it. The change is min-height rather than padding so the text
   keeps its own rhythm and only the box grows. */
.v2-drawer__link,
.v2-drawer__child {
    display: flex;
    align-items: center;
    min-height: 44px;
}

/* The child list loses its top padding in exchange: the taller rows already
   carry the space, and keeping both opened a gap under every group heading. */
.v2-drawer__group {
    padding-top: 0;
    padding-bottom: var(--v2-s1);
}

/* The drawer's own ceiling followed the old 72px header. The masthead is two
   rows on a phone — the utility line and the bar — so it has to be measured
   rather than assumed, or the last menu item sits under the fold with nothing
   to say it is there. */
.v2-drawer {
    max-height: calc(100dvh - var(--v2-masthead, 97px));
}

/* The narrowest phones still in use are 320px wide, and there the header's own
   words are what overflow: the logo, the word MENU, a globe and a language code
   come to 325. The icons say the same thing — a burger and a globe are the two
   most understood marks on the web — so under 400px the labels go and the marks
   stay. The accessible name is on the button either way, so a screen reader
   still hears "Menu". */
@media (max-width: 399px) {
    .v2-burger__label {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .v2-header__bar {
        gap: var(--v2-s1);
    }

    .v2-logo img {
        height: 24px;
    }
}

/* The case study index on a phone.

   The plate — one large picture beside the list, swapped as the reader moves
   down it — needs a second column, so below 900px it is hidden. That left the
   section as twelve lines of text and nothing to look at, which is what the
   review meant by the shapes not showing: a case study is sold by the thing
   that was built.

   So each row carries its own picture at this size, and gives it up again where
   the plate returns. */
.v2-row__thumb {
    display: block;
    aspect-ratio: 16 / 9;
    margin-bottom: var(--v2-s2);
    overflow: hidden;
    background: rgba(0, 0, 0, 0.12);
}

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

@media (min-width: 900px) {
    .v2-row__thumb {
        display: none;
    }
}

/* The logo wall, legibly.

   0.55 was chosen against the light page these were first placed on. In the
   footer they stand on navy, and knocking white artwork back by nearly half
   turns it into mid-grey on dark blue — five of the ten were barely there on a
   phone, which is where the review saw them. 0.8 still reads as a quiet row
   rather than ten brands shouting.

   And a phone has no pointer, so the hover that restores them fully is a state
   it can never reach: on a coarse pointer they simply sit at full strength. */
.v2-partners__logo {
    opacity: 0.8;
}

@media (hover: none) {
    .v2-partners__logo {
        opacity: 1;
    }
}

/* ==========================================================================
   THE REST OF THE SITE
   ==========================================================================

   Everything above this line dresses one page. Everything below dresses the
   other five kinds of page the site actually has — listings, term pages,
   articles, the 262 imported pages and the 404 — and it is written to the same
   three rules, because the whole point of rolling the theme out is that a
   reader cannot tell which template produced the page they are on.

   Two of those page types are not composed here at all. An article body and an
   imported page body are strings of somebody else's HTML: Drupal 7 field
   wrappers, Bootstrap 3 columns, `<font>` tags, tab panes that need a script
   that is no longer loaded. Nothing in a Blade file can rearrange them, so the
   whole treatment is CSS, and it is the longest thing in this stylesheet for
   that reason. It is scoped to two hooks — `.v2-prose--article` for a body that
   is one column of running text, `.v2-imported` for a body that is a page
   layout of its own — so neither can reach the composed sections above.

   Order matters below and is not alphabetical. Several rules here have exactly
   the same specificity as one earlier in the file and win only by coming after
   it; each of those says so where it stands.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The first section of an inner page
   --------------------------------------------------------------------------

   The home page's masthead is `.v2-hero`, which does two things nothing else
   needed until now: it drops the section's own top hairline, because the sticky
   header already draws one and two of them a pixel apart is a 2px rule that
   reads as a mistake; and it cuts the top padding, because clamp(64px, 8vw,
   128px) is right between two subjects on a long page and absurd between the
   masthead and the bar directly above it — measured at 1440 it put the h1 about
   130px below the header.

   Every inner page needs the same two things and none of them is a hero, so
   they are a modifier of their own. Same numbers as `.v2-hero`, deliberately:
   the gap under the header must not depend on which template is rendering. */

.v2-section--lead {
    border-top: 0;
    /* Only the top. The bottom padding is the section's own, because what is
       below a masthead is the rest of the page and that spacing was already
       right. */
    padding-top: clamp(40px, 3.5vw, 72px);
}

/* The same doubled hairline, from the other two directions.
 *
 * An article opens with its own masthead section and an imported page opens
 * with the register bar; neither can carry `--lead`, the first because it needs
 * its own bottom padding of zero and the second because it is one <article>
 * element doing the job of a section. `:has()` is the honest way to say "the
 * section that opens an imported page" — the alternative is a `:first-child`
 * that silently stops working the day something is put in front of it. */
.v2-article > .v2-section:first-child,
.v2-section:has(> .v2-imported-head) {
    border-top: 0;
}

/* --------------------------------------------------------------------------
   The crumb line
   --------------------------------------------------------------------------

   The clone printed a Bootstrap breadcrumb: a grey pill with slashes rendered
   by ::before pseudo-elements. This is the same three links set as the theme
   sets every other piece of context — small tracked capitals sitting on a
   hairline, which is exactly the `.v2-head` gesture with the rule underneath
   instead of beside.

   Only the two ancestors are capitalised. The last crumb is the article's own
   title and runs to ninety characters on the imported corpus; ninety characters
   of upper case is a wall, not a label. It wraps to three lines at 375px and
   that is the right answer — truncating a title with an ellipsis hides the one
   word that told the reader where they are. */

.v2-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s1) var(--v2-s2);
    padding-bottom: var(--v2-s2);
    margin-bottom: var(--v2-s5);
    border-bottom: 1px solid var(--v2-rule);
    font-family: var(--v2-sans);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
}

.v2-crumbs a {
    text-transform: uppercase;
    /* Declared transparent rather than absent, so the hairline appears by
       changing colour instead of by appearing — otherwise the line of type
       shifts a pixel the moment a pointer touches it. */
    border-bottom: 1px solid transparent;
    padding-bottom: 1px;
    transition: color 180ms var(--v2-ease), border-color 180ms var(--v2-ease);
}

.v2-crumbs a:hover,
.v2-crumbs a:focus-visible {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
}

/* Set in the rule colour rather than the ink: it is a divider that happens to
   be drawn with a character, and at full strength three of them are louder than
   the words they separate. */
.v2-crumbs__sep {
    color: var(--v2-rule);
}

.v2-crumbs__here {
    text-transform: none;
    letter-spacing: 0.02em;
    color: var(--v2-ink-soft);
}

/* --------------------------------------------------------------------------
   Article — a case study or a post
   --------------------------------------------------------------------------

   The masthead is the hero's geometry at a smaller scale: the headline in the
   first seven columns, the picture in the last five and cropped by the window
   rather than sitting inside a box. The two are not merged into one rule
   because they differ in the two places that matter — the article's text block
   is anchored to the top rather than centred against the picture (a headline
   and a rule read from the top down; a headline, a standfirst and two buttons
   balance against a photograph), and the picture is capped 40px shorter,
   because the hero's 380px cap was chosen against a wide bench shot and these
   are whatever the crawl saved. */

.v2-article__masthead {
    /* Flush at the bottom, so the picture lands directly on the body section's
       top rule instead of floating above it. Same shape as `.v2-hero`. */
    padding-block: clamp(40px, 3.5vw, 72px) 0;
}

.v2-article__text {
    grid-column: 1 / -1;
}

/* Carries the `.v2` for the same reason `.v2 .v2-hero__figure` does: the
   `figure { margin: 0 }` reset is element-scoped, and a bare class loses to it
   on margins. That is what silently killed the hero's bleed once already —
   grid-column kept applying, margin-right did not, and the photograph merely
   looked a little small. */
.v2 .v2-article__figure {
    grid-column: 1 / -1;
    margin-top: var(--v2-s5);
}

.v2-article__figure img {
    width: 100%;
    /* Cropped rather than shrunk, and capped: without a ceiling the picture's
       height tracks the column width, so the masthead grows taller the wider
       the screen gets and drags the headline away from the body. */
    max-height: 420px;
    object-fit: cover;
    object-position: center;
}

@media (min-width: 900px) {
    .v2-article__text {
        grid-column: 1 / 8;
        /* Top, not centre. There is a rule and one heading here, nothing to
           balance against the picture, and centring leaves the eyebrow floating
           in the middle of the column. */
        align-self: start;
    }

    .v2 .v2-article__figure {
        grid-column: 8 / -1;
        margin-top: 0;
        align-self: start;
        /* Past the right gutter. body.v2 already carries overflow-x: hidden, so
           this crops rather than scrolls. */
        margin-right: var(--v2-gutter-neg);
    }
}

/* What the piece is filed under, and the way back to the listing. Both are grid
   items of the body section's `.v2-grid`, so both have to name their columns or
   they auto-place into whatever the prose left over. */

.v2-article__terms {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s2) var(--v2-s3);
    margin-top: var(--v2-s6);
    padding-top: var(--v2-s3);
    /* The strong rule, used once per page: this is what closes the article.
       The terms are its colophon, not a tray of chips — they are the same
       underlined links the rest of the site uses and they need no box. */
    border-top: 1px solid var(--v2-rule-strong);
}

.v2-article__back {
    grid-column: 1 / -1;
    margin-top: var(--v2-s4);
}

/* --------------------------------------------------------------------------
   Imported article bodies — the measure
   --------------------------------------------------------------------------

   `.v2-prose--article` is the whole treatment for a body this stylesheet did
   not write. 277 news posts and 223 case studies, the first printed raw as the
   clone left it and the second run through Str::markdown(); between them they
   contain 16,000 paragraphs, 2,700 headings, 20,000 links, 228 tables and 4,700
   images, and none of it can be touched from a Blade file because each body is
   a single string.

   Every count quoted in the comments below was taken from that corpus rather
   than guessed, which is the only reason it is worth styling `<font>` and not
   worth loading a monospace face for ten inline `<code>` elements.

   The two overrides at the top of this block exist only because of where they
   sit in the file: `.v2-prose { grid-column: 1 / 7 }` and `.v2-prose >
   p:first-child` are declared several hundred lines above at the same
   specificity, so these win by coming after them and by nothing else. Moving
   this block up the file would silently undo both. */

.v2-prose--article {
    grid-column: 1 / -1;
    /* About 70 characters of Nunito Sans at 17px. The container holds the
       measure so the paragraphs themselves do not have to, which is what lets a
       table or a wide image inside the same column reach past it. */
    max-width: 72ch;
    /* The imported bodies contain bare URLs several hundred characters long.
       Nothing else in this file breaks a word, and nothing else has to. */
    overflow-wrap: break-word;
}

@media (min-width: 900px) {
    .v2-prose--article {
        grid-column: 1 / 10;
    }
}

.v2-prose--article > :first-child {
    margin-top: 0;
}

.v2-prose--article > :last-child {
    margin-bottom: 0;
}

/* The About section's standfirst, cancelled here.
 *
 * Setting the first paragraph larger is an editorial decision about a paragraph
 * somebody wrote to be a standfirst. The first paragraph of an imported body is
 * whatever the crawl found — a legal notice, half a sentence, an image caption
 * — so the treatment has to come off, and it has to come off at a specificity
 * that beats `.v2-prose > p:first-child`.
 *
 * The child combinator here is not decoration either. Written as a descendant
 * it matched the first paragraph inside every blockquote, table cell and list
 * item in the body — and at two classes plus an element it outweighed
 * `.v2-prose--article blockquote p`, so every case study's opening quotation
 * rendered at body size. Caught in the browser rather than in review, which is
 * what a `:first-child` written without a combinator is always worth. */
.v2-prose--article > p:first-child {
    font-family: var(--v2-sans);
    font-weight: 400;
    font-size: 1.0625rem;
    line-height: 1.7;
    color: var(--v2-ink-soft);
    max-width: none;
}

/* --------------------------------------------------------------------------
   Imported article bodies — running text
   -------------------------------------------------------------------------- */

.v2-prose--article p {
    font-size: 1.0625rem;
    line-height: 1.7;
    color: var(--v2-ink-soft);
    margin: 0 0 var(--v2-s3);
    /* The container holds the measure. Leaving the reset's 78ch here as well
       means two limits fighting, and the narrower one wins in a column that was
       already narrower than either. */
    max-width: none;
}

/* 294 of the news bodies open with their own <h1>, because they are whole pages
   rather than article fragments. It sits inside a text column rather than
   across the masthead, so it is set a step under `.v2-display`. */
.v2-prose--article h1 {
    font-family: var(--v2-head-font);
    font-weight: 700;
    font-size: clamp(1.75rem, 1.1rem + 2vw, 2.75rem);
    line-height: 1.12;
    letter-spacing: -0.02em;
    color: var(--v2-ink);
    margin: 0 0 var(--v2-s3);
}

/* The hairline over every h2 is the one gesture that turns three thousand words
   into a document with an index. It is the same rule `.v2-head` draws under a
   section label, doing the same job one level down — and it is why h3 does not
   get one: two ruled levels is a ladder, and the reader stops being able to
   tell which line means "new subject". 1,859 h2s in the news corpus and 886 in
   the case studies, so this is most of what a long body looks like. */
.v2-prose--article h2 {
    font-family: var(--v2-head-font);
    font-weight: 700;
    font-size: clamp(1.375rem, 1.15rem + 0.9vw, 1.875rem);
    line-height: 1.2;
    letter-spacing: -0.015em;
    color: var(--v2-ink);
    margin: var(--v2-s5) 0 var(--v2-s2);
    padding-top: var(--v2-s2);
    border-top: 1px solid var(--v2-rule);
}

/* A body that opens on a heading would otherwise draw its rule directly under
   the section hairline above it. */
.v2-prose--article h2:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

.v2-prose--article h3 {
    font-family: var(--v2-head-font);
    font-weight: 600;
    font-size: clamp(1.125rem, 1.05rem + 0.35vw, 1.3125rem);
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--v2-ink);
    margin: var(--v2-s4) 0 var(--v2-s1);
}

/* 38 of them in the whole corpus. Dropped into the reading face rather than
   given a fourth display size — at this depth the distinction is "a labelled
   paragraph", and that is what bold sans at body size says. */
.v2-prose--article h4,
.v2-prose--article h5,
.v2-prose--article h6 {
    font-family: var(--v2-sans);
    font-weight: 700;
    font-size: 1rem;
    line-height: 1.4;
    color: var(--v2-ink);
    margin: var(--v2-s3) 0 var(--v2-s1);
}

/* The case studies are written `### **1. System Overview**` — a Markdown
   heading whose entire text is also bold. Without this, every third heading in
   the corpus renders a weight heavier than its neighbours and the hierarchy
   stops meaning anything. */
.v2-prose--article h1 strong,
.v2-prose--article h2 strong,
.v2-prose--article h3 strong,
.v2-prose--article h4 strong,
.v2-prose--article h5 strong,
.v2-prose--article h6 strong {
    font-weight: inherit;
}

/* 20,556 links. In a column of --v2-ink-soft a link has to be findable without
   being read as emphasis, so this is the one place in the theme where the
   accent is allowed to repeat down a page: it is the only thing that makes the
   difference between a sentence and a reference. The hairline is drawn in the
   rule colour and only becomes accent under the pointer, so a paragraph with
   five links in it is not five red underlines. */
.v2-prose--article a {
    color: var(--v2-accent);
    border-bottom: 1px solid var(--v2-rule);
    padding-bottom: 1px;
    transition: color 180ms var(--v2-ease), border-color 180ms var(--v2-ease);
}

.v2-prose--article a:hover,
.v2-prose--article a:focus-visible {
    color: var(--v2-accent-deep);
    border-bottom-color: var(--v2-accent);
}

/* Emphasis is a step darker than the running text. That is how emphasis works
   in a theme with one accent: the bold word cannot also be red, or the page has
   4,586 accents on it. */
.v2-prose--article strong,
.v2-prose--article b {
    font-weight: 700;
    color: var(--v2-ink);
}

.v2-prose--article em,
.v2-prose--article i {
    font-style: italic;
}

.v2-prose--article u {
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}

.v2-prose--article sup,
.v2-prose--article sub {
    font-size: 0.7em;
    /* Zero, so a footnote marker cannot open the line it sits in. */
    line-height: 0;
}

.v2-prose--article small {
    font-size: 0.875rem;
    color: var(--v2-ink-mute);
}

/* 31 `<font>` elements, each carrying a colour attribute from whatever editor
   typed them in 2014. A presentational attribute has zero specificity, so
   naming the element is enough to beat it without !important — which is what
   keeps the one-accent rule true on pages nobody has looked at since the
   crawl. */
.v2-prose--article font {
    color: inherit;
    font-size: inherit;
    font-family: inherit;
}

/* --------------------------------------------------------------------------
   Imported article bodies — lists
   --------------------------------------------------------------------------

   The reset at the top of this file strips the markers off every `ul` in the
   theme. Fighting it back — `list-style: revert` — would put a filled circle
   into a page that has no round shapes anywhere else, so the marker is replaced
   rather than restored: a 10px hairline dash in the accent, which is the same
   mark the rest of the page draws when it wants to say "item". */

.v2-prose--article ul {
    list-style: none;
    padding-left: 0;
    margin: 0 0 var(--v2-s3);
}

.v2-prose--article ul > li {
    position: relative;
    padding-left: var(--v2-s3);
    margin-bottom: var(--v2-s1);
}

.v2-prose--article ul > li::before {
    content: "";
    position: absolute;
    left: 0;
    /* Aligned to the middle of the first line rather than to its top, so the
       dash sits against the x-height of the word beside it. */
    top: 0.75em;
    width: 10px;
    height: 1px;
    background: var(--v2-accent);
}

/* The ordered list is the ruled index, reused where it belongs: 01, 02, 03 in
   small tracked figures, exactly as `.v2-row__index` sets them. A counter
   rather than the browser's own numbering, because `list-style:
   decimal-leading-zero` cannot be typed. */
.v2-prose--article ol {
    list-style: none;
    padding-left: 0;
    margin: 0 0 var(--v2-s3);
    counter-reset: v2-ol;
}

.v2-prose--article ol > li {
    position: relative;
    counter-increment: v2-ol;
    padding-left: var(--v2-s4);
    margin-bottom: var(--v2-s1);
}

.v2-prose--article ol > li::before {
    content: counter(v2-ol, decimal-leading-zero);
    position: absolute;
    left: 0;
    top: 0;
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    /* Sized so the small figure sits on the same baseline as the first line of
       the item beside it. */
    line-height: 2.1;
    color: var(--v2-ink-mute);
    font-variant-numeric: tabular-nums;
}

.v2-prose--article li ul,
.v2-prose--article li ol {
    margin: var(--v2-s1) 0 0;
}

.v2-prose--article li p {
    margin-bottom: var(--v2-s1);
}

/* --------------------------------------------------------------------------
   Imported article bodies — quotation
   --------------------------------------------------------------------------

   Every case study opens `> **Project in Nutshell:** …`. That paragraph is the
   piece's standfirst and should look like one, which is why the quote is set
   large and dark rather than indented and grey. Two hairlines, no left bar and
   no ground: a left bar is a decoration that says "quote" a second time after
   the type has already said it. */

.v2-prose--article blockquote {
    margin: var(--v2-s4) 0;
    padding: var(--v2-s3) 0;
    border-top: 1px solid var(--v2-rule-strong);
    border-bottom: 1px solid var(--v2-rule);
}

.v2-prose--article blockquote p {
    font-size: clamp(1.1875rem, 1.05rem + 0.7vw, 1.5rem);
    line-height: 1.45;
    font-weight: 450;
    color: var(--v2-ink);
    max-width: 46ch;
    margin-bottom: var(--v2-s2);
}

.v2-prose--article blockquote > :last-child {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   Imported article bodies — tables
   --------------------------------------------------------------------------

   228 tables, several of them fifteen columns wide, and no wrapper can be added
   around any of them because the body is one string. `display: block` is the
   whole fix: it makes the table its own scroll container, so a wide table
   scrolls inside itself instead of taking the page sideways with it. The rows
   keep aligning because the browser rebuilds an anonymous table box inside.

   `width: max-content` clamped to 100% is what stops a three-column table being
   stretched across the measure to fill a container it does not need. */

.v2-prose--article table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
    margin: var(--v2-s4) 0;
    font-size: 0.9375rem;
}

.v2-prose--article th {
    text-align: left;
    font-family: var(--v2-head-font);
    font-weight: 600;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
    padding: var(--v2-s1) var(--v2-s2) var(--v2-s1) 0;
    border-bottom: 1px solid var(--v2-rule-strong);
    /* A header cell that wraps to three words is what makes a data table
       unreadable; the table scrolls now, so it can afford the width. */
    white-space: nowrap;
}

.v2-prose--article td {
    padding: var(--v2-s1) var(--v2-s2) var(--v2-s1) 0;
    border-bottom: 1px solid var(--v2-rule);
    color: var(--v2-ink-soft);
    vertical-align: top;
}

.v2-prose--article td p {
    margin: 0;
}

/* The last rule of a table is the table's own bottom edge and does not need
   drawing twice. */
.v2-prose--article tr:last-child td {
    border-bottom: 0;
}

/* Several case bodies ship a <colgroup> of fixed pixel widths measured against
   the clone's 1170px column. Left alone they force the scroll container open on
   every screen. */
.v2-prose--article col,
.v2-prose--article colgroup {
    width: auto;
}

/* --------------------------------------------------------------------------
   Imported article bodies — pictures and embeds
   -------------------------------------------------------------------------- */

/* Never `width: 100%`. A third of the 4,700 images in these bodies are inline
   logos and icons 40px across, and stretching one of those to the measure is
   the loudest possible way to say nobody read the page. */
.v2-prose--article img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: var(--v2-s4) 0;
}

.v2-prose--article figure {
    margin: var(--v2-s4) 0;
}

/* The hero caption's treatment, because it is the same thing: small tracked
   capitals under a hairline saying what the picture above is. */
.v2-prose--article figcaption {
    margin-top: var(--v2-s1);
    padding-top: var(--v2-s1);
    border-top: 1px solid var(--v2-rule);
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
}

.v2-prose--article svg {
    max-width: 100%;
    height: auto;
}

/* The imported width/height attributes on these are presentational and lose to
   a stylesheet, which is the only reason a 16:9 box can be imposed on them. */
.v2-prose--article iframe,
.v2-prose--article video,
.v2-prose--article embed,
.v2-prose--article object {
    display: block;
    width: 100%;
    max-width: 100%;
    aspect-ratio: 16 / 9;
    height: auto;
    border: 0;
    margin: var(--v2-s4) 0;
}

.v2-prose--article hr {
    border: 0;
    height: 0;
    border-top: 1px solid var(--v2-rule);
    margin: var(--v2-s5) 0;
}

/* Ten inline `<code>` elements and one `<pre>` in the entire corpus.
 *
 * Deliberately NOT monospace. A third face was removed from this theme because
 * it was the single loudest tell that the page came out of a generator, and
 * eleven elements is not a reason to load it again. What says "this is a
 * literal" here is the change of paper and the weight, which is what the rest
 * of the file uses for the same job. */
.v2-prose--article code {
    font-family: var(--v2-sans);
    font-weight: 600;
    font-size: 0.9375em;
    background: var(--v2-paper-sunk);
    padding: 0.1em 0.3em;
    border-radius: 0;
}

.v2-prose--article pre {
    background: var(--v2-paper-sunk);
    padding: var(--v2-s3);
    margin: var(--v2-s4) 0;
    overflow-x: auto;
    font-family: var(--v2-sans);
    font-size: 0.9375rem;
    line-height: 1.6;
    /* Wrapped rather than scrolled where it can be: without a monospace face
       there are no columns to preserve. */
    white-space: pre-wrap;
    word-break: break-word;
}

.v2-prose--article pre code {
    background: none;
    padding: 0;
    font-weight: 400;
}

.v2-prose--article details {
    border-top: 1px solid var(--v2-rule);
    padding: var(--v2-s2) 0;
}

.v2-prose--article summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--v2-ink);
}

/* --------------------------------------------------------------------------
   Imported article bodies — the clone's own classes
   --------------------------------------------------------------------------

   These matter only because this document does not load the clone's stylesheet.
   Every one of them is scoped under `.v2-prose--article` so nothing here can
   reach a composed section. */

/* 277 RDF shims, one per news body: a <span property="dc:title"> holding the
   title again. Drupal hid it; nothing here does, so it prints the headline a
   second time directly under the headline. */
.v2-prose--article .element-hidden,
.v2-prose--article .element-invisible {
    display: none;
}

/* The crumb bar 274 of the imported posts carry inside their own body.
 *
 * This is the most important rule in the block. Those posts take the
 * bodyCarriesOwnHeader() branch, so the view prints no `.v2-crumbs` of its own
 * and this is the only crumb line they have — which means it has to come out
 * looking exactly like the one the other branch draws, or a reader can tell
 * which of the two paths produced the page. Same type, same hairline, same
 * spacing; the date lands at the far end of the rule, which is where the themed
 * masthead puts it too. */
.v2-prose--article .mod-breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s1) var(--v2-s2);
    padding-bottom: var(--v2-s2);
    margin-bottom: var(--v2-s5);
    border-bottom: 1px solid var(--v2-rule);
    font-family: var(--v2-sans);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
}

.v2-prose--article .mod-breadcrumbs .breadcrumb {
    margin: 0;
}

.v2-prose--article .mod-breadcrumbs a {
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    border-bottom-color: transparent;
}

.v2-prose--article .mod-breadcrumbs a:hover {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
}

.v2-prose--article .breadcrumb.news-date {
    margin-left: auto;
    color: var(--v2-ink-mute);
}

.v2-prose--article .content-title-outer {
    margin-bottom: var(--v2-s5);
}

.v2-prose--article .content-title {
    margin: 0;
}

/* Bootstrap's grid is not loaded, so these are already plain blocks — but
   several carry inline widths and paddings measured against a 1170px column
   from the crawl, and inside a 72ch measure those are wrong in both directions.
   An article body is one column of running text; there is no layout here to
   preserve. */
.v2-prose--article .container-1170,
.v2-prose--article .container-fluid,
.v2-prose--article .container,
.v2-prose--article .row,
.v2-prose--article [class^="col-"],
.v2-prose--article [class*=" col-"] {
    width: auto;
    max-width: none;
    margin: 0;
    padding: 0;
    float: none;
}

/* 1,053 centred paragraphs, un-centred.
 *
 * This is a deliberate override of the author's own class and it is worth
 * saying why: centred body copy is the single loudest generated-page tell, this
 * theme centres nothing anywhere, and the class was applied in a WYSIWYG to
 * paragraphs that sat in a narrow Bootstrap column where centring read as
 * layout rather than as emphasis. In a 72ch reading measure it reads as neither.
 *
 * Note that the imported-PAGE layer below does the opposite and keeps the
 * centring. That is not an inconsistency: a page body is a composed marketing
 * layout where a centred band is part of the composition, and an article body
 * is prose. */
.v2-prose--article .text-center,
.v2-prose--article .rtecenter {
    text-align: left;
}

/* The picture keeps its own width. `img-responsive` is Bootstrap's
   `max-width: 100%`, which the img rule above already does — what must not be
   added is `width: 100%`. */
.v2-prose--article .img-responsive {
    max-width: 100%;
    height: auto;
}

/* 250 in-body calls to action. Given the theme's solid button, and written at
   two classes so it beats the prose `a` rule above rather than inheriting its
   underline — a button with a hairline under its label is neither. */
.v2-prose--article .btn-blue {
    display: inline-flex;
    align-items: center;
    min-height: 48px;
    padding: 0 var(--v2-s3);
    background: var(--v2-accent);
    border: 1px solid var(--v2-accent);
    border-bottom-color: var(--v2-accent);
    color: #fff;
    font: 600 0.9375rem/1 var(--v2-sans);
    margin: var(--v2-s2) 0;
}

.v2-prose--article .btn-blue:hover {
    background: var(--v2-accent-deep);
    border-color: var(--v2-accent-deep);
    color: #fff;
}

/* 221 bodies embed a Drupal view listing — a block of teasers the clone
   rendered as a grid. With no grid to render it into they are a stack of
   unstyled divs, so they are made into the thing this theme would have built
   anyway: a ruled index. */
.v2-prose--article .view-content .views-row {
    padding: var(--v2-s2) 0;
    border-top: 1px solid var(--v2-rule);
}

.v2-prose--article .views-field {
    margin: 0;
}

.v2-prose--article .views-field-title {
    font-family: var(--v2-head-font);
    font-weight: 600;
}

/* 30 forms with 361 fields between them, mostly a name, an email and a message.
   Same underline-only fields as `.v2-field`, because a reader who fills one in
   on an imported page and one in the contact section should not be able to tell
   they were built five years apart. */
.v2-prose--article input,
.v2-prose--article select,
.v2-prose--article textarea {
    font: inherit;
    width: 100%;
    max-width: 38ch;
    padding: var(--v2-s1) 0;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--v2-rule);
    border-radius: 0;
    color: inherit;
}

.v2-prose--article input:focus,
.v2-prose--article select:focus,
.v2-prose--article textarea:focus {
    border-bottom-color: var(--v2-accent);
    outline: none;
}

.v2-prose--article label,
.v2-prose--article .control-label {
    display: block;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
    margin-bottom: var(--v2-s1);
}

.v2-prose--article .form-item,
.v2-prose--article .form-group {
    margin-bottom: var(--v2-s3);
}

.v2-prose--article button,
.v2-prose--article input[type="submit"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: auto;
    min-height: 48px;
    padding: 0 var(--v2-s3);
    background: var(--v2-accent);
    border: 1px solid var(--v2-accent);
    color: #fff;
    font: 600 0.9375rem/1 var(--v2-sans);
    cursor: pointer;
}

.v2-prose--article button:hover,
.v2-prose--article input[type="submit"]:hover {
    background: var(--v2-accent-deep);
    border-color: var(--v2-accent-deep);
}

/* --------------------------------------------------------------------------
   The taxonomy strip
   --------------------------------------------------------------------------

   What the clone rendered as Bootstrap tabs — a caption strip, a script, and
   one hidden panel per taxonomy, plus a first panel repeating every term of
   every taxonomy again. Nothing there filtered anything; the tabs only decided
   which list of links was on screen.

   So the tabs are gone and the strip is the same ruled row the rest of the
   theme is made of: the taxonomy's name in the narrow well, its terms on one
   line beside it. Above 768px it takes exactly `.v2-row`'s template, so the
   labels stand in the same column as the numbers in the index below and the two
   blocks read as one table rather than as a control panel bolted to a list. */

.v2-filter {
    /* The top margin arrives with `.v2-list--spaced`, which the element already
       carries. Only the hairline is this rule's job. */
    border-top: 1px solid var(--v2-rule);
}

.v2-filter__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s1) var(--v2-gap);
    padding-block: var(--v2-s3);
    border-bottom: 1px solid var(--v2-rule);
}

.v2-filter__set {
    display: flex;
    flex-wrap: wrap;
    /* Tighter than a line of prose would want, because each term now carries
       its own outline and the outlines do the separating. */
    gap: var(--v2-s1);
}

/* Terms as chips.

   They were plain text with a hairline that appeared under the pointer, which
   is right for a link inside a sentence and wrong here: this strip is the only
   control on the page, and sixty words set like body copy do not say "choose
   one" — they say "read me". A reader who does not know they are options never
   discovers the filter at all, which is what the review meant by the strip
   looking like nothing.

   An outline is the smallest thing that says "this is a target". No radius and
   no shadow: the border is the whole device, and it stays in the language the
   rest of the page is written in. */
.v2-filter__link {
    display: inline-flex;
    align-items: center;
    min-height: 34px;
    padding: 0 var(--v2-s2);
    border: 1px solid var(--v2-rule);
    font-size: 0.875rem;
    color: var(--v2-ink-soft);
    white-space: nowrap;
    transition: color 160ms var(--v2-ease), border-color 160ms var(--v2-ease),
        background-color 160ms var(--v2-ease);
}

.v2-filter__link:hover,
.v2-filter__link:focus-visible {
    border-color: var(--v2-accent);
    color: var(--v2-accent);
    /* A wash rather than a fill: the filled state belongs to the term actually
       being looked at, and spending it on hover would leave nothing to say
       "you are here" with. */
    background: color-mix(in srgb, var(--v2-accent) 7%, transparent);
}

/* The term whose page is open. Marked with aria-current in the markup, so the
   styling and the announcement are the same fact rather than two. */
.v2-filter__link[aria-current] {
    background: var(--v2-accent);
    border-color: var(--v2-accent);
    color: #fff;
}

/* A thumb is 44px wide whatever the design language is, and this strip is the
   one place on the listing with sixty targets side by side. */
@media (max-width: 767px) {
    .v2-filter__link {
        min-height: 44px;
    }
}

@media (min-width: 768px) {
    .v2-filter__row {
        /* The number gutter, then everything else.
           
           It was `.v2-row`'s full three-track template, on the reasoning that
           the strip and the index should read as one table. Measured, they did
           not: the label took the whole 5fr title column to hold one word — 480
           empty pixels — and the chips started at 630, which lines up with
           nothing below. Now the gutter still matches, the label and the chips
           both begin where the case study titles begin, and the void is gone. */
        grid-template-columns: 3rem minmax(0, 1fr);
        align-items: baseline;
    }

    /* Both in the second track, so they stack: the group's name over its
       options, each starting on the same line as the titles underneath. */
    .v2-filter__label,
    .v2-filter__set {
        grid-column: 2;
    }
}

/* --------------------------------------------------------------------------
   The dated index — news and blog
   --------------------------------------------------------------------------

   The same ruled row as everywhere else with a date where the number goes. Two
   things follow from that and both are measured rather than chosen.

   First, the lead track is 8rem and not the usual 3rem. A number is two
   characters; "13 March 2026" is thirteen, and at 3rem it wraps to three lines
   under a headline that is one. German runs longest of the six — "18 September
   2026" — and 8rem holds it.

   Second, the intro above the rows must take the same track, or the h1 stops
   standing over the headlines it names and the page has two grids in it again.
   That is the whole reason `.v2-news` exists; it has no appearance of its own.

   THE SECOND POINT NO LONGER APPLIES TO THE LISTING. /news and /blog are a card
   grid now, and a card starts at the shell's own edge — there is no date well
   in front of it to line up with. The 8rem indent was inherited from the ruled
   version and left the h1 156px right of the first card, standing over nothing.
   So the intro on those two pages drops the lead track entirely and the heading
   starts where the grid starts.

   `.v2-row--dated` keeps the 8rem track: the row is still used, and still
   correct, wherever a dated list is rendered. The two are separated here rather
   than deleted together, because the reason each one has the number it has is
   different. */

@media (min-width: 768px) {
    .v2-row--dated {
        grid-template-columns: 8rem minmax(0, 5fr) minmax(0, 6fr);
    }

    .v2-news .v2-intro {
        grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    }

    .v2-news .v2-intro__title {
        grid-column: 1;
    }

    .v2-news .v2-intro__text {
        grid-column: 2;
    }
}

/* The same voice as `.v2-row__index`, because it is doing the same job: the
   fact in the left well that says which item this is. Tabular figures so the
   column of dates lines up down the page. */
.v2-row__date {
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    color: var(--v2-ink-mute);
    font-variant-numeric: tabular-nums;
}

/* Carried on the reversed grounds with the number it stands in for. The token
   is already redefined inside those bands, so this only matters the day one of
   them sets an explicit colour on `.v2-row__index` and forgets the date. */
.v2-section--brand .v2-row__date,
.v2-section--blue .v2-row__date,
.v2-section--night .v2-row__date {
    color: var(--v2-ink-mute);
}

@media (min-width: 768px) {
    .v2-row__date {
        /* The track was measured to hold the longest month name on one line;
           without this a narrower window re-wraps it anyway. */
        white-space: nowrap;
    }
}

/* A post created in the panel and not yet dated prints an empty cell, so the
   three columns stay in register down the page. On one column there is nothing
   to keep in register and the empty cell is only a gap. */
@media (max-width: 767px) {
    .v2-row__date:empty {
        display: none;
    }
}

/* --------------------------------------------------------------------------
   Two repairs to the shared row
   --------------------------------------------------------------------------

   Both are latent bugs in the rules several hundred lines above, and both are
   only visible in the band between 768px and 900px — which is why they survived
   the home page. `.v2-row__thumb` is hidden at 900 and up and the row is one
   column below 768, so there is a 132px window where a three-column grid has an
   extra item in it that nobody placed.

   They are declared here, at the end of the file, purely so they come after the
   `@media (min-width: 768px)` block that sets `.v2-row--two`. Same specificity;
   source order is the whole reason they win. */

@media (min-width: 768px) {
    /* Full width, above everything else in the row. Left to auto-place, the
       picture lands in the 3rem number gutter and renders 27px wide — and it
       pushes the number, the title and the sentence one column right behind it. */
    .v2-row--two .v2-row__thumb,
    .v2-row--dated .v2-row__thumb {
        grid-column: 1 / -1;
    }

    /* `.v2-row--two` is a two-track grid, so the teaser auto-places into the
       number column and sets one word per line. It belongs under the title,
       which is track 2. This is the worst-looking defect on the case listing as
       it stands, and it is fixed for the home page's own case index at the same
       time — the markup there is identical. */
    .v2-row--two .v2-row__text {
        grid-column: 2 / -1;
    }
}

/* --------------------------------------------------------------------------
   The paginator
   --------------------------------------------------------------------------

   The one it replaces was the clone's Bootstrap pager: rounded pills with a
   filled box on the current page. Rounded, filled and boxed are the three
   things this theme does not do, so it is rebuilt out of what the rest of the
   page is made of — hairlines, one accent, and type.

   Left-ranged rather than centred, and with no rule above it. The last row of
   the index already draws a border-bottom; a border-top here would land a pixel
   under it and read as a 2px line somebody meant to be a divider. */

.v2-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s2) var(--v2-s3);
    margin-top: var(--v2-s5);
}

/* The case listing wraps the paginator partial in a `.v2-pager` of its own, so
   on that page there are two of them nested. Harmless except for the margin,
   which would otherwise be applied twice and open 128px under the index. */
.v2-pager .v2-pager {
    margin-top: 0;
}

/* Previous and Next, in the `.v2-more` voice with the `.v2-link` underline. */
.v2-pager__step {
    font-family: var(--v2-sans);
    font-size: 0.8125rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    border-bottom: 1px solid var(--v2-rule);
    padding-bottom: 2px;
    transition: color 180ms var(--v2-ease), border-color 180ms var(--v2-ease);
}

a.v2-pager__step:hover,
a.v2-pager__step:focus-visible {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
}

/* Rendered as a <span aria-disabled="true"> on the first and last page. It must
   not read as clickable at all, so the underline comes off rather than being
   greyed: an underlined word in a row of links is a link, whatever colour it
   is. There is no hover state on purpose. */
.v2-pager__step.is-disabled {
    color: var(--v2-ink-mute);
    border-bottom-color: transparent;
    cursor: default;
}

/* Wrapping is what keeps seventeen page numbers inside 375px. */
.v2-pager__pages {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s2);
}

.v2-pager__page {
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
    /* Wide enough for two figures, so the run of numbers is evenly spaced
       rather than bunching around the single digits. */
    min-width: 1.5rem;
    text-align: center;
    padding-bottom: 2px;
    border-bottom: 1px solid transparent;
    transition: color 180ms var(--v2-ease), border-color 180ms var(--v2-ease);
}

a.v2-pager__page:hover,
a.v2-pager__page:focus-visible {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
}

/* The page the reader is on: the one accented thing in the component, and the
   only place in it that gets a permanent rule. No pill, no fill, no radius —
   the theme's rule-under-a-live-item, held on instead of drawn on hover. */
.v2-pager__page.is-current {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
    font-weight: 700;
    cursor: default;
}

/* The ellipsis the paginator hands over where it has folded a run of pages
   away. Not a control, so it carries nothing that suggests it is one. */
.v2-pager__gap {
    color: var(--v2-ink-mute);
    font-size: 0.875rem;
    letter-spacing: 0.1em;
}

/* --------------------------------------------------------------------------
   Imported pages — the compatibility layer
   --------------------------------------------------------------------------

   262 pages, and the one page type this theme cannot lay out. Every other view
   composes typed rows and can decide what shape it takes; a page here is a
   string of the clone's own HTML — a Bootstrap 3 grid, Drupal 7 field wrappers,
   tab panes, carousels and 7,334 <img> tags — written against a stylesheet this
   document deliberately does not load. Nothing in it can be rearranged from a
   Blade file without rewriting markup nobody has read since it was crawled.

   So the deal this block makes is narrow and worth stating: it restores enough
   of Bootstrap 3 and Drupal for those pages to hold their own shape, and it
   re-states the theme's type and colour on top. It does not try to turn them
   into the theme. Everything is scoped under `.v2-imported`, which is on the
   wrapper and nowhere else, so none of it can reach a composed section.

   Bootstrap **3** breakpoints, not 4 or 5. The markup is unmistakably BS3 —
   `col-xs-`, `container-fluid`, `img-responsive`, Drupal's `element-invisible`
   — and `col-sm-6 col-md-4 col-lg-2` is the commonest triple on the site. Using
   4's numbers would move every one of those a breakpoint out of step.

   Known residues, so nobody reports them as bugs: the tab panes (126) and
   carousels (168) need the clone's jQuery, and without it every pane and slide
   renders stacked and visible — everything readable, nothing dead. The spoilers
   render open. `.component-bg-card` and `.badge-item` lose their decorative
   grounds and read as plain type in a column. */

/* No `.v2-shell` and no padding, on purpose.
 *
 * Twenty of the sections on a page like /services/software/mobile-app-development
 * are `.container-fluid` bands carrying their ground as an inline
 * background-colour or photograph, and those are meant to run the full width of
 * the screen — which is this theme's own "change of paper" rule arrived at from
 * the other direction. The shell's padding would inset every one of them and
 * leave a stripe of paper down both sides of each band. The gutter is applied
 * one level in, by the containers, so bands and non-bands still produce the
 * same content column. */
.v2-imported {
    max-width: var(--v2-shell);
    margin-inline: auto;
}

/* The register bar above an imported body, and the fallback <h1>.
 *
 * Measured against the imported page rather than against the theme: the clone
 * centres its content in a 1170px column and the shell runs to 2160. Give this
 * bar the shell and its eyebrow starts about 80px to the left of the first
 * heading underneath it — the kind of near-miss that reads as carelessness
 * rather than as a decision. */
.v2-imported-head {
    width: 100%;
    max-width: calc(1170px + var(--v2-gutter) * 2);
    margin-inline: auto;
    padding-inline: var(--v2-gutter);
}

/* --- containers ---------------------------------------------------------- */

/* 2,835 `.container-1170` and ~99 `.container`. Same column as the register bar
   above, so the two line up. */
.v2-imported .container,
.v2-imported .container-1170 {
    width: 100%;
    max-width: calc(1170px + var(--v2-gutter) * 2);
    margin-inline: auto;
    /* !important, for the horizontal padding only.
     *
     * This class IS the page's grid line — every heading, band and list on an
     * imported page starts where this container's padding ends, and the whole
     * argument for the compatibility layer is that they all start at the same
     * place. Exactly one container in the corpus carries `padding-left: 25px;
     * padding-right: 25px` in a style attribute, on /company, and that one
     * inline declaration pulled its section's heading 33px left of every other
     * heading on the page — the kind of near-miss that reads as carelessness
     * rather than as a decision.
     *
     * Scoped to padding-inline, so a section that wants its own vertical
     * spacing still gets it. One occurrence today; the rule is here so an
     * import tomorrow cannot quietly move the grid line again. */
    padding-inline: var(--v2-gutter) !important;
}

/* `:has()` is load-bearing here and this is the reason.
 *
 * A fluid band that wraps a 1170 column must stay full-bleed — its inline
 * background IS the ground — and let the inner column carry the gutter. A fluid
 * band with no inner column has to carry the gutter itself or its text touches
 * the edge of the screen. Both cases end up producing the same content column,
 * which is what makes bands and non-bands line up down the page. */
.v2-imported .container-fluid:not(:has(.container, .container-1170)) {
    padding-inline: var(--v2-gutter);
}

/* --- the grid ------------------------------------------------------------ */

/* 4,257 rows and about 14,000 columns. No offsets, no pulls and three pushes in
   the whole corpus, so none of those are implemented. */
.v2-imported .row {
    display: flex;
    flex-wrap: wrap;
}

/* The negative margin only where every child is a column.
 *
 * And it is granted only inside something that pushes back. The margin is half
 * of Bootstrap's gutter trick — the row pulls out, its container pushes in — so
 * on a row whose parent has no gutter padding there is nothing to cancel it and
 * the row simply hangs 14px off the side of the window. The clone puts rows
 * straight into `.container-fluid`, into `.field-item`, into bare divs; a
 * container or a column is the only parent that makes the pull correct.
 *
 * This is the fix for an 8px horizontal overflow at 375px. The clone nests
 * `.container-1170` and un-classed wrappers such as `.baner-big` directly
 * inside a `.row`, and a blanket negative margin pushes those past the
 * viewport because they carry no compensating padding. Restricting it took
 * documentElement.scrollWidth from 368 back to 360 at a 375 viewport. */
.v2-imported :is(.container-1170, .container, [class*="col-"]) > .row:has(> [class*="col-"]):not(:has(> :not([class*="col-"]))),
.v2-imported :is(.container-1170, .container, [class*="col-"]) > .flex-row:has(> [class*="col-"]):not(:has(> :not([class*="col-"]))) {
    margin-inline: calc(var(--v2-gap) / -2);
}

.v2-imported [class*="col-xs-"],
.v2-imported [class*="col-sm-"],
.v2-imported [class*="col-md-"],
.v2-imported [class*="col-lg-"] {
    width: 100%;
    /* A flex item's default min-width is auto, so one long word or one wide
       image inside a column widens the column past its share and takes the page
       sideways. */
    min-width: 0;
    padding-inline: calc(var(--v2-gap) / 2);
}

.v2-imported .col-xs-1  { width: 8.3333%; }
.v2-imported .col-xs-2  { width: 16.6667%; }
.v2-imported .col-xs-3  { width: 25%; }
.v2-imported .col-xs-4  { width: 33.3333%; }
.v2-imported .col-xs-5  { width: 41.6667%; }
.v2-imported .col-xs-6  { width: 50%; }
.v2-imported .col-xs-7  { width: 58.3333%; }
.v2-imported .col-xs-8  { width: 66.6667%; }
.v2-imported .col-xs-9  { width: 75%; }
.v2-imported .col-xs-10 { width: 83.3333%; }
.v2-imported .col-xs-11 { width: 91.6667%; }
.v2-imported .col-xs-12 { width: 100%; }

@media (min-width: 768px) {
    .v2-imported .col-sm-1  { width: 8.3333%; }
    .v2-imported .col-sm-2  { width: 16.6667%; }
    .v2-imported .col-sm-3  { width: 25%; }
    .v2-imported .col-sm-4  { width: 33.3333%; }
    .v2-imported .col-sm-5  { width: 41.6667%; }
    .v2-imported .col-sm-6  { width: 50%; }
    .v2-imported .col-sm-7  { width: 58.3333%; }
    .v2-imported .col-sm-8  { width: 66.6667%; }
    .v2-imported .col-sm-9  { width: 75%; }
    .v2-imported .col-sm-10 { width: 83.3333%; }
    .v2-imported .col-sm-11 { width: 91.6667%; }
    .v2-imported .col-sm-12 { width: 100%; }
}

@media (min-width: 992px) {
    .v2-imported .col-md-1  { width: 8.3333%; }
    .v2-imported .col-md-2  { width: 16.6667%; }
    .v2-imported .col-md-3  { width: 25%; }
    .v2-imported .col-md-4  { width: 33.3333%; }
    .v2-imported .col-md-5  { width: 41.6667%; }
    .v2-imported .col-md-6  { width: 50%; }
    .v2-imported .col-md-7  { width: 58.3333%; }
    .v2-imported .col-md-8  { width: 66.6667%; }
    .v2-imported .col-md-9  { width: 75%; }
    .v2-imported .col-md-10 { width: 83.3333%; }
    .v2-imported .col-md-11 { width: 91.6667%; }
    .v2-imported .col-md-12 { width: 100%; }
}

@media (min-width: 1200px) {
    .v2-imported .col-lg-1  { width: 8.3333%; }
    .v2-imported .col-lg-2  { width: 16.6667%; }
    .v2-imported .col-lg-3  { width: 25%; }
    .v2-imported .col-lg-4  { width: 33.3333%; }
    .v2-imported .col-lg-5  { width: 41.6667%; }
    .v2-imported .col-lg-6  { width: 50%; }
    .v2-imported .col-lg-7  { width: 58.3333%; }
    .v2-imported .col-lg-8  { width: 66.6667%; }
    .v2-imported .col-lg-9  { width: 75%; }
    .v2-imported .col-lg-10 { width: 83.3333%; }
    .v2-imported .col-lg-11 { width: 91.6667%; }
    .v2-imported .col-lg-12 { width: 100%; }
}

/* --- what the theme's own resets took away ------------------------------- */

/* 957 `<ul>` lost both their markers and their indent to the `:where()` reset
   at the top of this file. Unlike an article body, a page body uses lists as
   layout as often as it uses them as prose — feature lists inside coloured
   cards, three-across — so the browser's own marker is restored rather than
   replaced with the accent dash. A hairline dash in every one of 957 lists
   would be the loudest thing on the site. */
.v2-imported ul,
.v2-imported ol {
    margin: 0 0 var(--v2-s3);
    padding-left: 1.35em;
    list-style: revert;
}

.v2-imported li {
    margin-bottom: var(--v2-s1);
}

/* 78ch inside a `col-md-4`, or inside a centred block, puts the text box
   visibly off-centre in its own column. The column holds the measure here. */
.v2-imported p {
    max-width: none;
}

/* The About section's standfirst treatment, cancelled — the first paragraph of
   an imported page is whatever the crawl found. Ties with `.v2-prose >
   p:first-child` on specificity and wins by coming after it. */
.v2-imported > p:first-child {
    font-family: var(--v2-sans);
    font-weight: 400;
    font-size: 1.0625rem;
    line-height: 1.6;
    color: var(--v2-ink-soft);
    max-width: none;
}

/* 7,334 images, a great many of them exported at width="1920".
 *
 * The four pages that carry a `<video>` need the same thing and were missed:
 * the demo on /services/industrial-design lays itself out at its intrinsic
 * 1820px and pushed the whole document 523px wider than the window, which is a
 * horizontal scrollbar on every section of a page whose fault is in one of
 * them. */
.v2-imported :is(img, video) {
    max-width: 100%;
    height: auto;
}

/* The embeds, for the same reason and with the same result: a YouTube iframe
   is written into these pages at its 560px default, which at 375 is 220px of
   the document hanging off the right. Height is left alone — an iframe has no
   intrinsic ratio to preserve, and `height: auto` would collapse it. */
.v2-imported iframe {
    max-width: 100%;
}

/* A word that cannot fit is still a word, and it makes the page as wide as
   itself: the sensor page carries a part number long enough to push 123px past
   the window on a phone. `break-word` only breaks the ones that have nowhere
   else to go, so ordinary prose wraps exactly as it did. */
.v2-imported :is(p, li, td, th, h1, h2, h3, h4, h5, h6) {
    overflow-wrap: break-word;
}

/* Links inside running text only. Scoped to the elements prose lives in rather
   than written as a blanket `a`, because a good third of the anchors on these
   pages wrap a card, a logo or a whole band, and colouring those accent turns a
   section into a wall of red. */
.v2-imported p a,
.v2-imported li a,
.v2-imported td a,
.v2-imported dd a,
.v2-imported h1 > a,
.v2-imported h2 > a,
.v2-imported h3 > a,
.v2-imported h4 > a,
.v2-imported h5 > a,
.v2-imported h6 > a {
    color: var(--v2-accent);
    border-bottom: 1px solid transparent;
    transition: border-color 180ms var(--v2-ease);
}

.v2-imported p a:hover,
.v2-imported li a:hover,
.v2-imported td a:hover,
.v2-imported dd a:hover,
.v2-imported h1 > a:hover,
.v2-imported h2 > a:hover,
.v2-imported h3 > a:hover,
.v2-imported h4 > a:hover,
.v2-imported h5 > a:hover,
.v2-imported h6 > a:hover {
    border-bottom-color: var(--v2-accent);
}

/* --- type ---------------------------------------------------------------- */

/* Without this the imported headings inherit Nunito Sans at weight 500 and read
   as body copy — the whole page becomes one undifferentiated column, which is
   the first thing anyone notices. The two ramps below are `.v2-display`'s and
   `.v2-h2`'s, quoted rather than referenced because a clamp cannot be stored in
   a token and re-read. */
.v2-imported h1,
.v2-imported h2,
.v2-imported h3,
.v2-imported h4,
.v2-imported h5,
.v2-imported h6 {
    font-family: var(--v2-head-font);
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 0 0 var(--v2-s2);
}

.v2-imported h1 {
    font-weight: 700;
    font-size: clamp(1.875rem, 0.8rem + 2.7vw, 3.75rem);
    line-height: 1.12;
    letter-spacing: -0.02em;
    margin-bottom: var(--v2-s3);
}

.v2-imported h2 {
    font-size: clamp(1.625rem, 1.1rem + 1.9vw, 2.75rem);
    line-height: 1.12;
}

.v2-imported h3 {
    font-size: 1.25rem;
    line-height: 1.3;
}

.v2-imported h4 {
    font-size: 1.0625rem;
    line-height: 1.35;
}

/* 3,209 uses. The clone's only weight utility. */
.v2-imported .c-font-bold {
    font-weight: 700;
}

/* Same treatment as the article body's quotes, and for the same reason: this
   theme says "quotation" with two hairlines and a change of size, and it has to
   say it the same way whichever template the reader is looking at. A coloured
   left bar was considered and dropped — 108 permanent accent rules is the
   accent becoming decoration. */
.v2-imported blockquote {
    margin: var(--v2-s3) 0;
    padding: var(--v2-s3) 0;
    border-top: 1px solid var(--v2-rule-strong);
    border-bottom: 1px solid var(--v2-rule);
}

/* --- colour on the clone's own grounds ----------------------------------- */

/* Two rules, both hung off an element that declares a ground inline — which is
   the only reliable signal these pages give.
 *
 * The first is the fix for `.v2-prose p { color: var(--v2-ink-soft) }` reaching
 * into a dark photographic band and printing #444d59 on near-black. Inside
 * anything that declares its own background, the paragraph inherits whatever
 * colour that band set for itself.
 *
 * The second is why there is NO blanket `.text-color-white { color: #fff }`
 * here: that was tried, and of the 606 uses a good many are card titles that
 * stand on paper in this theme — the blanket rule turned an entire six-card
 * section invisible. White is granted only where something above it has
 * actually declared a dark ground.
 *
 * And "declared a ground" is not the same as "declared a dark ground", which is
 * what the first version of this rule assumed. Counting the inline background
 * colours in the content settles it: 1,655 of them are #fafafa, #ffffff or
 * #f5f5f5, and nine are dark. Matching on `[style*="background-color"]` was
 * therefore wrong about 1,655 times, and wrong in the worst direction: white on
 * near-white is not faint text, it is no text at all. The automotive page lost
 * four paragraphs — ASPICE, ISO 26262, AUTOSAR, ISO 9001 — and read as a blank
 * gap where a section should have been.
 *
 * So the dark grounds are not named here at all. `App\Support\Bands` reads the
 * colour out of the style attribute while the page renders, measures it, and
 * writes `v2-band--dark` onto the ones that are actually dark — which is a fact
 * about the band rather than a list of the bands known so far. The first
 * version of this rule did name them, five hexes copied off a tally, and it
 * missed #030318 on the stackable-switch page. That is the failure mode of any
 * such list: it is right until the next page.
 *
 * A background *image* still counts as dark: those are photographs, and they
 * carry an overlay. */
.v2-imported [style*="background"] p {
    color: inherit;
}

.v2-imported :is([style*="background-image"], .v2-band--dark) :is(.text-color-white, .text-white),
.v2-imported :is([style*="background-image"], .v2-band--dark) :is(.text-color-white, .text-white) a {
    color: #fff;
}

/* --- the hero overlay, on 189 of the 262 pages --------------------------- */

/* The markup is `.baner-big > img + .baner-text > .container-1170 > p` with the
   caption's colour written inline as #ffffff. Un-positioned, that white caption
   falls below the photograph onto paper and vanishes — and an inline colour
   cannot be beaten without !important, which would also flatten the legitimate
   ones. So the overlay is put back instead of the colour being fought.
 *
 * Below 768 the photograph is about 110px tall and an overlay would spill out
 * of it, so the caption becomes a night band directly under the picture: a
 * change of paper rather than a broken overlay, which is the move this theme
 * makes everywhere else. */
.v2-imported .baner-text {
    background: var(--v2-night);
    padding-block: var(--v2-s3);
    color: #fff;
}

@media (min-width: 768px) {
    .v2-imported .baner-big {
        position: relative;
    }

    .v2-imported .baner-text {
        position: absolute;
        inset-inline: 0;
        bottom: clamp(var(--v2-s2), 4vw, var(--v2-s5));
        background: none;
        padding-block: 0;
    }
}

/* --- utilities and Drupal chrome ----------------------------------------- */

/* 186 uses. Drupal's visually-hidden. Unstyled it prints "You are here" and
   "Primary tabs" into the middle of the page. */
.v2-imported .element-invisible,
.v2-imported .element-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* 593 uses, and they still matter: several bands float a picture beside a
   column and collapse without it. */
.v2-imported .clearfix::after {
    content: "";
    display: table;
    clear: both;
}

.v2-imported .d-flex,
.v2-imported .flex-row {
    display: flex;
    flex-wrap: wrap;
}

.v2-imported .align-self-center {
    align-self: center;
}

/* Kept centred, unlike the article layer, which un-centres the same classes.
   That is a considered difference rather than an oversight: a page body is a
   composed marketing layout where a centred band is part of the composition,
   and an article body is prose, where centring is the generated-page tell. */
.v2-imported .text-center,
.v2-imported .rtecenter {
    text-align: center;
}

.v2-imported .text-right {
    text-align: right;
}

.v2-imported .text-left {
    text-align: left;
}

/* 1,114 empty divs whose only job was to draw a short rule under a heading.
   Drawn in the rule colour and not the accent: 1,114 red ticks is the accent
   everywhere, which is the same as nowhere. */
.v2-imported .line-underline {
    width: 56px;
    height: 1px;
    background: var(--v2-rule);
    margin: var(--v2-s2) 0 var(--v2-s3);
}

/* 3,627 uses — the wrapper round almost every heading on the site. */
.v2-imported .content-title-outer {
    margin-bottom: var(--v2-s2);
}

/* --- tables and forms ---------------------------------------------------- */

/* 18 tables. `display: block` is what makes a wide one scroll inside itself
   instead of scrolling the page — the same fix the article bodies need, for the
   same reason: there is no wrapper element to add. */
.v2-imported table {
    display: block;
    overflow-x: auto;
    width: 100%;
    border-collapse: collapse;
    margin-bottom: var(--v2-s3);
}

.v2-imported th,
.v2-imported td {
    padding: var(--v2-s1) var(--v2-s2);
    border-bottom: 1px solid var(--v2-rule);
    text-align: left;
    vertical-align: top;
}

/* 711 `.btn`, mapped to the GHOST treatment rather than the solid one. Some of
   these pages carry five buttons in a screenful and the accent is reserved for
   exactly one — five red slabs is the rule broken five times over. The page's
   real call to action is in the utility bar above it either way. */
.v2-imported .btn,
.v2-imported .form-submit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: 0 var(--v2-s3);
    border: 1px solid var(--v2-rule);
    background: transparent;
    color: var(--v2-ink);
    font: 500 0.9375rem/1 var(--v2-sans);
    cursor: pointer;
    transition: border-color 180ms var(--v2-ease);
}

.v2-imported .btn:hover,
.v2-imported .form-submit:hover {
    border-color: var(--v2-ink);
}

.v2-imported .form-control,
.v2-imported input[type="text"],
.v2-imported input[type="email"],
.v2-imported input[type="tel"],
.v2-imported textarea,
.v2-imported select {
    width: 100%;
    padding: var(--v2-s1) 0;
    border: 0;
    border-bottom: 1px solid var(--v2-rule);
    border-radius: 0;
    background: transparent;
    color: var(--v2-ink);
    font: 400 1rem/1.5 var(--v2-sans);
}

.v2-imported .form-control:focus,
.v2-imported input:focus,
.v2-imported textarea:focus,
.v2-imported select:focus {
    outline: none;
    border-bottom-color: var(--v2-accent);
}

.v2-imported .form-item {
    margin-bottom: var(--v2-s3);
}

/* --------------------------------------------------------------------------
   The body section of an article, when it is the first thing on the page
   --------------------------------------------------------------------------

   All 277 imported posts take the bodyCarriesOwnHeader() branch: the view
   prints no masthead of its own, so `.v2-article__body` is the first section
   under the header and its crumb bar is the imported one inside the body. With
   the section's ordinary clamp(64px, 8vw, 128px) that crumb line would sit
   twice as far below the header as the themed branch puts its own — which is
   the one thing a template with two branches may not do. Same number as
   `.v2-hero` and `.v2-section--lead`, for the same reason.

   Written as a `:first-child` rather than put on the class outright because on
   a case study, and on a post written in the panel tomorrow, this section comes
   after a masthead and wants its full padding back. */

.v2-article > .v2-article__body:first-child {
    padding-top: clamp(40px, 3.5vw, 72px);
}

/* --------------------------------------------------------------------------
   The enquiry form's working parts
   --------------------------------------------------------------------------

   The form itself was already styled — .v2-form, .v2-field and .v2-btn have
   been in this file since the home page was built. What was never styled is
   everything that happens when somebody actually uses it: the trap, the
   required marks, the message a rejected field prints, and the line that says
   it went. None of it had rules because until now the form posted to a path
   that did not exist, so none of these states could be reached.

   All four live on the blue band as often as on paper, so each is written
   against the tokens the band modifiers redefine rather than against a fixed
   colour. -------------------------------------------------------------- */

/* The honeypot.

   Moved off-screen rather than display:none or hidden, deliberately. A bot
   that reads the stylesheet skips a field that is not rendered and fills one
   that is, which defeats the trap; a field that is painted but positioned
   outside the viewport is filled by exactly the naive bots this is meant to
   catch. It stays out of the tab order and out of the accessibility tree from
   the markup, not from here. */
.v2-honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* The required mark.

   Muted rather than accented. Six red asterisks down one column is the accent
   becoming decoration, and it would be the only red on a band whose whole job
   is to carry one button. The mark is aria-hidden in the markup because the
   input already carries `required`, which is what a screen reader announces. */
.v2-field__req {
    margin-left: 0.25em;
    color: var(--v2-ink-mute);
}

/* A rejected field.

   Accent on paper, because that is where a red word means "this one". On any
   of the coloured grounds red on blue is unreadable, so the message goes to
   white and earns its emphasis from weight instead — the same substitution
   .v2-link makes when it crosses onto a band. */
.v2-field__error {
    display: block;
    margin-top: var(--v2-s1);
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--v2-accent);
}
.v2-section--blue .v2-field__error,
.v2-section--brand .v2-field__error,
.v2-section--night .v2-field__error {
    color: #fff;
}

/* The confirmation.

   Between two hairlines rather than in a tinted box: a box with a background
   and a radius is the house style of every form library and none of this
   theme. It is the first thing in the form's place after a send, so it is set
   at the standfirst size — somebody who has just pressed the button is looking
   for exactly this line. */
.v2-form__sent {
    grid-column: 1 / -1;
    margin: 0 0 var(--v2-s4);
    padding-block: var(--v2-s3);
    border-top: 1px solid currentColor;
    border-bottom: 1px solid currentColor;
    font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.25rem);
    font-weight: 600;
}

/* --------------------------------------------------------------------------
   The offices
   --------------------------------------------------------------------------

   Six columns across, not six rows down. The addresses are one set of
   comparable facts and a reader is picking the country nearest them out of the
   set, which is a scan across rather than a read down — the one arrangement the
   imported page got right.

   Separation is a hairline over each column, aligned across the row. That is
   this theme's whole method applied to a case that usually gets cards: six
   boxes with a border and a shadow each is what every other site does here, and
   it is six times as much furniture for the same six addresses. A rule and a
   column of type says the same thing and disappears while you read it.

   The count steps 1 → 2 → 3 → 6 rather than filling with auto-fit, because
   auto-fit would put four across at some widths and leave two stranded
   underneath. Six divides by one, two, three and six; those are the only
   arrangements that come out even. ---------------------------------------- */

.v2-offices {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--v2-s4) var(--v2-gap);
}
@media (min-width: 560px) {
    .v2-offices {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (min-width: 900px) {
    .v2-offices {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
@media (min-width: 1200px) {
    /* Six columns, because there were six offices. There are two now, and a
       fixed six left them huddled in the first third of the row with two
       thirds of empty paper beside them - the same fault the figures row had
       when the offices count came off it.

       auto-fit spreads whatever the panel holds. The floor keeps a column from
       becoming a half-page slab when there are only two, and the exception
       below restores the drawn six the moment there are seven, which is when
       auto-fit would start wrapping them unevenly. */
    .v2-offices {
        /* auto-FILL, not auto-fit. auto-fit collapses the tracks nothing is
           in and stretches the rest, which turned two offices into two
           half-page slabs. auto-fill keeps the empty tracks, so two offices
           stay two narrow columns at the left of the row - which is what the
           six-column original looked like and what reads better here. */
        grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));

        /* Wider than the page gutter, and only here. Two columns sitting a
           standard gutter apart read as the first two of a row of six that
           failed to load; set further apart they read as two, which is what
           they are. The row gap is left alone - it only matters if the offices
           ever wrap onto a second line. */
        column-gap: var(--v2-s5);
    }

    .v2-offices:has(.v2-office:nth-child(7)) {
        grid-template-columns: repeat(6, minmax(0, 1fr));
    }
}

/* The column itself.

   Declared for both the anchor and the div because an office with no map is
   the same column with nothing to click, and the two must not differ by so
   much as a pixel of padding. */
.v2-office {
    display: block;
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule);
    text-decoration: none;
    color: inherit;
}

/* The country outline.

   Given a fixed height and a fixed place at the top of the column so the six
   names below sit on one line across the row. Without it each drawing would
   set its own height — Poland is nearly square, Latvia is a letterbox — and
   the names would step up and down across the page.

   Each shape is fitted to its own box rather than all six sharing one scale.
   True relative area was drawn first and rejected on looking at it: at this
   size one scale leaves Germany and Poland legible and reduces Latvia, Serbia
   and the Netherlands to specks. These are read as "which of these is mine",
   and an icon nobody can make out is not a truer icon for being to scale.

   No fill. A solid silhouette is a block of ink where this theme puts a line,
   and at six across a row of solid shapes reads as a chart of something. The
   stroke is not scaled with the shape (vector-effect) because the drawings are
   normalised to a 100-unit box and a country that fills a fifth of it would
   otherwise be drawn five times as finely as its neighbour. */
.v2-office__figure {
    display: block;
    margin-bottom: var(--v2-s3);
}
.v2-office__map {
    display: block;
    width: auto;
    /* 64px was drawn for six across. At two the column has room, and the
       shape is what a reader recognises before any of the words. */
    height: 124px;
    overflow: visible;
    fill: none;
    stroke: var(--v2-ink-mute);
    stroke-width: 1.25px;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    transition: stroke 0.2s var(--v2-ease);
}

/* The number.

   The same figure the ruled index prints in its left column, kept here so the
   two treatments are recognisably the same device seen from a different angle.
   It is decoration in the strict sense — the name below it is the label — so
   it is muted and small and never the thing the eye lands on first. */
/* Every part below is scoped through `.v2-offices` rather than named on its
   own. That is not tidiness: this component now renders inside `.v2-imported`
   as well as on the composed contacts page, and the compatibility layer styles
   bare `h3` and `p` there. `.v2-imported h3` is (0,1,1) and beat a lone
   `.v2-office__name` at (0,1,0), so on /company the office names came out at
   28px against 17px on /contacts — the same component, two sizes, on two pages
   a reader moves between. Scoping takes each of these to (0,2,0), which wins.
   Verified rendered on both pages rather than reasoned about. */
.v2-office__index {
    display: block;
    margin-bottom: var(--v2-s2);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    color: var(--v2-ink-mute);
}
.v2-office__name {
    margin: 0 0 var(--v2-s2);
    font-family: var(--v2-head-font);
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
}
/* The pin in front of the address.

   An icon rather than a word, because "Location" in front of a location is a
   label saying what the reader can already see. It sits on the first line's
   baseline and takes the accent when the column is hovered, so it reads as part
   of the link rather than as decoration beside it. */
.v2-office__pin {
    width: 14px;
    height: 14px;
    margin-right: 6px;
    vertical-align: -2px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}

/* The address, as the one thing in the column you can click. */
.v2-office__place {
    color: inherit;
    text-decoration: none;
    transition: color 160ms var(--v2-ease);
}

.v2-office__place:hover,
.v2-office__place:focus-visible {
    color: var(--v2-accent);
    text-decoration: underline;
    text-underline-offset: 3px;
}


.v2-office__address {
    margin: 0;
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--v2-ink-soft);
}
.v2-office__phone {
    margin: var(--v2-s1) 0 0;
    font-size: 0.9375rem;
    color: var(--v2-ink-mute);
}

/* Under the pointer the rule thickens to the ink and the name takes the
   accent — the same two things that happen to a row in the ruled index, so a
   reader who has hovered one already knows what this one does. The rule is
   redrawn in a colour rather than in a weight: a border that grows by a pixel
   moves everything under it. */
a.v2-office:hover,
a.v2-office:focus-visible {
    border-top-color: var(--v2-rule-strong);
}
.v2-offices a.v2-office:hover .v2-office__name,
.v2-offices a.v2-office:focus-visible .v2-office__name {
    color: var(--v2-accent);
}
.v2-offices a.v2-office:hover .v2-office__map,
.v2-offices a.v2-office:focus-visible .v2-office__map {
    stroke: var(--v2-accent);
}

/* --------------------------------------------------------------------------
   The news grid
   --------------------------------------------------------------------------

   The one place in this theme that is a grid of cards rather than a ruled
   index, and the exception is argued in the view: an archive of 276 posts that
   each arrived with a photograph of what it is about is not the same kind of
   list as eight services whose icons were drawn afterwards.

   The exception is to the LAYOUT, not to the rules. There is no border on four
   sides, no radius and no shadow anywhere below — a card here is a picture, a
   hairline and type under it. That is what stops a grid reading as a component
   borrowed from somewhere else, and it is the whole reason this section can
   sit in the same stylesheet as the rest.

   3 → 2 → 1 columns. Twelve to a page divides by all three, so the last row is
   never a short one left hanging. ------------------------------------------ */

.v2-cards {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--v2-s5) var(--v2-gap);
    margin-top: var(--v2-s5);
}
@media (min-width: 620px) {
    .v2-cards {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (min-width: 1000px) {
    .v2-cards {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.v2-card {
    display: block;
    text-decoration: none;
    color: inherit;
}

/* The picture.

   A fixed 3:2 window rather than each image's own proportions. The hero images
   came out of the crawl in every shape from square to panoramic, and twelve
   different heights in four rows is not a grid — it is twelve pictures that
   happen to be near each other. object-fit crops to the window instead of
   letterboxing, so no card carries bands of empty paper down its sides. */
.v2-card__frame {
    display: block;
    position: relative;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    /* The diagonal hatch.
     *
     * A tile of 45-degree lines in the accent, at the weight where it reads as
     * a texture rather than as stripes. It has two jobs and they are the same
     * drawing: it is what occupies the window in the moment before a lazy
     * image arrives, and it is what stands in for the one post in 276 that has
     * no hero image — which keeps that post the same shape as its neighbours
     * instead of a caption with a hole above it.
     *
     * An SVG tile rather than a repeating-linear-gradient, because this file
     * says no gradients and means it; the two band grounds are drawn the same
     * way, with an inline SVG, for the same reason. */
    background-color: var(--v2-paper-sunk);
    background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='9'%20height='9'><path%20d='M-1,1%20l2,-2%20M0,9%20l9,-9%20M8,10%20l2,-2'%20stroke='%23a32b2c'%20stroke-opacity='0.30'%20stroke-width='1.1'/></svg>");
}
.v2-card__frame img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Sat directly on the hatch, an image with transparency would show it
       through and read as a printing fault. */
    background: var(--v2-paper);
    transition: opacity 0.3s var(--v2-ease);
}

/* Under the pointer the picture steps back and lets its own ground show
   through, so the hatch is what the movement is made of rather than a scale or
   a shadow — the two things every card grid reaches for and neither of which
   exists anywhere else in this theme. The hairline under the date darkens at
   the same moment, which is what a row of the ruled index does, so the two
   patterns answer a pointer the same way. */
a.v2-card:hover .v2-card__frame img,
a.v2-card:focus-visible .v2-card__frame img {
    /* 0.82 was tried first and could not be seen — which makes it a rule that
       costs a repaint and buys nothing. At 0.6 the hatch reads through the
       photograph as a diagonal wash in the accent, which is the point: the
       card answers the pointer with the one pattern this page owns. */
    opacity: 0.6;
}

.v2-card__date {
    display: block;
    margin-top: var(--v2-s3);
    padding-top: var(--v2-s2);
    border-top: 1px solid var(--v2-rule);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    transition: border-color 0.2s var(--v2-ease);
    /* An undated post still opens the hairline and reserves the line, so the
       headline under it starts level with the others across the row. */
    min-height: 1.6em;
}
a.v2-card:hover .v2-card__date,
a.v2-card:focus-visible .v2-card__date {
    border-top-color: var(--v2-rule-strong);
}

.v2-card__title {
    margin: var(--v2-s2) 0 0;
    font-family: var(--v2-head-font);
    font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
    transition: color 0.2s var(--v2-ease);
}
a.v2-card:hover .v2-card__title,
a.v2-card:focus-visible .v2-card__title {
    color: var(--v2-accent);
}

.v2-card__text {
    margin: var(--v2-s2) 0 0;
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--v2-ink-soft);
}

/* --------------------------------------------------------------------------
   The filter, in two steps

   Only reached once the script has run: it adds `.is-tabbed` after it has built
   the tab row out of the group labels. Without it none of this applies and the
   strip stays as the server rendered it — every group open, which is long but
   complete.
   -------------------------------------------------------------------------- */

.v2-filter__tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--v2-s3);
    padding-block: var(--v2-s2);
    border-bottom: 1px solid var(--v2-rule);
}

/* The group names, set as the page's own eyebrow so the tabs read as labels
   rather than as another row of chips. The chips below are the things being
   chosen; two ranks of identical buttons would say they are the same rank. */
.v2-filter__tab {
    padding: var(--v2-s1) 0;
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    cursor: pointer;
    transition: color 160ms var(--v2-ease), border-color 160ms var(--v2-ease);
}

.v2-filter__tab:hover {
    color: var(--v2-ink);
}

.v2-filter__tab.is-active {
    color: var(--v2-ink);
    border-bottom-color: var(--v2-accent);
}

/* With one group showing at a time the strip is a quarter of what it was, so
   the rules between groups have nothing left to separate and the chips can come
   down a size. Small is the point: this sits between the heading and the work,
   and it was pushing the first case study off the screen. */
.v2-filter.is-tabbed .v2-filter__row {
    border-bottom: 0;
    padding-block: var(--v2-s2) var(--v2-s3);
}

.v2-filter.is-tabbed .v2-filter__link {
    min-height: 30px;
    padding: 0 var(--v2-s1);
    font-size: 0.8125rem;
}

@media (max-width: 767px) {
    .v2-filter.is-tabbed .v2-filter__link {
        /* Still a thumb target on a phone, whatever the type size says. */
        min-height: 40px;
        padding: 0 var(--v2-s2);
    }

    .v2-filter__tabs {
        gap: var(--v2-s2);
    }
}

/* The hidden attribute only carries `display: none` from the user agent's own
   stylesheet, and any rule in this file outranks that — so a row set to
   display:grid stayed on screen after the script hid it, and all four groups
   were visible under a tab bar that claimed one was chosen. Both elements the
   script hides are named here, because "it is hidden" has to beat "it is a
   grid" every time and not only where somebody remembered. */
.v2-filter__row[hidden],
.v2-filter__label[hidden] {
    display: none;
}

/* --------------------------------------------------------------------------
   Carousels that never start
   --------------------------------------------------------------------------

   The clone drove three widgets from its jQuery bundle, and this theme
   deliberately does not load that bundle. Two of them degrade harmlessly. The
   third does not: `.gallery-container.gallery-carousel` is a strip of partner
   and client logos that a plugin laid out at runtime, so with no script every
   `.gallery-item` falls back to what a div is — full width, one per line. On
   /company that turned 27 logos into 6,500px of scrolling, one logo per
   screenful, which is most of why that page was 18,000px long.

   The fix is not to load the plugin. A logo wall does not want to be a
   carousel: a carousel hides most of the logos most of the time, needs a
   script to show any of them, and exists because the row would not otherwise
   fit — which is a layout problem, and grid solves layout problems. So the
   container becomes a static grid and the widget stops being a widget. 65
   imported pages carry this markup and all of them get it.

   auto-fill rather than auto-fit: a wall of five logos should be five cells at
   their proper size with the rest of the row empty, not five logos stretched
   across the whole width like a menu bar. ---------------------------------- */

.v2-imported .gallery-container {
    display: grid;
    /* 130 rather than 150 so a 1170px column fits seven cells: the partner
       wall is thirteen logos and the client wall fourteen, which come out 7+6
       and 7+7 instead of 6+6+1 with one logo stranded on a line of its own. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 130px), 1fr));
    gap: var(--v2-s4) var(--v2-gap);
    align-items: center;
}

/* The cell. `place-items` rather than a flexbox, so a logo that is wider than
   its cell is centred rather than pushed against the left edge — a wall of
   logos reads as a wall only if they share a centre line. */
.v2-imported .gallery-item {
    display: grid;
    place-items: center;
    min-width: 0;
}
.v2-imported .gallery-item > span {
    display: block;
    max-width: 100%;
}

/* The logo itself.
 *
 * Capped by HEIGHT, not width. These files are every shape from a square mark
 * to a long wordmark, and constraining the width makes a wordmark tiny beside
 * a square one; capping the height is what makes a set of logos look like a
 * set. The width: auto is there to beat the compatibility layer's
 * .img-responsive, which sets width:100% on every imported image.
 *
 * Grey, and coloured under the pointer. Twenty-seven logos in their own brand
 * colours is twenty-seven accents on a page whose whole argument is that it
 * has one. This is the ordinary treatment for a logo wall and it is one
 * declaration to remove if a partner agreement requires the original colours. */
.v2-imported .gallery-item img {
    width: auto;
    height: auto;
    max-width: 100%;
    /* 56 and 0.78 after looking at 46 and 0.68 on screen: at the smaller
       numbers half the wordmarks — Texas Instruments, Renesas, ST — were grey
       smudges rather than names. Muted is the intent; illegible is not. */
    max-height: 56px;
    object-fit: contain;
    filter: grayscale(1);
    opacity: 0.78;
    transition: filter 0.2s var(--v2-ease), opacity 0.2s var(--v2-ease);
}
.v2-imported .gallery-item:hover img {
    filter: none;
    opacity: 1;
}

/* --------------------------------------------------------------------------
   The history timeline
   --------------------------------------------------------------------------

   Four pages carry `.about-us__history-step`, and without the clone's
   stylesheet each step stacked its year, its icon, its heading and its
   paragraph in one column: eleven steps at 420px each, 4,800px for a list of
   eleven sentences.

   Laid out the way this theme lays out every other list of dated things — the
   figure in a narrow left column, the substance in a wide right one, a
   hairline between rows. It is the ruled index again, which is the point: an
   imported page should reach for the shapes the composed pages already use
   rather than invent a twelfth one. ---------------------------------------- */

.v2-imported .about-us__history-step {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--v2-s2);
    padding-block: var(--v2-s4);
    border-top: 1px solid var(--v2-rule);
}
@media (min-width: 720px) {
    .v2-imported .about-us__history-step {
        /* Three columns, not two. The step has a fourth child nobody would
           guess from its name — `.about-us__step-img-wrapper`, the photograph —
           and on a two-column grid it dropped into the next row under the year,
           which put a stranded thumbnail in the margin of every entry. Given a
           column of its own it becomes what it is: the picture belonging to
           that year, at the end of the line that describes it. */
        grid-template-columns: 7rem minmax(0, 1fr) 200px;
        gap: var(--v2-gap);
        align-items: start;
    }
    .v2-imported .about-us__step-year {
        grid-column: 1;
    }
    .v2-imported .about-us__step-description {
        grid-column: 2;
    }
    .v2-imported .about-us__step-img-wrapper {
        grid-column: 3;
    }
}

/* The photograph.
 *
 * A fixed 3:2 window for the same reason the news grid uses one: these came
 * out of the crawl at every shape from square to panoramic, and eleven
 * different heights down the right-hand edge is eleven pictures near each
 * other rather than a column. */
.v2-imported .about-us__step-img-wrapper {
    width: 100%;
}
.v2-imported .about-us__step-img-wrapper img {
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}
.v2-imported .about-us__step-year {
    font-family: var(--v2-head-font);
    font-size: 1.125rem;
    font-weight: 700;
    color: var(--v2-ink-mute);
}

/* The dot the clone drew on its vertical rule. There is no vertical rule here
   — the hairline between steps does that job — so the dot is a mark pointing
   at nothing. */
.v2-imported .about-us__step-point {
    display: none;
}

/* The icon.
 *
 * The clone puts it inside an <h3>, and it is inconsistent about how: five of
 * the eleven steps give the picture an <h3> to itself followed by a second
 * <h3> holding the title, and the other six put the picture and the title in
 * the SAME <h3>.
 *
 * That inconsistency is worth writing down, because the obvious rule is wrong.
 * Zeroing the font on `h3:has(> img)` — to kill the line box an icon-only
 * heading leaves behind — silently deleted the title on all six of the steps
 * that share a heading, and the page still looked plausible: six entries with
 * a picture and a paragraph and no headline, which reads as content nobody
 * wrote rather than as a stylesheet eating it.
 *
 * So the type is left alone and only the picture is constrained. An icon-only
 * heading keeps a little more air above its title than it strictly needs;
 * that is the cheaper of the two mistakes by a distance. */
.v2-imported .about-us__step-description h3 {
    margin: 0 0 var(--v2-s2);
}
/* One box for every icon, rather than one height.
 *
 * Capping the height alone left the widths anywhere between 16px and 72px —
 * these files are a tall padlock, a wide map of Europe, a square coin and a
 * wordmark — so in the six steps where the icon and the title share a heading,
 * every title started at a different place. A fixed box with object-fit
 * containing the drawing inside it gives the column one left edge and the
 * icons one apparent size, without stretching any of them.
 *
 * !important on the dimensions because each of these tags carries its own
 * `style="width: 79px; height: 75px"`. */
.v2-imported .about-us__step-description h3 > img {
    display: inline-block;
    width: 40px !important;
    height: 32px !important;
    object-fit: contain;
    object-position: left center;
    vertical-align: middle;
    margin-right: var(--v2-s2);
}

/* The one absolutely positioned image on the site.

   /company carries `<img class="about-us-image" style="position:absolute;
   width:956px">`, which in the clone hung out of a positioned container as a
   deliberate bleed. Nothing here is positioned, so it escaped its column
   instead and ran 235px past the right edge of the screen — clipped, so it
   never showed as a scrollbar and never showed up in an overflow check either.

   !important because the numbers are in the tag's own style attribute, and an
   inline style is the one thing in an imported body that a stylesheet cannot
   outrank any other way. Scoped to the class, so it is this image and not a
   licence for the compatibility layer to start overriding inline styles. */
.v2-imported .about-us-image {
    position: static !important;
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;
}

/* --------------------------------------------------------------------------
   The paginator, made visible

   It was set as a line of text with a hairline under whichever number you were
   on: 25 pixels tall, at the foot of a page nineteen screens long, and the only
   way to reach case study number twenty. Correct in the theme's voice and far
   too quiet for its job — by the time a reader has scrolled past twelve
   projects, a row of small grey numerals reads as a caption, not as the way
   forward.

   The chips in the filter above solved the same problem the same way, so the
   two now match: an outline is what says "target", and the filled one is where
   you are. Nothing new is introduced — this is the filter's own vocabulary
   applied to the other control on the page.
   -------------------------------------------------------------------------- */

.v2-pager {
    /* A rule above it, and room. The paginator is a footer to the list and was
       sitting in the same undifferentiated space as the last card's text. */
    padding-top: var(--v2-s4);
    border-top: 1px solid var(--v2-rule);
    gap: var(--v2-s2);
    align-items: center;
}

.v2-pager .v2-pager {
    padding-top: 0;
    border-top: 0;
}

.v2-pager__pages {
    align-items: center;
    gap: var(--v2-s1);
}

/* Numbers and steps share one box so the row sits on a single line rather than
   on three different baselines. */
.v2-pager__page,
.v2-pager__step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 40px;
    padding: 0 var(--v2-s2);
    border: 1px solid var(--v2-rule);
    border-bottom-width: 1px;
    font-size: 0.875rem;
    transition: color 160ms var(--v2-ease), border-color 160ms var(--v2-ease),
        background-color 160ms var(--v2-ease);
}

.v2-pager__page {
    min-width: 40px;
    padding-inline: var(--v2-s1);
}

a.v2-pager__page:hover,
a.v2-pager__page:focus-visible,
a.v2-pager__step:hover,
a.v2-pager__step:focus-visible {
    border-color: var(--v2-accent);
    color: var(--v2-accent);
    background: color-mix(in srgb, var(--v2-accent) 7%, transparent);
}

/* Where you are: filled, as the chosen term is in the filter. */
.v2-pager__page.is-current {
    background: var(--v2-accent);
    border-color: var(--v2-accent);
    color: #fff;
}

/* On the first and last page. It keeps its box so the row does not change
   width as you move through it, but nothing about it invites a press. */
.v2-pager__step.is-disabled {
    border-color: var(--v2-rule);
    color: var(--v2-ink-mute);
    opacity: 0.55;
}

/* The fold marker is not a control and does not get a box. */
.v2-pager__gap {
    border: 0;
    padding-inline: 0;
    min-width: auto;
}

@media (max-width: 767px) {
    /* 40px is the desktop size and it is four short of a thumb. Nineteen pages
       of case studies means this control gets pressed more than any other on
       the page. */
    .v2-pager__page,
    .v2-pager__step {
        min-height: 44px;
    }

    .v2-pager__page {
        min-width: 44px;
    }

    /* Previous and Next take the ends of the row and the numbers sit between
       them: on a phone the two steps are what get used, and hunting for them
       inside a wrapped block of seventeen numerals is the worst version of
       this control. */
    .v2-pager {
        justify-content: space-between;
    }

    .v2-pager__pages {
        order: 2;
        width: 100%;
        justify-content: center;
    }
}

/* --------------------------------------------------------------------------
   The phone, measured

   Everything below comes from one pass with a ruler at 390px rather than from
   taste: every control on the page was measured, and these are the ones that
   came in under 44px. That number is not a convention — it is roughly the width
   of an adult thumb, and under it the misses are not the user's fault.

   Inline links inside imported prose are deliberately not touched. A link in
   the middle of a sentence is exempt from the rule for the obvious reason: the
   sentence sets its own line height, and padding one link would push the line
   apart. 77 of them on the services page alone.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
    /* The masthead. The email and the call to action are the two things a phone
       reader is most likely to press, and they were the smallest: 32 and 36. */
    .v2-utility__bar {
        min-height: 44px;
    }

    .v2-utility__item,
    .v2-utility__cta {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* The logo is the way home from every page, and it was a 24px strip. The
       image keeps its size; the link grows around it. */
    .v2-logo {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }

    /* Reached by keyboard only, but the first thing a keyboard reaches. */
    .v2-skip {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* The filter, both ranks. The tabs were 33 and the terms 40 — close enough
       to look right in a screenshot and wrong under a thumb. */
    .v2-filter__tab {
        min-height: 44px;
    }

    .v2-filter.is-tabbed .v2-filter__link,
    .v2-filter__link {
        min-height: 44px;
    }

    /* The imported contact form. 41px is the clone's own height and it is the
       one number on this page that is nearly right, which is exactly why it
       would never have been reported. The font size is already 16px, so iOS
       does not zoom the page when a field takes focus — that part was sound. */
    .form-control,
    input[type='text'],
    input[type='email'],
    input[type='tel'],
    select {
        min-height: 44px;
    }

    /* A 13px native checkbox next to a line of terms nobody can hit. The label
       beside it is clickable too, but the box is what people aim at. */
    .form-checkbox,
    input[type='checkbox'] {
        width: 22px;
        height: 22px;
    }
}

/* The copyright, in the eyebrow's type but under its own name — see the
   partial for why it cannot be an eyebrow any more. */
.v2-footer__copyright {
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
}

/* Centred, and the links with it.

   The row was copyright hard left and two links hard right, which is the
   arrangement a footer uses when its last line is a toolbar. This one is not:
   it is three small pieces of small print, and read as one centred group they
   close the page instead of framing it. */
.v2-footer__base {
    justify-content: center;
    text-align: center;
}

/* The last line of the footer — the copyright, the privacy link, LinkedIn.
   Three links on their own at 21px tall, at the very bottom of the page, which
   is where a thumb is least accurate because it is already at the edge of its
   reach. They are not inside a sentence, so nothing is broken by giving them
   room. */
@media (max-width: 767px) {
    .v2-footer__base a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }
}

/* Controls inside the imported markup.

   The breadcrumb, the card titles and the tag lists on 262 pages are the
   clone's own links at 23px. They are not inline links inside a sentence —
   each stands alone as a thing to press — so the exemption does not apply to
   them.

   Padding rather than min-height, and deliberately: vertical padding on an
   inline element enlarges what a finger can hit without adding to the line box,
   so the tap area grows and not one of those 262 layouts moves. */
@media (max-width: 767px) {
    .breadcrumb a,
    .field-content > a,
    .views-field > a {
        padding-block: 11px;
    }

    /* 22 was already past the 24px floor's neighbourhood but short of it. The
       label beside it is clickable too and carries the comfortable target; this
       is about the box somebody actually aims at. */
    .form-checkbox,
    input[type='checkbox'] {
        width: 24px;
        height: 24px;
    }
}

/* Three defects the ruler could not see and a screenshot could.

   1. The phone number came back. The rule that hides it on a narrow bar is
      `display: none`, and the tap-target rule above sets `display: inline-flex`
      on every item in the bar — same specificity, written later, so it won.
      The number reappeared in a bar with no room for it and was clipped
      mid-digit. Re-asserted here, after, where it belongs: the rules that
      decide whether a thing exists have to come after the rules that decide
      how big it is.

   2. An empty eyebrow. On an imported page the head prints its `//` mark and
      its rule with no label between them, so the top of every one of those 262
      pages opened with a stray pair of slashes.

   3. An empty banner caption. The clone's hero carries a text box over the
      image; on the pages where it is empty it stacks under the picture on a
      phone as a 48px navy strip that means nothing.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
    .v2-utility__item--phone,
    .v2-utility__item[rel='noopener'] {
        display: none;
    }
}

/* :has() rather than :empty — the element holds whitespace from the template,
   which :empty does not forgive, and what actually matters is that the only
   thing inside it is the rule. */
.v2-head:has(> .v2-head__rule:only-child) {
    display: none;
}

/* Hidden when it holds nothing that would print.
   
   Two dead ends got here. :empty does not forgive the newline the template
   leaves inside the box, and :has() cannot be nested inside another :has(),
   which is a syntax error and takes the whole rule down with it — silently, as
   an invalid selector. So the test is flat: does this caption contain any of
   the things a caption is made of. */
.baner .baner-text:not(:has(h1, h2, h3, h4, h5, p, a, span, ul, ol, img, button)) {
    display: none;
}

/* The burger and the globe belong on the right.

   They were sitting against the logo with 117px of empty bar to their right.
   The cause is a rule meant for the desktop: `.v2-nav + .v2-header__aside`
   cancels the auto margin so the controls sit beside the navigation rather than
   being pushed away from it. On a phone the navigation is display:none — but it
   is still in the document, so the adjacent sibling still matches and the
   cancel still applies to a row that has nothing else in it.

   A selector that tests for a sibling is testing the markup, not the layout. If
   the rule is about the nav being visible, it has to live where the nav is
   visible. */
@media (max-width: 1023px) {
    .v2-nav + .v2-header__aside,
    .v2-header__aside {
        margin-left: auto;
    }
}

/* --------------------------------------------------------------------------
   The clone's logo carousels, as a grid

   These are marked up as a slick carousel and the theme never loads slick, so
   what renders is the raw list: thirteen logos wrapping wherever they happen
   to fit. Measured on /company, that meant a strip starting 58px to the left of
   the column every other block on the page starts at, three rows of 7, 5 and 1,
   and logos ranging from 70x43 to 96x56 because each file is a different shape.
   
   Nothing here restores the carousel. A carousel is the wrong control for a
   list nobody needs to page through — it hides most of the list and moves while
   being read. Equal cells show all of it at once, which is what a wall of
   partner marks is for.
   -------------------------------------------------------------------------- */

.about-us-carousel .gallery-carousel,
.gallery-container.gallery-carousel {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    align-items: center;
    justify-items: center;
    gap: var(--v2-s4) var(--v2-s3);
}

/* The clone hangs the strip off the page's own column by a margin of its own.
   Equal cells only read as a grid if the grid starts where everything above it
   starts. */
.about-us-carousel {
    margin-inline: 0;
    padding-inline: 0;
}

.about-us-carousel .gallery-carousel img,
.gallery-container.gallery-carousel img {
    /* By height, never by width: these files run from 152x67 to 406x312, and
       matching their widths prints one at four times the presence of another
       for no reason but its export settings. */
    height: 46px;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    /* One ink, full strength. Thirteen brand palettes at once would be the
       loudest thing on a page about the company, so the colour goes — but the
       fade goes with it: these files are already light grey artwork, and
       greyscale at 75% left half the row invisible on paper. Removing the
       colour is what makes a logo wall quiet; dimming it is what makes it
       unreadable. */
    filter: grayscale(1);
    opacity: 1;
    transition: opacity 180ms var(--v2-ease), filter 180ms var(--v2-ease);
}

.about-us-carousel .gallery-carousel img:hover,
.gallery-container.gallery-carousel img:hover {
    filter: none;
    opacity: 1;
}

/* No pointer, no hover: on a phone the colour never returns, so the greyscale
   is the only state these ever have. That is fine — it is the state they were
   designed to be read in here. */
@media (hover: none) {
    .about-us-carousel .gallery-carousel img,
    .gallery-container.gallery-carousel img {
        filter: grayscale(1);
    }
}

/* The carousel's own arrows and dots, which point at a track that no longer
   moves. */
.about-us-carousel .slick-arrow,
.about-us-carousel .slick-dots,
.gallery-carousel .slick-arrow,
.gallery-carousel .slick-dots {
    display: none;
}

/* --------------------------------------------------------------------------
   "Some cases" — the clone's carousel of related projects

   Marked up as a slick carousel and never initialised in this theme, so it
   renders as its raw list: on /industries/automotive that is 35 projects, each
   taking the full 1170 column with a small picture floating at the left and its
   title 200 pixels below. Measured: 15,400px of one block, on a page 32,932px
   tall. A reader looking for the paragraph after it scrolls through thirty-three
   screens of thumbnails.

   A grid of cards shows the same 35 projects in about a quarter of the height
   and, unlike the carousel it replaces, shows all of them at once. Nothing is
   hidden — hiding a page's content in CSS is how a site quietly stops saying
   what it says.
   -------------------------------------------------------------------------- */

.view-some-cases .view-content {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: var(--v2-s4) var(--v2-s3);
}

/* --------------------------------------------------------------------------
   Current openings
   --------------------------------------------------------------------------

   The vacancies arrive from the crawl already carrying the three things a job
   row wants — a title, a unit in brackets after it, and a location underneath —
   and no styling at all, so they printed as five small links with two stray
   lines under each. What they need is not a card: it is the same ruled row the
   rest of this theme uses for a list of anything, so the careers page reads as
   part of the same document as the services index.

   The unit is the parenthesis the view leaves loose in the title div, which is
   why the rule below colours the whole line and lets the anchor inside take the
   ink back: there is no element around the unit to select. */
/* A band that was built around the clone's enquiry form, after the form has
   been hidden.

   The stylesheet removes those forms everywhere — they post to a dead path —
   and the theme prints its own band underneath instead. What it cannot remove
   is the room the form was given: these bands carry an inline padding-bottom
   of 5% and an inner margin of 40px, sized for a six-field form, and with two
   lines of text left in them they read as a section that failed to load. The
   careers page is the clearest case: 288px of band around 116px of writing.

   Overridden with !important because the padding is an inline style, which is
   the one thing in this markup a stylesheet cannot otherwise outrank, and
   scoped by :has() to the bands that actually lost a form. */
.v2-imported .container-fluid:has(.block-webform) {
    padding-bottom: var(--v2-s4) !important;
    padding-top: var(--v2-s4) !important;
}

.v2-imported .container-fluid:has(.block-webform) > .container-1170 {
    margin-block: 0 !important;
}

/* --------------------------------------------------------------------------
   The register bar's labels, withdrawn
   --------------------------------------------------------------------------

   Every section of this theme opened with a double slash, a two-digit index and
   a small-caps label — "// 01  INDUSTRIES" — and every imported page opened
   with the same bar carrying the area its URL sits in. It is the device that
   made the composed sections and the 165 imported bodies read as one document.

   It is also nine words of furniture in front of every heading on the site, and
   the decision here is to take the words away and keep the rule they sat on: a
   hairline running to the right margin still separates one section from the
   next, without naming it twice — once in the label and again in the heading
   directly under it.

   Hidden rather than cut out of eight partials, because the numbering is
   computed in PHP and threaded through every section; putting the labels back
   is deleting this block, and that is worth more than the dead markup costs. */
/* The whole bar, not just its words. Hiding the index and the label left the
   double slash — which is drawn on the bar itself as a ::before, not on either
   of them — floating above a hairline with nothing between them, which reads
   as a section header that failed to load rather than as a rule.

   Safe to hide whole: .v2-head is used in sixteen templates and in every one of
   them it holds exactly three things, all of them this device — the index, the
   eyebrow and the rule. No heading, no content, nothing a reader needs. The
   space it was giving is already the section's own padding. */
.v2-head {
    display: none;
}

/* The masthead's own copy of the same device — same double slash, same
   hairline, a different class, which is why it survived the rule above. It
   goes with them: the label under it read "Engineering services" directly over
   a headline that already says what the engineering is for. */
.v2-hero__meta {
    display: none;
}

.v2-imported .view-careers .views-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 4px var(--v2-s3);
    padding-block: var(--v2-s3);
    border-top: 1px solid var(--v2-rule);
    color: var(--v2-ink-mute);
    font-size: 0.9375rem;
}

.v2-imported .view-careers .views-row:last-child {
    border-bottom: 1px solid var(--v2-rule);
}

.v2-imported .view-careers .views-row .title {
    /* The heading face, because this line is the reason the section exists. */
    font-family: var(--v2-head-font);
    font-size: clamp(1.0625rem, 0.95rem + 0.4vw, 1.25rem);
    font-weight: 600;
    line-height: 1.3;
    color: var(--v2-ink-mute);
}

.v2-imported .view-careers .views-row .title a {
    color: var(--v2-ink);
    text-decoration: none;
    /* The whole row is the target, which is what a list of links should be:
       the anchor is stretched over it rather than the row being wrapped in a
       second anchor the markup does not have. */
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 0 1px;
    transition: background-size 180ms var(--v2-ease);
}

.v2-imported .view-careers .views-row .title a:hover,
.v2-imported .view-careers .views-row .title a:focus-visible {
    background-size: 100% 1px;
}

.v2-imported .view-careers .views-row .loc-town {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    /* Tabular so a column of place names lines up on its right edge with the
       mono the rest of the theme labels things in. */
    font-family: var(--v2-sans);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    white-space: nowrap;
}

@media (max-width: 599px) {
    /* Two columns of very different lengths do not survive a phone: the place
       name drops under the title rather than squeezing it. */
    .v2-imported .view-careers .views-row {
        grid-template-columns: 1fr;
    }

    .v2-imported .view-careers .views-row .loc-town {
        grid-column: 1;
        grid-row: auto;
        justify-self: start;
    }
}

.view-some-cases .views-row {
    display: flex;
    flex-direction: column;
    gap: var(--v2-s1);
    /* Each card is its own item in the grid; the clone's float and width rules
       were written for a track that slid sideways. */
    float: none;
    width: auto;
    margin: 0;
    padding: 0;
}

/* The picture, at one size for every card. The source files are all 337x346,
   so a fixed height with cover gives a straight top line across the row — the
   thing that most makes a grid read as a grid. */
.view-some-cases .views-field-field-image-portfolio {
    margin: 0;
}

.view-some-cases .views-field-field-image-portfolio img {
    display: block;
    width: 100%;
    height: 180px;
    object-fit: cover;
    background: var(--v2-paper-sunk, rgba(0, 0, 0, 0.04));
}

.view-some-cases .views-field-title a {
    font-family: var(--v2-head-font);
    font-weight: 600;
    line-height: 1.3;
}

/* The market, service and technology lines under the title.
   
   Supporting text, not headings. Only the market line was already quiet; the
   other two printed at the title's own size and colour, so each card read as
   four headings stacked. The class names are the clone's own and are spelled
   the way its editor typed them — "clasify" included. */
.view-some-cases .views-field-field-market-type,
.view-some-cases .views-field-field-clasify-portfolio-serv,
.view-some-cases .views-field-field-clasify-portfolio-tech {
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--v2-ink-mute);
}

/* An empty field still takes its line height, and the technology line is empty
   on most of these cards — which is why the bottoms of the cards did not agree
   with each other. */
.view-some-cases .views-field:empty {
    display: none;
}

/* Arrows and dots for a track that no longer moves. */
.view-some-cases .slick-arrow,
.view-some-cases .slick-dots {
    display: none;
}

/* --------------------------------------------------------------------------
   The clone's tab blocks, read as sections

   `.tabs-component` and `.content-tab-1` are a tab widget on 74 and 33 pages.
   Their script is not in this theme, so every pane is open at once — and the
   wrapper carries a full-bleed photograph as its background, which the panes
   were never meant to sit on because only one of them ever showed and it had
   the theme's own panel behind it. What renders now is four sets of dark body
   text laid directly over a dark photograph. Not "slightly off": unreadable.

   Restoring the widget is the wrong repair. Tabs hide three quarters of a page
   from a reader and all of it from a printer, and this content is a list of
   what a service includes — it wants to be read straight through. So the panes
   become sections, one after another, and the photograph goes.

   The tab strip goes with it: measured, every label repeats the heading of the
   pane it points at, so keeping it would print each title twice.
   -------------------------------------------------------------------------- */

/* !important, and it is earned: the photograph is set in a style attribute on
   the element itself — `background-image: url(...)` written into the markup by
   the clone — and nothing in a stylesheet outranks that. This is the one lever
   that removes it without editing 74 pages of stored HTML. */
.tabs-component,
.content-tab-1 {
    background-image: none !important;
    background-color: transparent !important;
}

.tabs-component > .nav-tabs,
.content-tab-1 > .nav-tabs,
.tabs-component .tab-bg,
.content-tab-1 .tab-bg {
    display: none;
}

/* The block sits outside the page's column — it is a direct child of the body
   field, not of a container — so with the full-bleed photograph gone its text
   ran from edge to edge.
   
   Given the same box as `.container-1170` rather than the shell's, because
   that is the column the imported paragraphs above and below it stand in.
   Measured: 128 with this, 58 with the shell's gutter, and the difference is
   exactly the width of the tab block being out of step with its own page. */
/* Only when the block is not already inside one.
   
   Both spellings exist in the imported markup: on some pages the widget is a
   direct child of the body field and has to carry the column itself; on others
   it already sits inside a `.container-1170`, and giving it the column again
   indented its text by another 57px — measured at 185 against the page's 128.
   The direct-child selector is the difference between the two cases. */
.field-item > .tabs-component,
.field-item > .content-tab-1,
.field-items > .tabs-component,
.field-items > .content-tab-1 {
    width: 100%;
    max-width: calc(1170px + var(--v2-gutter) * 2);
    margin-inline: auto;
    padding-inline: var(--v2-gutter);
}

/* Panes: stacked, in the flow, each separated by the theme's own hairline
   rather than by the box the widget used to draw. */
.tabs-component .tab-pane,
.content-tab-1 .tab-pane {
    display: block;
    opacity: 1;
    position: static;
    padding-block: var(--v2-s3);
}

.tabs-component .tab-pane + .tab-pane,
.content-tab-1 .tab-pane + .tab-pane {
    border-top: 1px solid var(--v2-rule);
}

/* --------------------------------------------------------------------------
   The imported page banner

   Two things measured wrong on /company/careers and on every page built the
   same way.

   The picture is 1200px wide and was printing at exactly that, so on a 1440
   screen it stopped 240px short of the right edge and left a white strip beside
   a full-bleed band. The file is a banner; it should cover.

   And the caption over it — an <h1>, the page's own title — computed to 13.6px.
   The clone sized it in a rule this theme does not load, so it fell back to
   something smaller than the body text underneath it, sitting in the middle of
   a photograph. A heading over an image needs to be a heading and it needs a
   ground: white type on a bright sky is unreadable at any size.
   -------------------------------------------------------------------------- */

.baner,
.baner-big {
    position: relative;
    width: 100%;
}

.baner img,
.baner-big img {
    display: block;
    width: 100%;
    height: clamp(220px, 34vw, 480px);
    object-fit: cover;
}

/* The caption sits over the picture and needs its own darkness to be read
   against whatever the photograph is doing behind it.
   
   Stretched over the whole banner rather than left as the 114px strip it was
   positioned into: a gradient that only covers the middle third reads as a bar
   somebody left there, and the caption then sits on a hard edge. */
.baner img + .baner-text,
.baner-big img + .baner-text {
    inset: 0;
    height: auto;
    display: flex;
    align-items: center;
    background: linear-gradient(90deg, rgba(17, 28, 43, 0.85) 0%, rgba(17, 28, 43, 0.6) 50%, rgba(17, 28, 43, 0.15) 100%);
    color: #fff;
}

.baner img + .baner-text > .container-1170,
.baner-big img + .baner-text > .container-1170 {
    width: 100%;
}

/* !important, twice earned. The heading carries `font-size: 80%` in a style
   attribute and holds a <b> with another `font-size: 80%` inside it, so the
   page's own title computed to 13.6px — smaller than the body text below the
   picture. The typeface is written in there too, as Verdana, which appears
   nowhere else on this site. Neither can be reached from a stylesheet without
   this, and the alternative is editing the stored HTML of every page that has
   a banner. */
.baner img + .baner-text :where(h1, h2, h3),
.baner-big img + .baner-text :where(h1, h2, h3),
.baner img + .baner-text :where(h1, h2, h3) b,
.baner-big img + .baner-text :where(h1, h2, h3) b {
    font-family: var(--v2-head-font) !important;
    font-size: clamp(1.75rem, 1rem + 2.4vw, 3rem) !important;
    font-weight: 700 !important;
    line-height: 1.1 !important;
    color: #fff !important;
    margin: 0;
}

/* The banner title, when it was never written as a heading.
 *
 * 110 of the 262 pages put it in a `<p>` — /services/embedded is one, and its
 * "Embedded Software" printed at 17px in #444d59 over a photograph of a
 * circuit board, which is to say it did not print at all. 75 pages use a real
 * heading and get the rule above; these got the prose rule instead, because
 * that is all a paragraph is until something says otherwise.
 *
 * `:not(:has(h1, h2, h3))` is what keeps the two apart. On the 51 pages that
 * have both, the paragraph is a standfirst under the title and must stay the
 * size it is — only a banner with no heading at all promotes its first
 * paragraph.
 *
 * `:first-of-type`, because a banner that runs to two paragraphs means the
 * second one as a sentence.
 */
.baner img + .baner-text:not(:has(h1, h2, h3)) :where(p:first-of-type),
.baner-big img + .baner-text:not(:has(h1, h2, h3)) :where(p:first-of-type) {
    font-family: var(--v2-head-font) !important;
    font-size: clamp(1.75rem, 1rem + 2.4vw, 3rem) !important;
    font-weight: 700 !important;
    line-height: 1.1 !important;
    color: #fff !important;
    margin: 0;
}

/* And the rest of it. The band carries a dark gradient, so everything on it is
   light — the standfirsts on those 51 pages were reading in the body colour,
   which is #444d59 on near-black. */
.baner img + .baner-text :where(p, li, span, strong, b),
.baner-big img + .baner-text :where(p, li, span, strong, b) {
    color: #fff;
}

/* Every rule in this block is written `img + .baner-text`, not `.baner-text`.
 *
 * The class alone is not a promise of anything: the clone reuses it further
 * down a good many pages as a plain wrapper, with no photograph above it and so
 * no gradient behind it. Those copies sit on paper, and a rule that says "the
 * ground here is dark, print white" turns them into white text on white — which
 * is what happened to the list under "Book 24h Expert Call" on the broadcasting
 * pages, and to the button itself, which the outline variant had been rendering
 * invisible since long before.
 *
 * The `img +` is the whole test. A banner is a picture with words over it; a
 * `.baner-text` with no picture before it is a div. */

/* The clone centres this text with a class of its own. Left, with everything
   else on the page. */
.baner .baner-text .rtecenter,
.baner-big .baner-text .rtecenter {
    text-align: left;
}

.baner img + .baner-text a,
.baner-big img + .baner-text a {
    color: #fff;
}

/* --------------------------------------------------------------------------
   The imported pages' own buttons

   612 calls to action across 137 pages, written as .btn-blue, .btn-red and
   .btn-white by the clone. The article bodies already get the theme's button;
   the imported pages never did, so every one of those 612 rendered as a line of
   plain text — including "Search Our Openings" sitting on the careers banner
   looking like a caption.

   Same treatment as the article bodies, so the two page types do not disagree
   about what a button is. Two classes deep so it outranks the prose link rule
   and its underline: a button with a hairline under the label is neither.
   -------------------------------------------------------------------------- */

.v2-imported .btn-blue,
.v2-imported .btn-red,
.v2-imported .btn-white {
    /* `!important` on the display, and only on it. A good many of these carry
       `style="width:230px; display:inline-block;"` — the clone sized them by
       hand — and an inline declaration outranks any stylesheet. The result was
       a 48px box with its label sitting on the top edge, because inline-block
       ignores `align-items` and the line height is 1: the four domain buttons
       under "More about our domain-specific software development" on
       /services/embedded all read as text stuck to the ceiling of a red slab.

       `justify-content` for the same reason: a button given a fixed 230px has
       room to spare, and the label belongs in the middle of it. */
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: 0 var(--v2-s3);
    background: var(--v2-accent);
    border: 1px solid var(--v2-accent);
    color: #fff;
    font: 600 0.9375rem/1 var(--v2-sans);
    margin: var(--v2-s2) 0;
    text-decoration: none;
}

.v2-imported .btn-blue:hover,
.v2-imported .btn-red:hover,
.v2-imported .btn-white:hover {
    background: var(--v2-accent-deep);
    border-color: var(--v2-accent-deep);
    color: #fff;
}

/* On the banner the ground is already dark and already carries the accent's
   weight, so the button is outlined rather than filled — otherwise the red
   block competes with the page title beside it. */
.baner img + .baner-text .btn-blue,
.baner-big img + .baner-text .btn-blue,
.baner img + .baner-text .btn-red,
.baner-big img + .baner-text .btn-red {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.65);
}

.baner img + .baner-text .btn-blue:hover,
.baner-big img + .baner-text .btn-blue:hover {
    background: #fff;
    border-color: #fff;
    color: #131a24;
}

/* --------------------------------------------------------------------------
   Testimonials in the imported pages

   The clone prints each one as a quote, then a 250px round portrait on a line
   of its own, then the name under that — about 480px of page for four lines of
   text and one small face. Three of them on /industries/automotive came to
   3,476px, and the portrait is the largest thing in the block while being the
   least of what it says.

   A quote is text with an attribution. The face belongs beside the name, at the
   size of a name.
   -------------------------------------------------------------------------- */

.constructor-testimonial .author {
    display: flex;
    align-items: center;
    gap: var(--v2-s2);
}

.constructor-testimonial .author-image {
    flex: none;
    width: 64px;
}

.constructor-testimonial .author-image img {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    object-fit: cover;
}

/* The folded tail of a long project grid, and the control that unfolds it.
   Both only exist once the script has run. */
.view-some-cases .view-content > .is-folded {
    display: none;
}

.v2-fold__more {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    margin-top: var(--v2-s3);
    padding: 0 var(--v2-s3);
    border: 1px solid var(--v2-rule);
    background: none;
    font: 500 0.875rem/1 var(--v2-sans);
    color: var(--v2-ink);
    cursor: pointer;
    transition: border-color 160ms var(--v2-ease), color 160ms var(--v2-ease),
        background-color 160ms var(--v2-ease);
}

.v2-fold__more:hover,
.v2-fold__more:focus-visible {
    border-color: var(--v2-accent);
    color: var(--v2-accent);
    background: color-mix(in srgb, var(--v2-accent) 7%, transparent);
}

/* The accordion the script builds out of an imported tab block. None of this
   applies until it has run; without it every pane is open, which is what the
   page did before and still does with no JavaScript. */
/* Doubled class, which is the cheapest way to weigh (0,2,0) and outrank
   `.v2-prose--article button` (0,1,1). Inside an article every `<button>` is a
   call to action — accent fill, white label — and an accordion toggle is not
   one: it is a heading you can press. */
.v2-accordion__toggle.v2-accordion__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--v2-s2);
    width: 100%;
    padding: var(--v2-s2) 0;
    border: 0;
    background: none;
    text-align: left;
    cursor: pointer;
    color: inherit;
    font: inherit;
}

/* A flex item will not shrink below the widest word it contains unless it is
   told it may: `min-width` starts at `auto` there, not at zero. The toggle
   holds whatever heading block the imported page gave it, and on the sensor
   page that block sat 95px wider than the button around it and took the
   document with it. */
.v2-accordion__toggle > * {
    min-width: 0;
}

/* The mark, drawn rather than typed: a glyph would sit on the text baseline and
   need a different nudge at every heading size on the page. */
.v2-accordion__toggle::after {
    content: '';
    flex: none;
    width: 14px;
    height: 14px;
    border-right: 2px solid var(--v2-ink-mute);
    border-bottom: 2px solid var(--v2-ink-mute);
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform 180ms var(--v2-ease);
}

.v2-accordion__toggle[aria-expanded='true']::after {
    transform: rotate(-135deg) translate(-4px, -4px);
}

.v2-accordion__toggle:hover::after,
.v2-accordion__toggle:focus-visible::after {
    border-color: var(--v2-accent);
}

.v2-accordion__body[hidden] {
    display: none;
}

.v2-accordion__body {
    padding-bottom: var(--v2-s3);
}

/* --------------------------------------------------------------------------
   Two measured typographic faults in the imported pages
   -------------------------------------------------------------------------- */

/* The scale skipped a step. A section heading prints at 44px and its own
   sub-headings at 20 — three pixels above the 17px body text — so a page that
   has three levels of structure reads as though it has two, and the third one
   reads as bold body copy. 28px is the step the rest of the scale implies.
   
   Not applied to card titles or to anything inside a banner: those are their
   own components and were measured at the size they meant to be. `.v2-office__name`
   joins that list for the same reason and one more: the offices block renders
   the theme's own component inside an imported body, so this rule was reaching
   into it and printing the office names at 28px on /company against 17px on
   /contacts. The two selectors are both (0,2,0) — `:not()` takes the
   specificity of its most specific argument — and this one is further down the
   file, so it won on order alone. An exclusion is the honest fix; bumping the
   component's specificity to outrun it would just move the race. */
.v2-imported :where(h3):not(.views-row *, .baner-text *, .title *, .v2-fold__more *, .v2-office__name) {
    font-size: clamp(1.375rem, 1.15rem + 0.9vw, 1.75rem);
    line-height: 1.25;
}

.v2-imported :where(h4):not(.views-row *, .baner-text *, .title *) {
    font-size: clamp(1.125rem, 1.05rem + 0.35vw, 1.3125rem);
    line-height: 1.3;
}

/* Line length. The 1170 column is right for a layout and wrong for a sentence:
   measured at 138 characters on the full-width paragraphs, against the 45–85
   that a reader can return from without losing the line. The cap is on the
   text, not on the column, so images, grids and two-column rows keep the full
   width they were designed for. */
.v2-imported :where(p, li):not(.views-row *, .baner-text *, .v2-fold__more *) {
    max-width: 78ch;
}

/* A centred paragraph is centred inside its column, and a max-width would pin
   it to the left of one. The clone marks those with its own class. */
.v2-imported :where(p, li).rtecenter {
    max-width: none;
}

/* A column that is not in a row.
 *
 * `.col-md-3` sitting as a direct child of `.container-1170`, with
 * `margin-left: 30px` in a style attribute. The compatibility layer gives every
 * column `width: 100%` at the narrow end — correct inside a row, where that is
 * what stacks the columns — and 100% plus a 30px margin is 30px more than the
 * container has. At 375 that pushed the page to 410 and gave the whole document
 * a horizontal scrollbar.
 *
 * A column outside a row is not participating in a grid; nothing is lining up
 * with it and its margin is doing no work that the container's own padding is
 * not already doing. Scoped to direct children of the two content containers,
 * so a real column inside a real row keeps its gutter untouched. */
.v2-imported .container > [class*="col-"],
.v2-imported .container-1170 > [class*="col-"] {
    margin-inline: 0 !important;
}

/* 14px rather than 12px, in all three places this shape exists.
 *
 * Asked for on /company, applied to `.v2-crumbs` and the article layer at the
 * same time. A crumb line that is one size on an imported page and another on
 * a post is exactly the inconsistency this treatment was copied across to
 * avoid, and the request is a reading complaint rather than a page-specific
 * one: 12px spaced capitals is small for a line somebody is meant to navigate
 * by, and it is the smallest type on the page. */

/* --------------------------------------------------------------------------
   The crumb line on an imported page
   --------------------------------------------------------------------------

   194 imported pages open with `.mod-breadcrumbs > .breadcrumb` — the clone's
   Bootstrap breadcrumb, which without its stylesheet is a bare line of body
   text reading "Home / About / Software and Hardware Product Dev Company",
   the same size and colour as the paragraph under it and carrying no signal
   that it is navigation at all.

   The treatment below is the article layer's, repeated for `.v2-imported`
   rather than invented: small, spaced capitals on a hairline, the accent only
   under a pointer. That is not a shortcut — an article and a page reached from
   the same menu must not have visibly different crumb lines, and there are
   exactly three places this shape now exists (`.v2-crumbs` for composed pages,
   `.v2-prose--article` for posts, here for imported pages).

   The last segment was a bare text node — the page's own name, with no element
   around it to hook — so it took the container's grey and the line came out
   with all three steps weighing the same. A trail that does not say which step
   you are standing on is decoration.

   `v2-collapse.js` now wraps it in `.v2-crumbs__here`, the same class the
   composed pages use, and the hierarchy is the ordinary one: the ancestors are
   links and read as links, the page you are on is the darkest thing on the
   line and is not a link because you are already there. -------------------- */

.v2-imported .mod-breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--v2-s1) var(--v2-s2);
    /* Air above it. On the 110 pages that open with a full-bleed banner this
       line begins at the exact pixel the photograph ends, so it read as a
       caption stuck to the bottom edge of the picture rather than as the first
       thing on the page below it. */
    padding-top: var(--v2-s3);
    padding-bottom: var(--v2-s3);
    margin-bottom: var(--v2-s4);
    border-bottom: 1px solid var(--v2-rule);
    font-family: var(--v2-sans);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    line-height: 1.5;
    color: var(--v2-ink-mute);
}

.v2-imported .mod-breadcrumbs .breadcrumb {
    /* Bootstrap's own margin and its grey pill, both of which this line has
       been inheriting from nothing at all. Stated so the rule survives a
       stylesheet that does define them. */
    margin: 0;
    padding: 0;
    background: none;
    border-radius: 0;
}

.v2-imported .mod-breadcrumbs a {
    text-transform: uppercase;
    color: var(--v2-ink-mute);
    /* Declared transparent rather than absent, so the hairline appears by
       changing colour instead of by appearing — otherwise the line of type
       shifts a pixel the moment a pointer touches it. */
    border-bottom: 1px solid transparent;
    padding-bottom: 1px;
    transition: color 180ms var(--v2-ease), border-color 180ms var(--v2-ease);
}

/* Where you are. Sentence case against the links' capitals, because it is a
   name and not a label, and in the page's own ink so the eye lands on it
   first. */
.v2-imported .mod-breadcrumbs .v2-crumbs__here {
    text-transform: none;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--v2-ink);
}

.v2-imported .mod-breadcrumbs a:hover,
.v2-imported .mod-breadcrumbs a:focus-visible {
    color: var(--v2-accent);
    border-bottom-color: var(--v2-accent);
}

/* The middle segments the clone renders as `<span class="nolink" tabindex="0">`
   — a heading in the path that has no page of its own. It is not a link and
   must not look like one; capitals put it in the same register as the links
   either side without offering to be clicked. The tabindex is the clone's, and
   it puts a focus stop on a span that does nothing, so the outline is left
   alone rather than removed: a focus ring on a dead stop is confusing, a
   removed one is a keyboard trap you cannot see. */
.v2-imported .mod-breadcrumbs .nolink {
    text-transform: uppercase;
    color: var(--v2-ink-mute);
}

/* The other half of a matched pair.
 *
 * The rule further up forces `.container-1170` to the theme's gutter, because
 * one container in the corpus carried `padding: 0 25px` in a style attribute
 * and pulled its heading 33px off the page's grid line. What that rule did not
 * account for is that the same section also carries `margin: 0 -25px` on the
 * row inside it: the clone wrote the two as a pair, the padding insetting the
 * heading and the negative margin pulling the row back out to the container's
 * edge. Overriding one half and leaving the other is worse than overriding
 * neither — the row then hangs 5px past the viewport on both sides, which at
 * 375 is a horizontal scrollbar on a page that had none. Found by measuring,
 * not by reading: the desktop looked right.
 *
 * So the inline margin goes too, and the stylesheet's own compensation takes
 * over. The second selector below repeats the `:has()` test the general row
 * rule uses, so a row of columns still gets its gutter cancelled and a row of
 * something else still does not — the distinction that keeps 375 from
 * overflowing in the first place. It has to be restated here rather than
 * inherited because only an !important declaration can outrank a style
 * attribute, and !important on the general rule would apply it to rows the test
 * deliberately excludes. */
.v2-imported .container > .row,
.v2-imported .container-1170 > .row {
    margin-inline: 0 !important;
}

.v2-imported .container > .row:has(> [class*="col-"]):not(:has(> :not([class*="col-"]))),
.v2-imported .container-1170 > .row:has(> [class*="col-"]):not(:has(> :not([class*="col-"]))) {
    margin-inline: calc(var(--v2-gap) / -2) !important;
}

/* --------------------------------------------------------------------------
   The "solutions for business" card
   --------------------------------------------------------------------------

   125 imported pages carry this component and the compatibility layer had no
   rule for a single part of it. Without the clone's stylesheet the icon fell
   back to `.img-responsive`, which is `width: 100%` — so a 512px source became
   a 371px picture inside a 400px column, and a card whose content is one line
   of title and three of text stood 507px tall with a piece of clipart
   occupying three quarters of it.

   Rebuilt as the theme builds a card, and the reference is the news grid: a
   hairline, a title, type under it. No box, no radius, no shadow.

   Two different things are wrong with the pictures, so there are two rules.
   Measured across the corpus rather than guessed: of the 125 pages, 120 give
   each card its own icon — those say something, and they only needed a size.
   Five repeat one image down every card in the row, and on those it is a tick:
   the same tick, three or six times, next to titles that already say what they
   say. That is not an icon, it is a texture, and it is the exact thing this
   stylesheet's own index-list comment refuses to print. ---------------------- */

.v2-imported .solutions-for-business-card {
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule);
    height: 100%;
}

/* The title line. The icon and the words are siblings in a flex row rather
   than the icon being a block above them, so a card with no picture and a card
   with one start their title at the same height across the row. */
.v2-imported .solution-title {
    display: flex;
    align-items: center;
    gap: var(--v2-s2);
    margin-bottom: var(--v2-s2);
}

.v2-imported .solution-title > div {
    font-family: var(--v2-head-font);
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
}

/* The 120 pages whose icons differ.
 *
 * Capped by height and given `flex: none` so a wide mark cannot push the title
 * out of the card. width:auto is there to beat `.img-responsive`. */
.v2-imported .solution-title img {
    flex: none;
    width: auto !important;
    height: auto !important;
    max-height: 32px;
    max-width: 48px;
    object-fit: contain;
}

/* The five pages that repeat a tick.
 *
 * Matched on the filename, which is the only thing that separates them: the
 * markup is identical to the 120 that keep their icons. Narrow and explicit on
 * purpose — this removes four known files and nothing it has not been pointed
 * at. The alt text is not lost with it; it repeats the title word for word on
 * every one of these cards, so a screen reader was already reading each of
 * them twice. */
.v2-imported .solution-title img[src*="checkmark"],
.v2-imported .solution-title img[src*="check-mark"],
.v2-imported .solution-title img[src*="checked"] {
    display: none;
}

/* …and the tick comes back as the theme's own, at a fifteenth of the size.
 *
 * Drawn rather than fetched: two straight strokes is geometry this file can
 * state exactly, and it inherits the accent instead of arriving as somebody
 * else's PNG in somebody else's blue. Only on the cards the rule above
 * emptied, so the 120 pages with real icons never see it. */
.v2-imported .solution-title:has(> img[src*="checkmark"])::before,
.v2-imported .solution-title:has(> img[src*="check-mark"])::before,
.v2-imported .solution-title:has(> img[src*="checked"])::before {
    content: "";
    flex: none;
    width: 18px;
    height: 18px;
    background-color: var(--v2-accent);
    -webkit-mask: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'><path%20d='M4%2013l6%206L20%205'%20fill='none'%20stroke='black'%20stroke-width='3'%20stroke-linecap='square'/></svg>") center / contain no-repeat;
    mask: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'><path%20d='M4%2013l6%206L20%205'%20fill='none'%20stroke='black'%20stroke-width='3'%20stroke-linecap='square'/></svg>") center / contain no-repeat;
}

.v2-imported .solutions-for-business-card p {
    margin: 0;
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--v2-ink-soft);
}

/* The clone's enquiry form, hidden.
 *
 * 192 imported pages end with it and on every one it is dead: `action="/antibot"`
 * is a Drupal path that was never a route here, and the markup carries no CSRF
 * token, so even repointed it would be refused. It is not a styling problem —
 * a visitor fills it in, presses the button and is handed a 404 while nothing
 * is written anywhere — and leaving it on the page while the theme prints a
 * working one below would put two "Tell us about your project" forms on the
 * same screen, one of which quietly loses the enquiry.
 *
 * Three selectors because the clone wrapped it three ways: the form block
 * itself, the `.get-quote` layout around it on 184 pages, and the `.tell-us`
 * band on the two that have one. The view keys its replacement off the same
 * class names, so a page that hides one always gets the other.
 *
 * Hidden rather than deleted from the database: the stored markup is what
 * SITE_THEME=legacy still renders, and that theme's copy of this form posts to
 * the right endpoint. */
.v2-imported .block-webform,
.v2-imported .get-quote,
.v2-imported .get-quote-cards,
.v2-imported .tell-us {
    display: none;
}

/* .get-quote-cards is the fourth wrapper and it was missed, because a class
   selector matches a whole token: `.get-quote` does not match
   `get-quote-cards`. It is the row of six proof badges the clone printed under
   its form, it is on 127 pages, and the theme prints its own row of the same
   badges from the panel row immediately below it.
   
   So every one of those pages was printing both — and after the figures were
   corrected in the panel they no longer agreed: "8 years of engineering
   expertise" from the panel, directly under "22 years" from the crawl, with
   "500+ projects" and "Featured at IBC, Embedded World, MWC" beside it, both of
   which had already been struck from the panel copy.
   
   Hidden rather than rewritten in 127 records and 43 translations: the two rows
   say the same thing, one of them is editable and one of them is a fossil, and
   the fossil is the one to stop printing. */

/* --------------------------------------------------------------------------
   The testimonial card
   --------------------------------------------------------------------------

   22 pages carry `.constructor-testimonial`, and only its `.author` row had a
   rule. Everything above that row was unstyled, which on
   /company/testimonials-clients left twenty quotes in two columns with no
   two of them the same shape: the client logos are set with
   `style="width: 150px"` in the markup, so every one came out 150 wide and
   anywhere from 24 to 95 tall, and the attribution floated wherever the quote
   above it happened to end.

   The columns were already equal height — the compatibility layer's `.row` is
   a flexbox and stretches them. What was ragged was the inside. So the card
   becomes a column with the quote at the top and the attribution pushed to the
   bottom, and the two cards in a row end on the same line. ------------------ */

.v2-imported .constructor-testimonial {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule);
}

.v2-imported .testemonial-text {
    /* The clone's spelling. Left as found: renaming it here would only mean
       this rule stopped matching the markup. */
    flex: 1 1 auto;
}

.v2-imported .testimonial-title {
    margin-bottom: var(--v2-s2);
    font-family: var(--v2-head-font);
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
}

.v2-imported .testemonial-text p {
    margin: 0 0 var(--v2-s2);
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--v2-ink-soft);
}

/* The client's logo.
 *
 * Capped by height, like every other wall of logos on this site, and the
 * !important is unavoidable: the width and the height are both in the tag's
 * own style attribute. Height is the dimension that makes a set of marks look
 * like a set — constrain the width instead and a wordmark ends up a third the
 * size of a square badge standing next to it.
 *
 * Muted to grey, coloured under the pointer. Twenty client logos in twenty
 * brand colours is twenty accents on a page that has one. */
.v2-imported .testemonial-text p > img {
    width: auto !important;
    height: auto !important;
    max-height: 30px;
    max-width: 150px;
    object-fit: contain;
    margin-top: var(--v2-s2);
    filter: grayscale(1);
    opacity: 0.75;
    transition: filter 0.2s var(--v2-ease), opacity 0.2s var(--v2-ease);
}

.v2-imported .constructor-testimonial:hover .testemonial-text p > img {
    filter: none;
    opacity: 1;
}

/* The attribution, on a hairline at the foot of the card. */
.v2-imported .constructor-testimonial .author {
    margin-top: var(--v2-s3);
    padding-top: var(--v2-s2);
    border-top: 1px solid var(--v2-rule);
}

.v2-imported .author-name {
    font-family: var(--v2-head-font);
    font-size: 0.9375rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
}

.v2-imported .author-position {
    margin-top: 2px;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--v2-ink-mute);
}

/* The clone puts an empty `<a href="…linkedin…"></a>` inside the name, styled
   in its own stylesheet as a background-image icon. Here it has no content, no
   background and no accessible name — an invisible link that a keyboard still
   stops on and a screen reader announces as "link", with nothing after it.
   Hidden, because a control with no name cannot be given one from a
   stylesheet, and a nameless stop is worse than no stop. */
.v2-imported .author-name > a:empty {
    display: none;
}

/* The avatar. 48 rather than the 64 the earlier rule set: it sits beside a
   15px name and a 13px role, and at 64 the photograph was the largest thing in
   the attribution. */
.v2-imported .constructor-testimonial .author-image,
.v2-imported .constructor-testimonial .author-image img {
    width: 48px;
    height: 48px;
}

/* --------------------------------------------------------------------------
   The imported tab strip, and the press logos under it
   -------------------------------------------------------------------------- */

/* The dead tab nav.
 *
 * 89 pages open a tab block with `<ul class="nav nav-tabs">`, whose links carry
 * `data-toggle="tab"` and do their work through Bootstrap's JavaScript — which
 * this theme does not load. So the list is a row of controls that cannot
 * control anything: clicking one jumps to an anchor and nothing else happens.
 *
 * It is also redundant twice over. v2-collapse.js rebuilds the panes as an
 * accordion with real buttons, so the navigation exists; and where that script
 * does not run, every pane prints open, so there is nothing to navigate to.
 * Whichever of the two is true, this list is a dead control above working
 * content, and on /company/press-kit it rendered as a bare bullet list of two
 * items directly under the crumb line. */
.v2-imported .nav-tabs {
    display: none;
}

/* The press logos.
 *
 * Twenty mastheads, each set to `width: 210px; height: 90px` in its own style
 * attribute, so every one arrived as a 210×90 box with its own background
 * inside it — EE Times in a red block, Robotics & Automation in a black one,
 * Hackernoon in green — on warm paper. The result reads as twenty stickers
 * rather than as one wall of coverage.
 *
 * Given the treatment the other three logo walls on this site already have:
 * capped by height, greyed, coloured under the pointer. The inline dimensions
 * have to be beaten with !important, and the selector matches exactly that
 * inline pair, which appears on this page and nowhere else in the corpus. */
.v2-imported a > img[style*="width: 210px"] {
    width: auto !important;
    height: auto !important;
    /* 44 and 0.8, after looking at 34 and 0.72 on screen: at the smaller
       numbers half the mastheads — evertiq, All About Circuits, embedded —
       were grey smudges. The partner wall settled on 56/0.78 for the same
       reason; these run a little smaller because there are twenty of them
       across four columns rather than seven. */
    max-height: 44px;
    max-width: 100%;
    object-fit: contain;
    filter: grayscale(1);
    opacity: 0.8;
    transition: filter 0.2s var(--v2-ease), opacity 0.2s var(--v2-ease);
}

.v2-imported a:hover > img[style*="width: 210px"] {
    filter: none;
    opacity: 1;
}

/* The tile the masthead sits in. Centred and given a floor, so a tall logo and
   a wide one occupy the same cell and the wall keeps its rows. */
.v2-imported [class*="col-"] > div:has(> a > img[style*="width: 210px"]) {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 72px;
}

/* --------------------------------------------------------------------------
   The icon card
   --------------------------------------------------------------------------

   52 pages build a card as `.content-title-outer > .icon-image + .content-title
   .have-icon`, and none of it had a rule. `.icon-image` holds a 512px SVG and
   `.img-responsive` is `width: 100%`, so on /ai-ml-recommendations the four
   steps of "How It Works" printed as four 512px numbered circles, one per
   screenful, and the six ticks under "Built to Integrate" at 371px each.

   `.have-icon` is the clone's own hook and it says what the layout was meant to
   be: the icon beside the title, not above it at display size. Rebuilt that way,
   in the same shape as the other card this stylesheet fixes — a hairline, a
   title, type under it. --------------------------------------------------- */

.v2-imported .content-title-outer:has(> .icon-image) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--v2-s2) var(--v2-s3);
    align-items: start;
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule);
}

/* A fixed box rather than a cap, so the titles beside them start on one line
   across a row: these files are a mix of square badges and wider marks, and
   capping the height alone leaves each icon a different width. */
.v2-imported .icon-image {
    flex: none;
    width: 40px;
    height: 40px;
}

.v2-imported .icon-image img {
    width: 100% !important;
    height: 100% !important;
    object-fit: contain;
}

.v2-imported .content-title.have-icon {
    min-width: 0;
}

/* The title. The clone wraps it in `<div><strong>…</strong></div>` rather than
   in a heading, so there is no tag to style — the weight has to be put back on
   the element that actually holds the words. */
.v2-imported .content-title.have-icon > div:first-child,
.v2-imported .content-title.have-icon > div:first-child strong {
    font-family: var(--v2-head-font);
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
}

.v2-imported .content-title.have-icon p {
    margin: var(--v2-s2) 0 0;
    line-height: 1.55;
    color: var(--v2-ink-soft);
}

/* The clone sets `font-size: 14px` on a span inside most of these paragraphs.
   Left alone would be fine; it is restated here only so the card's body text
   matches the other cards on the same page rather than being a pixel smaller
   than them by accident of which section it is in. */
.v2-imported .content-title.have-icon p span[style*="font-size"] {
    font-size: 0.9375rem !important;
}

/* --------------------------------------------------------------------------
   The text-and-picture band
   --------------------------------------------------------------------------

   96 pages wrap this band in `.container-1170-has-image` — one hyphenated
   class, not `.container-1170` plus a modifier — so the container rule never
   matched it and it had no rule of its own.

   That is why /services/hardware had two grid lines: its own headings started
   at 58 while every other heading on the site starts at 128. The wrapper sits
   inside a `.row`, the compatibility layer makes a row a flexbox, and a flex
   item with no width shrinks to its content — so the wrapper came out 1285
   wide at x=0, and the container inside it had no room left to centre in.
   `width: 100%` is the whole fix: the wrapper fills the row again and the
   container inside it lines up with every other container on the site.

   No padding here. The `.container-1170` inside already carries the gutter,
   and adding it twice would inset this band 115px while its neighbours sit at
   58. -------------------------------------------------------------------- */

.v2-imported .container-1170-has-image {
    width: 100%;
}

/* Text left, picture right — which is what `.has-image` and `.image-right-side`
   were named for.
 *
 * The clone positioned the picture into the space its text column left free.
 * Nothing here is positioned, so the two stacked: a column of text, then the
 * same picture at 1170×780 underneath it, which is most of a screen of
 * photograph doing the work of an illustration.
 *
 * `:has()` guards it, because `.has-image` is a container and a container is
 * allowed to hold other things: only a band that really does have a picture
 * beside it becomes two columns.
 *
 * One breakpoint, at 900. Below that the picture belongs under the words —
 * side by side at phone width would leave two columns too narrow to be either
 * a paragraph or a photograph. */
@media (min-width: 900px) {
    .v2-imported .has-image:has(> .image-right-side) {
        display: grid;
        /* 7/5 rather than 6/5: the left column is running text and the right
           one is a single photograph, so the words need the wider half. At 6/5
           the measure came out around 35 characters, which is a newspaper
           column, not a paragraph. */
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
        /* Column gap only, and the row gap carried by the children instead —
           see the picture's `span 99` below. A gap applies between every pair
           of rows the grid has, and the picture gives this grid 99 of them
           whether or not there is anything in them, so a shorthand `gap` here
           bought 98 gaps at 32px: 3136px of empty paper under the last row.
           That was the hole under "Explore other references". */
        column-gap: var(--v2-gap);
        row-gap: 0;
        /* Top-aligned, not centred. These text blocks run far longer than the
           picture beside them, and centring left the photograph floating in
           the middle of a column of empty paper with no edge to belong to. */
        align-items: start;
    }

    /* The row gap, put back where the empty rows cannot claim it.
     *
     * Every content child except the first, which is what a 32px row gap was
     * spacing before. Margins on a grid item do not collapse, so the spacing
     * comes out identical to the gap it replaces — the heading's own 16px
     * bottom margin still sits on top of it, as it did — and the 95 empty rows
     * the picture opens below the content get nothing, because there is no
     * child in them to carry a margin. */
    .v2-imported .has-image:has(> .image-right-side)
        > *:not(.image-right-side) ~ *:not(.image-right-side) {
        margin-top: var(--v2-gap);
    }

    /* Which child goes where, said out loud.
     *
     * Auto-placement is right only while the band holds exactly the two things
     * the rule above imagines, and most of them do. The client-quotes band on
     * the cloud-dashboard page holds four — a heading, two rows and the
     * picture — so the second one landed in the picture's column and the
     * quotations were laid out 305px wide beside an acre of empty paper.
     *
     * Naming the columns costs nothing in the two-child case, which resolves
     * to exactly what it did before, and makes the four-child case behave. */
    .v2-imported .has-image:has(> .image-right-side) > * {
        grid-column: 1;
    }

    .v2-imported .has-image:has(> .image-right-side) > .image-right-side {
        grid-column: 2;
        /* Every row, not just the first.
         *
         * Pinned to row 1 the picture still sets that row's height, and row 1
         * is the heading — so a 614px photograph beside a 135px title left 479px
         * of empty paper under the title before the content below it could
         * start. That is the hole on /services/embedded under "Collaborating
         * With Vipulse".
         *
         * `span 99` rather than `1 / -1`: these grids declare no rows, so -1
         * resolves against an explicit grid that has none and the placement is
         * dropped. 99 is simply more rows than any of these bands has; the
         * spare ones are implicit and zero-height.
         *
         * Zero-height, but not free: a row gap is charged between every pair
         * of rows, empty ones included. That is why the gap above is a column
         * gap and the rows are spaced by the children themselves. Anything
         * that puts a row gap back on this grid brings the 3136px hole back
         * with it. */
        grid-row: 1 / span 99;
        align-self: start;
    }
}

/* The picture keeps its own proportions inside whichever column it lands in.
   A fixed ratio was tried and rejected: unlike the news grid, these are one
   photograph per band rather than a row of them, so there is nothing for a
   crop to line up with and cropping only loses part of the picture. */
.v2-imported .image-right-side-inner img {
    width: 100%;
    height: auto;
}

/* --------------------------------------------------------------------------
   The listing header

   Three measured adjustments, all of them on the lead section only — the home
   page's own section headings are aligned to the numbered lists underneath them
   and must not move.
   -------------------------------------------------------------------------- */

/* The heading sat 76px in from the page edge because `.v2-intro` opens with a
   3rem gutter — the column the numbers of an index sit in. A listing has no
   numbered index under it, so that column held nothing and pushed the title
   away from the edge everything else on the page lines up with. */
@media (min-width: 768px) {
    .v2-section--lead .v2-intro {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Stacked, not side by side.

       The heading and the sentence explaining it were set on 5fr and 6fr, which
       put them at opposite ends of a 1300px line and made them read as two
       separate things — a title on the left, an unrelated remark on the right.
       Under the heading the sentence reads as what it is, the second line of
       it. This is the same conclusion the rule for the home page's sections
       reached, and the reasoning above it says so; this override had quietly
       undone it for every listing. */
    .v2-section--lead .v2-intro__title,
    .v2-section--lead .v2-intro__text {
        grid-column: 1;
    }

    /* Held to a measure: the shell is far wider than a line stays readable. */
    .v2-section--lead .v2-intro__text {
        max-width: 62ch;
    }
}

/* The rule under the heading was 64px below it — far enough that it read as the
   top of the next thing rather than the underline of this one. The filter is
   part of the header, not a separate block. */
.v2-section--lead .v2-filter {
    margin-top: var(--v2-s3);
}

/* And the rule under the tab labels: the strip was 66px tall for a line of
   12px capitals, so the border sat well clear of the word it belongs to. */
.v2-filter__tabs {
    padding-block: var(--v2-s1) 0;
}

.v2-filter__tab {
    padding-block: var(--v2-s1);
}

/* The chips follow the heading. With the title pulled out to the page edge the
   filter's own 3rem gutter left them 76px inside it, so the header read as two
   columns that disagreed about where the page starts. Same reason the gutter
   went from the heading: there is no numbered index on a listing for it to line
   up with. */
@media (min-width: 768px) {
    .v2-section--lead .v2-filter__row {
        grid-template-columns: minmax(0, 1fr);
    }

    .v2-section--lead .v2-filter__label,
    .v2-section--lead .v2-filter__set {
        grid-column: 1;
    }
}

/* The last of the listing header's spacing, all of it measured rather than
   guessed at.
   
   A little air under the heading. And then the other way: the chips and the
   rule that starts the list had 128px between them — the row's own bottom
   padding plus the list's top margin, each reasonable on its own and absurd
   together. The rule belongs to the list it opens, and it was floating in the
   middle of nothing. */
.v2-section--lead .v2-filter {
    margin-top: var(--v2-s4);
}

.v2-filter.is-tabbed .v2-filter__row {
    padding-bottom: var(--v2-s2);
}

.v2-section--lead .v2-filter + * {
    margin-top: var(--v2-s3);
}

/* --------------------------------------------------------------------------
   Imported posts that carry their own headline and hero

   Most posts get the theme's masthead: headline on the left, photograph to the
   right, both using the page. A few hundred came over from the clone with an
   <h1> and a hero image already inside the body, and for those the template
   steps aside and prints the body as it is.

   The body is a reading column — 72 characters, which is right for prose and
   wrong for everything else in it. Measured on a 1920 screen: the headline, the
   breadcrumb and a 1200px photograph were all squeezed into 734px with 58% of
   the page empty beside them.

   So the measure stays on the text and comes off the two things that are not
   text. Nothing is centred and nothing moves: the column keeps its left edge,
   and the heading and the picture simply reach further across the page they are
   already on.
   -------------------------------------------------------------------------- */
/* The test is not "is this long", it is "is this a page".
   
   These bodies are the clone's own page markup — container, row, columns — and
   a page layout inside a 72-character reading column is what put a 1200px
   photograph in a 734px box with more than half the screen empty beside it.
   Prose keeps the measure; a body that brought its own grid gets the page. */
.v2-prose--article:has(.container-1170),
.v2-prose--article:has(.container-fluid) {
    max-width: none;
    grid-column: 1 / -1;
}

/* The clone's own column, given the same box it has on an imported page so the
   two kinds of body do not disagree about where the text starts. */
.v2-prose--article .container-1170 {
    width: 100%;
    max-width: calc(1170px + var(--v2-gutter) * 2);
    margin-inline: auto;
    padding-inline: var(--v2-gutter);
}

/* And the same cap the imported pages get. Giving the body the page put the
   headline and the photograph right and the sentences wrong: 138 characters to
   a line, where a reader loses the return. The measure belongs on the text, not
   on the column — the grid, the images and the two-column rows keep the width
   they were drawn at. */
.v2-prose--article:has(.container-1170) :where(p, li):not(.rtecenter) {
    max-width: 78ch;
}

/* --------------------------------------------------------------------------
   The same compatibility layer, for post bodies that are whole clone pages

   A few hundred posts came over with their own container, rows and columns
   rather than as prose. Those rows were only laid out under `.v2-imported`, so
   inside an article body every column stacked at full width: a 3/12 author
   portrait printed at 601px, and paragraphs meant for a narrow column ran the
   width of the page.

   Repeated rather than added to the original selector list because the two
   places are matched differently — one by a class the template sets, one by
   what the body turned out to contain — and a single rule carrying both would
   be harder to read than two that each say what they are for.
   -------------------------------------------------------------------------- */
.v2-prose--article:has(.container-1170) .row {
    display: flex;
    flex-wrap: wrap;
}

/* The negative margin only where every child is a column.
 *
 * This is the fix for an 8px horizontal overflow at 375px. The clone nests
 * `.container-1170` and un-classed wrappers such as `.baner-big` directly
 * inside a `.row`, and a blanket negative margin pushes those past the
 * viewport because they carry no compensating padding. Restricting it took
 * documentElement.scrollWidth from 368 back to 360 at a 375 viewport. */
.v2-prose--article:has(.container-1170) .row:has(> [class*="col-"]):not(:has(> :not([class*="col-"]))),
.v2-prose--article:has(.container-1170) .flex-row:has(> [class*="col-"]):not(:has(> :not([class*="col-"]))) {
    margin-inline: calc(var(--v2-gap) / -2);
}

.v2-prose--article:has(.container-1170) [class*="col-xs-"],
.v2-prose--article:has(.container-1170) [class*="col-sm-"],
.v2-prose--article:has(.container-1170) [class*="col-md-"],
.v2-prose--article:has(.container-1170) [class*="col-lg-"] {
    width: 100%;
    /* A flex item's default min-width is auto, so one long word or one wide
       image inside a column widens the column past its share and takes the page
       sideways. */
    min-width: 0;
    padding-inline: calc(var(--v2-gap) / 2);
}

.v2-prose--article:has(.container-1170) .col-xs-1  { width: 8.3333%; }
.v2-prose--article:has(.container-1170) .col-xs-2  { width: 16.6667%; }
.v2-prose--article:has(.container-1170) .col-xs-3  { width: 25%; }
.v2-prose--article:has(.container-1170) .col-xs-4  { width: 33.3333%; }
.v2-prose--article:has(.container-1170) .col-xs-5  { width: 41.6667%; }
.v2-prose--article:has(.container-1170) .col-xs-6  { width: 50%; }
.v2-prose--article:has(.container-1170) .col-xs-7  { width: 58.3333%; }
.v2-prose--article:has(.container-1170) .col-xs-8  { width: 66.6667%; }
.v2-prose--article:has(.container-1170) .col-xs-9  { width: 75%; }
.v2-prose--article:has(.container-1170) .col-xs-10 { width: 83.3333%; }
.v2-prose--article:has(.container-1170) .col-xs-11 { width: 91.6667%; }
.v2-prose--article:has(.container-1170) .col-xs-12 { width: 100%; }

@media (min-width: 768px) {
    .v2-prose--article:has(.container-1170) .col-sm-1  { width: 8.3333%; }
    .v2-prose--article:has(.container-1170) .col-sm-2  { width: 16.6667%; }
    .v2-prose--article:has(.container-1170) .col-sm-3  { width: 25%; }
    .v2-prose--article:has(.container-1170) .col-sm-4  { width: 33.3333%; }
    .v2-prose--article:has(.container-1170) .col-sm-5  { width: 41.6667%; }
    .v2-prose--article:has(.container-1170) .col-sm-6  { width: 50%; }
    .v2-prose--article:has(.container-1170) .col-sm-7  { width: 58.3333%; }
    .v2-prose--article:has(.container-1170) .col-sm-8  { width: 66.6667%; }
    .v2-prose--article:has(.container-1170) .col-sm-9  { width: 75%; }
    .v2-prose--article:has(.container-1170) .col-sm-10 { width: 83.3333%; }
    .v2-prose--article:has(.container-1170) .col-sm-11 { width: 91.6667%; }
    .v2-prose--article:has(.container-1170) .col-sm-12 { width: 100%; }
}

@media (min-width: 992px) {
    .v2-prose--article:has(.container-1170) .col-md-1  { width: 8.3333%; }
    .v2-prose--article:has(.container-1170) .col-md-2  { width: 16.6667%; }
    .v2-prose--article:has(.container-1170) .col-md-3  { width: 25%; }
    .v2-prose--article:has(.container-1170) .col-md-4  { width: 33.3333%; }
    .v2-prose--article:has(.container-1170) .col-md-5  { width: 41.6667%; }
    .v2-prose--article:has(.container-1170) .col-md-6  { width: 50%; }
    .v2-prose--article:has(.container-1170) .col-md-7  { width: 58.3333%; }
    .v2-prose--article:has(.container-1170) .col-md-8  { width: 66.6667%; }
    .v2-prose--article:has(.container-1170) .col-md-9  { width: 75%; }
    .v2-prose--article:has(.container-1170) .col-md-10 { width: 83.3333%; }
    .v2-prose--article:has(.container-1170) .col-md-11 { width: 91.6667%; }
    .v2-prose--article:has(.container-1170) .col-md-12 { width: 100%; }
}

@media (min-width: 1200px) {
    .v2-prose--article:has(.container-1170) .col-lg-1  { width: 8.3333%; }
    .v2-prose--article:has(.container-1170) .col-lg-2  { width: 16.6667%; }
    .v2-prose--article:has(.container-1170) .col-lg-3  { width: 25%; }
    .v2-prose--article:has(.container-1170) .col-lg-4  { width: 33.3333%; }
    .v2-prose--article:has(.container-1170) .col-lg-5  { width: 41.6667%; }
    .v2-prose--article:has(.container-1170) .col-lg-6  { width: 50%; }
    .v2-prose--article:has(.container-1170) .col-lg-7  { width: 58.3333%; }
    .v2-prose--article:has(.container-1170) .col-lg-8  { width: 66.6667%; }
    .v2-prose--article:has(.container-1170) .col-lg-9  { width: 75%; }
    .v2-prose--article:has(.container-1170) .col-lg-10 { width: 83.3333%; }
    .v2-prose--article:has(.container-1170) .col-lg-11 { width: 91.6667%; }
    .v2-prose--article:has(.container-1170) .col-lg-12 { width: 100%; }
}

/* --------------------------------------------------------------------------
   White text written into a style attribute

   The class-based half of this is handled where `.text-color-white` is granted
   its colour, further up. This is the other half: headings the editor coloured
   by hand, `style="color:#ffffff"`, because at the time they sat on a dark
   band. "Why We Deliver Successfully" is one of them, and it was invisible on
   every expertise page that carries it.

   A style attribute outranks every stylesheet, so `!important` here is the
   honest answer rather than a shortcut.

   The exclusions are the grounds where white is still right: the page banner,
   which paints its own gradient, and the handful of genuinely dark bands.
   -------------------------------------------------------------------------- */

.v2-imported :is([style*="color:#ffffff"], [style*="color: #ffffff"], [style*="color:#fff;"], [style*="color: #fff;"]):not(
    .baner-text, .baner-text *,
    [style*="background-image"] *,
    .v2-band--dark, .v2-band--dark *
) {
    color: var(--v2-ink) !important;
}

/* --------------------------------------------------------------------------
   Photographic bands

   "Why We Deliver Successfully" and its six cards sit on a photograph of an
   engine — mean luminance 24, so nearly black. The theme's own ink colour was
   landing on it, which is dark grey on dark blue: legible in the sense that the
   letters are there, and unreadable in every sense that matters.

   The colour is only half of it. The photograph is busy — turbine blades, a
   circuit tracery — and even white type over it has to compete with whatever
   happens to be behind that particular line. So the band gets a scrim.

   `box-shadow: inset` rather than a `::before` overlay, because the content of
   these bands is imported markup of unknown shape: an absolutely positioned
   overlay needs a stacking context and a `z-index` on every child to sit
   behind them, and one card with its own `position` would disappear. An inset
   shadow paints over the background and under the content by definition, so
   there is nothing to get wrong. 100vmax is simply "larger than any band".

   Not scoped to `.container-fluid`, which is what this said at first and what
   made it miss most of them: the clone hangs the picture on whatever element
   happened to be there — a `.row` on the FPGA page, a `.container-1170` on the
   PCB one — and only sometimes on the band itself. Across the 58 service pages
   that left 51 headings and paragraphs printed in the theme's dark ink over a
   photograph.

   What identifies a band is not its class but that it paints a picture and has
   running text on top of it, and `:has()` can say exactly that. The empty
   `.bg-image` decorations hold no text and are left alone, and so is the hero,
   which paints its own gradient.

   `.tabs-component` and `.content-tab-1` are excluded for the opposite reason:
   they carry an inline photograph and the tab-block rule further up deliberately
   throws it away, because with every pane open at once there is no version of
   that band where text on the picture reads. Matching them here granted white
   ink for a ground that had just been removed — 917 lines of white on paper
   across the site, which is a worse fault than the one this rule fixes.

   And so is anything hung on an SVG. Two of them turned up: `quote2.svg`, the
   quotation mark drawn behind a pull quote on eleven pages, and `beefit_3.svg`,
   a bar chart in #FCF2F5. Both are ornaments on paper — scrimming them printed
   a grey slab over a quotation and turned half its words white. The photographs
   on these pages are all jpg or png; nothing that is actually a band is drawn
   as vector.
   -------------------------------------------------------------------------- */

.v2-imported [style*="background-image"]:not([style*=".svg"]):has(:is(p, h1, h2, h3, h4, li)):not(.front-accordion-item, .baner-big, .baner-big *, .tabs-component, .content-tab-1) {
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    box-shadow: inset 0 0 0 100vmax rgba(10, 15, 26, 0.74);
}

/* A flat dark band needs the ink but not the scrim — there is no photograph to
   hold the type off. Both kinds are handled together from here down. */
.v2-imported [style*="background-image"]:not([style*=".svg"]):has(:is(p, h1, h2, h3, h4, li)):not(.front-accordion-item, .baner-big, .baner-big *, .tabs-component, .content-tab-1),
.v2-imported [style*="background-image"]:not([style*=".svg"]):has(:is(p, h1, h2, h3, h4, li)):not(.front-accordion-item, .baner-big, .baner-big *, .tabs-component, .content-tab-1) :is(p, li, h1, h2, h3, h4, h5, span, strong, td, th),
.v2-imported .v2-band--dark,
.v2-imported .v2-band--dark :is(p, li, h1, h2, h3, h4, h5, span, strong, td, th) {
    color: #fff;
}

/* The hairlines that separate the cards were drawn for paper. */
.v2-imported [style*="background-image"]:not([style*=".svg"]):has(:is(p, h1, h2, h3, h4, li)):not(.front-accordion-item, .baner-big, .baner-big *, .tabs-component, .content-tab-1) :is(hr, .solution-title, .solution-item),
.v2-imported .v2-band--dark :is(hr, .solution-title, .solution-item) {
    border-color: rgba(255, 255, 255, 0.28);
}

.v2-imported [style*="background-image"]:not([style*=".svg"]):has(:is(p, h1, h2, h3, h4, li)):not(.front-accordion-item, .baner-big, .baner-big *, .tabs-component, .content-tab-1) a:not(.btn),
.v2-imported .v2-band--dark a:not(.btn) {
    color: #fff;
    text-decoration-color: rgba(255, 255, 255, 0.5);
}

/* The card icons on a dark band are the same defect one step further along:
   64px line drawings, drawn in near-black for a white card, sitting on #030318.
   They are not faint there, they are gone.

   `brightness(0) invert(1)` flattens whatever the icon is to white, which is
   right for line art and wrong for anything with colour in it — so this is
   scoped to `.solution-title`, the icon slot, and not to images generally. The
   photographs and vendor logos elsewhere in these bands are untouched. */
.v2-imported .v2-band--dark .solution-title img {
    filter: brightness(0) invert(1);
}

/* --------------------------------------------------------------------------
   The Isotope tiles

   "Full-cycle Engineering Services" and ten other pages lay their service
   tiles out with Isotope — a masonry library that positions each item
   absolutely from JavaScript. The library is not in this theme, so nothing
   positioned anything: the tiles stacked one per row at their natural 460px,
   and the caption that Isotope's stylesheet would have held over the picture
   fell out from under it and ran to the left edge of the window, past the
   margin every other line on the page starts at.

   Nothing here restores the masonry. The tiles are all the same square, so a
   plain grid gives the layout Isotope was computing, at no cost and with no
   script — and `auto-fill` does the column count that the library's
   breakpoints used to.

   The caption goes back under its picture rather than over it. On the clone it
   was a hover overlay, and a hover overlay is text that does not exist on a
   phone; underneath, it reads on every device and the picture keeps its whole
   frame. The tiles carry their own title inside the artwork, so the caption is
   the only text the card needs.
   -------------------------------------------------------------------------- */

/* The band around this grid has no inner container of its own — it is the one
   place on these pages where a `container-fluid` holds content directly — so
   the grid has to draw the page's own margin itself. Same numbers as
   `.container-1170`, which is what everything else on the page lines up with. */
.v2-imported .isotop-grid {
    display: grid;
    /* 200px, not the 232px this started at, and a gap of its own rather than
       the page gutter. The gutter is the distance between a column of text and
       the margin; between two tiles it is a hole. At 232 and a full gutter the
       five services on the industrial page came out four-and-one, with the
       fifth alone under three empty columns. */
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 32px 24px;
    width: 100%;
    max-width: calc(1170px + var(--v2-gutter) * 2);
    margin-inline: auto;
    padding-inline: var(--v2-gutter);
}

/* Isotope's sizer is a measuring stick, not a tile: the library reads its width
   to work out the column and never shows it. Here it is a real element in a
   real grid, so it would be an empty cell — except that on these pages the
   sizer class is put on the first tile as well, which is why it is displayed
   rather than hidden and only a sizer with nothing in it is dropped. */
.v2-imported .isotop-grid-sizer:empty {
    display: none;
}

.v2-imported .isotop-grid-item {
    min-width: 0;
}

.v2-imported .isotop-item-inner > a {
    display: flex;
    flex-direction: column;
    height: 100%;
    text-decoration: none;
    color: var(--v2-ink);
}

.v2-imported .isotop-item-inner > a > img {
    display: block;
    width: 100%;
    height: auto;
}

/* The caption. `.hw` and its `.center` are inline spans in the source, which is
   why the text ran the width of the window instead of the width of the card. */
.v2-imported .isotop-item-inner .hw,
.v2-imported .isotop-item-inner .hw > .center {
    display: block;
}

.v2-imported .isotop-item-inner .hw {
    flex: 1;
    padding: 16px 2px 0;
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--v2-ink-soft);
}

.v2-imported .isotop-item-inner .metro-title:not(:empty) {
    display: block;
    margin-bottom: 4px;
    font-family: var(--v2-head-font);
    font-weight: 700;
    color: var(--v2-ink);
}

/* The second copy of the caption, which the clone swapped in on hover. It is
   empty on every one of these pages and has nothing to say on any of them. */
.v2-imported .isotop-item-inner .hw-show {
    display: none;
}

/* The one thing the tile gains from being a link. The artwork already carries
   a "Read more", so the card does not need a second one. */
.v2-imported .isotop-item-inner > a:hover > img,
.v2-imported .isotop-item-inner > a:focus-visible > img {
    opacity: 0.88;
}

.v2-imported .isotop-item-inner > a:hover .hw,
.v2-imported .isotop-item-inner > a:focus-visible .hw {
    color: var(--v2-ink);
}

/* --------------------------------------------------------------------------
   FAQ

   The questions and answers on 87 pages, which until now printed as one
   unbroken run of prose inside a hairline box the clone drew with a style
   attribute. Folded by the script, and dressed here.

   Written for both layers. The same `.spoiler` markup appears in the news
   articles, where `v2-collapse.js` converts it exactly as it does on an
   imported page — but the styling was scoped to `.v2-imported` alone, so on 14
   posts the toggle fell through to the article layer's rule for a bare
   `<button>` and came out as an accent-red block with the question printed on
   it in the page's dark ink. Unreadable, and on the FAQ, which is the part of
   those posts a reader actually goes looking for.

   Drawn the way the rest of this theme draws a list: no box, no card, no fill.
   A rule above each question, the question set in the heading face, the mark on
   the right. What separates one question from the next is a line, and what
   makes the open one findable is that it is the only one with an answer under
   it. A box around each row would have made eleven boxes down the page and said
   nothing the lines do not.
   -------------------------------------------------------------------------- */

/* The clone's `style="border:solid 1px"` is on the element, so it cannot be
   turned off from here without this. It is the only thing in the block that
   needs it. */
:is(.v2-imported, .v2-prose--article) .v2-faq .spoiler-outer {
    border: 0 !important;
    padding: 0;
}

:is(.v2-imported, .v2-prose--article) .v2-faq {
    border-top: 1px solid var(--v2-rule);
}

/* The clone's own indicator: an empty `<div class="plus">` that its stylesheet
   drew a sign into. Nothing draws into it here, but it is still a block with a
   line height, so it was adding 27px of nothing under every question — which
   read as the rows being padded more below than above. The mark on the toggle
   does its job. */
:is(.v2-imported, .v2-prose--article) .v2-faq .plus {
    display: none;
}

/* The last question needs the line under it that the next question would have
   given it, or the list ends without an edge. */
:is(.v2-imported, .v2-prose--article) .v2-faq:last-of-type {
    border-bottom: 1px solid var(--v2-rule);
}

:is(.v2-imported, .v2-prose--article) .v2-faq__toggle {
    align-items: flex-start;
    padding: var(--v2-s2) 0;
}

/* Aligned with the first line of the question rather than with the middle of
   however many lines it runs to: a two-line question would otherwise carry its
   mark halfway down the row, out of line with every one-line question above it. */
:is(.v2-imported, .v2-prose--article) .v2-faq__toggle::after {
    margin-top: 0.6em;
}

/* The question. Sized as itself, not as a heading in the page's outline — an
   h3 here is a row in a list, and at the 28px the imported h3 rule gives it,
   eleven of them read as eleven sections. */
:is(.v2-imported, .v2-prose--article) .v2-faq__toggle :is(h2, h3, h4, h5) {
    margin: 0;
    font-family: var(--v2-head-font);
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.45;
    color: var(--v2-ink);
}

:is(.v2-imported, .v2-prose--article) .v2-faq__toggle:hover :is(h2, h3, h4, h5),
:is(.v2-imported, .v2-prose--article) .v2-faq__toggle:focus-visible :is(h2, h3, h4, h5) {
    color: var(--v2-accent);
}

/* The answer, indented to clear the question's own text column and held to a
   reading measure — these run to a dozen bullet points and the block is 10 of
   12 columns wide, which is far past what a sentence can be read across. */
:is(.v2-imported, .v2-prose--article) .v2-faq .v2-accordion__body {
    max-width: 74ch;
    padding-bottom: var(--v2-s3);
    color: var(--v2-ink-soft);
}

:is(.v2-imported, .v2-prose--article) .v2-faq .v2-accordion__body > :first-child {
    margin-top: 0;
}

:is(.v2-imported, .v2-prose--article) .v2-faq .v2-accordion__body :is(ul, ol) {
    margin: var(--v2-s2) 0;
    padding-left: 1.25em;
}

:is(.v2-imported, .v2-prose--article) .v2-faq .v2-accordion__body li {
    margin-bottom: 6px;
}


/* --------------------------------------------------------------------------
   The front accordion

   60 items across 13 service pages, and one more component whose script this
   theme does not carry. In the clone these are a horizontal accordion: five
   panels closed to a vertical ribbon, one open at a time. Nothing opens or
   closes anything here, so the panels printed as full-width slabs with the
   text lying directly on the picture — "Our Key Areas of Expertise" on the
   hardware page was five photographs with paragraphs across them.

   Not rebuilt as an accordion. The whole point of the original was to fit five
   panels in one screen, and a grid does that without hiding four fifths of the
   content behind a control.

   The picture becomes a plate at the top of a card and the text sits under it
   on paper, rather than over it under a scrim. That is the one choice here
   worth explaining: half of these images are photographs and half are flat
   brand colours — `lattice640.png` is 640×480 of solid yellow — and a scrim
   dark enough to carry white text turns the yellow one into olive. A plate
   keeps the brand colour as the brand colour and the sentence on paper, where
   it is legible without anything being negotiated.
   -------------------------------------------------------------------------- */

.v2-imported .front-accordion {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 32px 24px;
}

.v2-imported .front-accordion-item {
    display: flex;
    flex-direction: column;
    min-width: 0;
    /* The plate. `cover` rather than `contain`: on a photograph contain would
       letterbox, and on a flat colour there is nothing to crop. */
    background-size: cover;
    background-position: center top;
    background-repeat: no-repeat;
    /* Its height, held off the content by padding rather than by a wrapper —
       there is no element between the item and its text to make into one. */
    padding-top: 168px;
}

/* The ribbon the closed panels were reduced to. It says "Telecom" where the
   card's own title says "Telecommunications", and "Lattice" where the title
   says "Lattice Semiconductor" — a label for a panel that cannot be seen, on a
   card where it can. */
.v2-imported .front-accordion .vertical-title {
    display: none;
}

/* Paper, and opaque: this is what stops the plate showing through behind the
   sentence. */
.v2-imported .front-accordion-item-content {
    display: flex;
    flex-direction: column;
    flex: 1;
    background: var(--v2-paper);
    padding-top: var(--v2-s2);
}

.v2-imported .front-accordion-item-content .title {
    margin-bottom: 8px;
    font-family: var(--v2-head-font);
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--v2-ink);
}

.v2-imported .front-accordion-item-content .title a {
    color: inherit;
    text-decoration: none;
}

.v2-imported .front-accordion-item-content .text {
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--v2-ink-soft);
}

/* Pushed to the foot of the card so the links line up across the row: these
   descriptions run from two lines to nine, and a link that floats wherever the
   text happens to end reads as five different components. */
.v2-imported .front-accordion-item-content .link {
    margin-top: auto;
    padding-top: var(--v2-s2);
}

.v2-imported .front-accordion-item-content .link a {
    font-weight: 600;
    font-size: 0.9375rem;
    color: var(--v2-accent);
    text-decoration: none;
}

.v2-imported .front-accordion-item-content .link a:hover {
    text-decoration: underline;
}

/* A grid cell whose whole content is one button.
 *
 * Eleven of them, on two pages: the four domains under "More about our
 * domain-specific software development" and a set on the IoT page. The clone
 * sized each button by hand at 230px and dropped it into a 300px cell with
 * 50px of padding above and 90px under the row, which in this theme reads as
 * four red slabs floating in a great deal of nothing.
 *
 * A button that fills its cell turns them into what they are — a set of four
 * equal choices — and the padding comes back to the theme's own rhythm.
 *
 * `!important` because the width and the padding are both written inline, and
 * scoped to a narrow cell for the same reason the width is being overridden at
 * all: a full-width button is right in a quarter-column and wrong across 1170px,
 * which is where the same markup puts a single call to action.
 */
.v2-imported :is([class*="col-md-3"], [class*="col-md-4"], [class*="col-sm-3"], [class*="col-sm-4"]) p.rtecenter > a:is(.btn-blue, .btn-red, .btn-white):only-child {
    width: 100% !important;
}

.v2-imported [style*="padding-top"]:has(> p.rtecenter > a:is(.btn-blue, .btn-red, .btn-white):only-child) {
    padding-top: var(--v2-s2) !important;
}

.v2-imported .row:has(> [class*="col-"] > [style] > p.rtecenter > a:is(.btn-blue, .btn-red, .btn-white):only-child) {
    padding-bottom: var(--v2-s4) !important;
}

/* The card icons on a photographic band, for the reason the flat dark bands
   needed the same rule: these are line drawings in near-black, and the band
   they sit on is a photograph dark enough to carry white type. */
.v2-imported [style*="background-image"]:not([style*=".svg"]):has(:is(p, h1, h2, h3, h4, li)):not(.front-accordion-item, .baner-big, .baner-big *, .tabs-component, .content-tab-1) .solution-title img {
    filter: brightness(0) invert(1);
}

/* The portfolio category list.
 *
 * Forty-odd links written straight after one another with no separator between
 * them, so the browser reads "…TransportationBroadcastingConsumer…" as one
 * unbreakable word and lays it out 510px wide inside a 360px column. On a phone
 * that is a 319px horizontal scrollbar on the whole document.
 *
 * Wrapping alone would fix the overflow and leave the words still run together.
 * A flex row with a gap fixes both: the links wrap, and they stop being one
 * long string and start being what they are — a list of categories you can
 * pick from.
 */
.v2-imported .all-categories,
/* The other four panels of the same widget hold their links directly rather
   than in an `.all-categories` box. `:has(> a)` is what tells them apart from
   the first one, whose children are two divs and which must stay a block. */
.v2-imported .portfolio__tabs__content:has(> a) {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 16px;
}

.v2-imported .all-categories > a,
.v2-imported .portfolio__tabs__content > a {
    display: inline-flex;
    align-items: center;
    /* The tap target the list never had. */
    min-height: 32px;
}

/* --------------------------------------------------------------------------
   Contacts — the band's extra column foot
   --------------------------------------------------------------------------

   The contacts page is the home page's contact band with two more things in
   its left column: the page's own title and the addresses. Both sit under the
   email, both are small, and both are scoped to .v2-where so the band itself
   is untouched everywhere else it appears.

   The page had a layout of its own before this, built a column at a time, and
   it ended up resembling neither the home page nor anything else on the site.
   The band had already answered the question. */
.v2-where {
    margin-top: var(--v2-s5);
    padding-top: var(--v2-s3);
    border-top: 1px solid var(--v2-rule);
}

/* The page's <h1>, set at the size of a label. The band's own heading is what
   the page says; this is what the page is called, and a document needs it. */
.v2-where__title {
    margin: 0;
    font-family: var(--v2-sans);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v2-ink-mute);
}

/* Two addresses side by side: stacked they push the proof strip a screen down
   for two lines of street each. */
.v2-where__offices {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--v2-s3) var(--v2-s4);
    margin-top: var(--v2-s3);
}

@media (min-width: 480px) {
    .v2-where__offices {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* The card, on the blue and at the smallest size it still reads at. The shared
   rule gives it a hairline of its own for the band it was drawn for; the group
   above already has one, and a second per column turns two addresses into a
   table. */
.v2-where .v2-office {
    padding-top: 0;
    border-top: 0;
}

/* The country outline is a line drawing and this ground carries the blueprint
   artwork. At the artwork's own weight the two read as one drawing rather than
   as a picture placed on a background. */
.v2-where .v2-office__map {
    height: 44px;
    stroke: rgba(255, 255, 255, 0.5);
}

.v2-where .v2-office__figure {
    margin-bottom: var(--v2-s1);
}

.v2-where .v2-office__name {
    margin-bottom: 2px;
    font-size: 0.875rem;
    color: #fff;
}

.v2-where .v2-office__address,
.v2-where .v2-office__phone,
.v2-where .v2-office__phone a,
.v2-where .v2-office__place {
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--v2-ink-mute);
}

.v2-where .v2-office__place:hover,
.v2-where .v2-office__place:focus-visible,
.v2-where .v2-office__phone a:hover {
    color: #fff;
}

/* The two sentences under the ask: how we will answer, and what we will not do
   with what was typed. Reading size and held to a measure - they are the
   reassurance somebody wants before filling in a form, not a standfirst. */
.v2-contact__line {
    margin: var(--v2-s2) 0 0;
    max-width: 52ch;
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--v2-ink-mute);
}

/* --------------------------------------------------------------------------
   The mark before a heading
   --------------------------------------------------------------------------

   A short bar in front of every major heading, in the ground's opposite: red
   on paper and on the blue, blue on the red. It replaces the register bar this
   theme used to open each section with — that carried a number, a label and a
   rule, and was withdrawn for saying in nine words what the heading under it
   said in three. This says the same thing in one stroke: a section starts here.

   Drawn as a ::before on the heading rather than as an element, so it belongs
   to the line it marks — it wraps with it, it inherits its block, and no
   template has to remember to print it. em-sized for the same reason: at the
   masthead's 52px and at a section's 26px it stays the same weight relative to
   the letters beside it instead of shrinking into a dot on one and swelling
   into a slab on the other.

   Only the headings that open something. An h3 inside a card is a label on a
   thing, not the start of a section, and marking those would put the device on
   forty lines of one page. */
.v2-display::before,
.v2-h2::before,
.v2-contact__title::before {
    content: "";
    display: inline-block;
    /* A rule, not a slab. At 0.34em it read as a block of colour competing with
       the word beside it; the mark's job is to point at the heading, not to
       weigh the same as it. */
    width: 0.16em;
    height: 0.78em;
    margin-right: 0.4em;
    /* Sat on the baseline it marks rather than centred on the line box: at the
       cap height of the letters beside it the bar reads as part of the word,
       and centred it floats. */
    vertical-align: -0.02em;
    background: var(--v2-accent);
}

/* On the red ground the accent is the ground, so the mark takes the blue the
   theme pairs it with everywhere else. */
.v2-section--brand .v2-display::before,
.v2-section--brand .v2-h2::before {
    background: var(--v2-blue);
}

/* On the blue and on the night ground, red — which is the pairing the other
   way round, and the only other colour this theme has. */
.v2-section--blue .v2-display::before,
.v2-section--blue .v2-h2::before,
.v2-section--night .v2-display::before,
.v2-section--night .v2-h2::before {
    background: var(--v2-accent);
}

/* The imported bodies: their own h1 and h2 open sections the same way the
   composed ones do, so they take the same mark. Anything deeper is a label
   inside a section and is left alone. */
.v2-imported :is(h1, h2):not(.baner-text *, .breadcrumb *)::before {
    content: "";
    display: inline-block;
    width: 0.15em;
    height: 0.74em;
    margin-right: 0.38em;
    vertical-align: -0.02em;
    background: var(--v2-accent);
}

/* A heading standing on a dark band inside imported markup - the compatibility
   layer marks those with .v2-band--dark - reads on the same rule as the
   theme's own dark grounds. */
.v2-imported .v2-band--dark :is(h1, h2)::before {
    background: var(--v2-accent);
}

/* Never on a heading that is already a picture's caption or a banner title:
   those sit over a photograph, where a bar in front of the words is a mark on
   the image rather than on the page. */
.v2-imported .baner-text :is(h1, h2)::before,
.v2-imported .baner-big :is(h1, h2)::before {
    content: none;
}

/* --------------------------------------------------------------------------
   The About banner, rebuilt as type.

   It was a composed poster: one PNG carrying the heading, two paragraphs and
   four captions baked into the pixels. That reads well in exactly one language
   and cannot be edited, so the words are back to being words and only the
   photograph is still a picture.

   Everything is measured off the original artwork, which is 1672 x 941, and
   written as a share of that width so the rebuild lands on the same
   proportions rather than near them:

       panel gutter      97/1672   heading    91px    rule   90 x 3
       text column      593/1672   body       20px    icons  55px
       photograph edge  700/1672

   The sizes are in vw because the section runs edge to edge, which makes the
   viewport and the section the same width. Below --v2-bp-lap the artwork's
   proportions stop being the point - a 16:9 banner on a phone is a column of
   unreadable type - so the two halves stack and the sizes go back to the
   page's own scale.
   -------------------------------------------------------------------------- */


/* Everything below is written under `.v2-imported` on purpose. The imported
   stylesheet already claims `ul`, `li`, `p` and the headings at one class and
   one element, which outranks a plain block class however far down the file it
   sits — the markers came back and the captions set in the body colour. */

.v2-imported .about-hero {
    position: relative;
    display: flex;
    align-items: center;
    isolation: isolate;
    overflow: hidden;
    margin: 0 auto;
    max-width: 1920px;
    /* The artwork's proportions as a floor rather than as a fixed ratio: a
       translation that runs three lines longer than the Turkish grows the
       section instead of being cropped by it. */
    container-type: inline-size;
    background: #fff;
    --about-red: #c8373c;
    --about-ink: #0b1220;
    --about-body: #46505f;
}

/* The photograph sits behind the words rather than beside them, so a panel
   that grows fades out over the picture instead of meeting a column edge. */
.v2-imported .about-hero__media {
    position: absolute;
    z-index: 0;
    inset: 0 0 0 41.87%;
}

.v2-imported .about-hero__media img {
    display: block;
    width: 100%;
    height: 100%;
    max-width: none;
    object-fit: cover;
    object-position: center;
}

/* The join. The artwork fades the picture out over about 120px of its own
   width; as a gradient the seam moves with the layout instead of being burnt
   into the file at one position. */
.v2-imported .about-hero__media::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        to right,
        #fff 0%,
        rgba(255, 255, 255, 0.96) 8%,
        rgba(255, 255, 255, 0.78) 15%,
        rgba(255, 255, 255, 0.42) 24%,
        rgba(255, 255, 255, 0.12) 34%,
        rgba(255, 255, 255, 0) 46%
    );
}

.v2-imported .about-hero__panel {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: 35.5%;
    /* The artwork is 1672 x 941. The ratio is carried by the panel rather than
       by the section: a child queries the section, an element cannot query
       itself, and as a floor rather than a fixed height it lets a longer
       translation push the section taller instead of being cropped by it. */
    min-height: 56.28cqw;
    margin: 0 0 0 5.8%;
    padding: 2cqw 0;
}

.v2-imported .about-hero__panel > :first-child {
    margin-top: 0;
}

.v2-imported .about-hero__panel > :last-child {
    margin-bottom: 0;
}

/* Sized to the artwork's own line rather than to its cap height: at the weight
   this site carries, matching the height would set the heading half again as
   wide as the box it has to sit in and break it over two lines. The box is the
   thing the eye reads. */
.v2-imported .about-hero__panel :is(h1, h2) {
    margin: 0;
    font-family: var(--v2-sans);
    font-size: 4.38cqw;
    font-weight: 800;
    line-height: 1.08;
    letter-spacing: -0.02em;
    color: var(--about-ink);
}

/* The imported headings on this page carry an accent bar in front of them and
   the artwork's heading does not. */
.v2-imported .about-hero__panel :is(h1, h2)::before {
    content: none;
}

.v2-imported .about-hero__rule {
    width: 5.38cqw;
    height: 3px;
    margin: 2.3cqw 0 0;
    padding: 0;
    border: 0;
    background: var(--about-red);
}

.v2-imported .about-hero__panel p {
    margin: 1.9cqw 0 0;
    max-width: none;
    font-family: var(--v2-sans);
    font-size: 1.16cqw;
    font-weight: 400;
    line-height: 1.55;
    color: var(--about-body);
}

/* --------------------------------------------------------------- the pillars */

.v2-imported .about-hero__pillars {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0 0.6cqw;
    margin: 3.5cqw 0 0;
    padding: 0;
    list-style: none;
}

.v2-imported .about-hero__pillars li {
    margin: 0;
    padding: 0;
    list-style: none;
    text-align: center;
    font-family: var(--v2-sans);
    font-size: 1.2cqw;
    font-weight: 700;
    line-height: 1.32;
    color: var(--about-ink);
}

.v2-imported .about-hero__pillars li::marker {
    content: "";
}

.v2-imported .about-hero__pillars li::before {
    content: "";
    display: block;
    width: 3.3cqw;
    height: 3.3cqw;
    margin: 0 auto 1.2cqw;
    background: center / contain no-repeat;
}

.v2-imported .about-hero__pillars li:nth-child(1)::before { background-image: url("/images/about/team.svg"); }
.v2-imported .about-hero__pillars li:nth-child(2)::before { background-image: url("/images/about/focus.svg"); }
.v2-imported .about-hero__pillars li:nth-child(3)::before { background-image: url("/images/about/chip.svg"); }
.v2-imported .about-hero__pillars li:nth-child(4)::before { background-image: url("/images/about/shield.svg"); }

/* The closing line stands further from the captions than the captions do from
   their icons, which is what reads as a group and then a sentence. */
.v2-imported .about-hero__pillars + p {
    margin-top: 2.8cqw;
}

/* ------------------------------------------------------------------ stacking */

/* The artwork's proportions hold down to about a laptop. Past that the panel
   would be a 300px column of 8px type, so the picture goes above the words and
   the sizes return to the page's own scale. */
@media (max-width: 900px) {
    .v2-imported .about-hero {
        display: block;
        min-height: 0;
        max-width: none;
    }

    .v2-imported .about-hero__media {
        position: relative;
        inset: auto;
        aspect-ratio: 16 / 10;
    }

    /* Stacked, a left-to-right fade would wash out one side of the picture
       rather than the edge that meets the words. */
    .v2-imported .about-hero__media::after {
        background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 58%, #fff 100%);
    }

    .v2-imported .about-hero__panel {
        width: auto;
        margin: 0;
        padding: 0 var(--v2-gutter) clamp(40px, 8vw, 64px);
    }

    .v2-imported .about-hero__panel :is(h1, h2) {
        font-size: clamp(30px, 7.2vw, 48px);
    }

    .v2-imported .about-hero__rule {
        width: 72px;
        margin-top: 20px;
    }

    .v2-imported .about-hero__panel p {
        margin-top: 18px;
        font-size: clamp(15px, 4vw, 17px);
    }

    .v2-imported .about-hero__pillars {
        grid-template-columns: repeat(2, 1fr);
        gap: clamp(24px, 6vw, 36px) 16px;
        margin-top: clamp(28px, 7vw, 40px);
    }

    .v2-imported .about-hero__pillars li {
        font-size: clamp(14px, 3.8vw, 16px);
    }

    .v2-imported .about-hero__pillars li::before {
        width: 44px;
        height: 44px;
        margin-bottom: 12px;
    }

    .v2-imported .about-hero__pillars + p {
        margin-top: clamp(28px, 7vw, 40px);
    }
}

/* --------------------------------------------------------------------------
   Two small corrections to the footer and the row lists.
   -------------------------------------------------------------------------- */

/* A row list draws a hairline under each row to separate it from the next one.
   The last row has no next one, so its line closes nothing and reads as the
   start of a section that is not there. */
.v2-row:last-child {
    border-bottom: 0;
}

/* The LinkedIn mark, sized to the line it sits on rather than to itself. */
.v2-footer__social {
    display: inline-flex;
    align-items: center;
}

.v2-footer__social svg {
    width: 1.15em;
    height: 1.15em;
    display: block;
}

/* --------------------------------------------------------------------------
   The figures, as the last line of the masthead.

   They had a band of their own between the hero and the first real section:
   371px of page for 140px of content, no heading, no sentence under it, and
   nothing saying what the three numbers were evidence FOR. Every attempt to
   fix it by setting - bigger type, a dark ground, then a red one - made it
   louder without making it belong, because the problem was never how it looked.
   It was that a claim and its evidence had been put in separate rooms.

   So the band is gone and the evidence is on the claim's own line, small,
   under the buttons: eight years, fifty projects, ten engineers, read in the
   same breath as the headline they answer. One band fewer, no third ground on
   the page, and the red goes back to being what it was - the logo, the button
   and the bar in front of a heading.

   Named stat and not figure. `.v2-hero__figure` is already taken, by the
   photograph beside the headline, which is pushed a gutter past the right edge
   of the window - so the first version of this line inherited a grid column
   and a -57.6px right margin and printed its three items on top of each other.
   -------------------------------------------------------------------------- */

.v2-hero__stats {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--v2-s3);
    margin: var(--v2-s4) 0 0;
    padding: 0;
    list-style: none;
}

.v2-hero__stat {
    font-family: var(--v2-sans);
    font-size: 0.9375rem;
    line-height: 1.5;
    letter-spacing: 0.01em;
    color: var(--v2-ink-mute);
}

/* The number carries the fact and the words name it, so only the number is
   set in the heading face. Tabular figures because three of these sit in a row
   and a proportional 1 makes the gaps between them uneven. */
.v2-hero__stat strong {
    font-family: var(--v2-head-font);
    font-weight: 700;
    font-size: 1.1875rem;
    font-variant-numeric: tabular-nums;
    color: var(--v2-ink);
    margin-inline-end: 0.35em;
}

/* A middot between them rather than a rule: at this size a vertical hairline
   is a heavier mark than the type it separates. */
.v2-hero__stat + .v2-hero__stat::before {
    content: "·";
    margin-inline-end: var(--v2-s3);
    color: var(--v2-rule-strong);
    opacity: 0.35;
}

@media (max-width: 559px) {
    /* Stacked, the middots would sit at the start of a line where they read as
       bullets rather than as separators. */
    .v2-hero__stats {
        display: grid;
        gap: var(--v2-s1);
    }

    .v2-hero__stat + .v2-hero__stat::before {
        content: none;
    }
}
