/* ============================================================
   SERVICES CARDS — both views (slides + boxes)
   ============================================================
   Section structure
   -----------------
   .services-cards                         section root
     .services-cards__bar                  sticky bar (top of section)
       .services-cards__bar-blur           frosted layer above bar (visible only when sticky)
       .services-cards__bar-inner          solid cream bar (CTA + title)
     .services-cards__rows                 SLIDES view (7 .service-row)
     .services-boxes                       BOXES view (3 .services-boxes__row)

   View visibility is controlled by .is-view-rows / .is-view-boxes
   modifiers on the section root. Both views remain in the DOM
   so videos can be reparented between them on toggle without
   reloading.
   ============================================================ */


/* ── Tunables ───────────────────────────────────────────────
   Section-scoped vars so it's easy to retune from one place. */
.services-cards {
    --bar-h:                  clamp(72px, 8vh, 110px);   /* sticky bar height */
    --bar-blur-h:             10vh;                       /* blur strip above the cream bar */
    --bar-pad-x:              var(--pad-x);
    --bar-fill:               var(--color-white);
    --bar-blur-fill:          rgba(255, 251, 240, 0.10);  /* 10% cream */
    --bar-blur-amount:        40px;

    --row-h:                  clamp(170px, 20vh, 240px);  /* collapsed row — taller so video can grow */
    --row-h-expanded:         clamp(260px, 32vh, 340px);  /* expanded row (desktop/tablet) */
    --row-pad-y:              var(--space-5);             /* 24px — equal top/bottom padding shared by text + video */
    --row-col-gap:            var(--space-8);             /* 40px — gap between text/num/video columns (was --space-6 = 32px) */

    --row-bg:                 var(--color-black2);
    --row-bg-expanded:        var(--color-black);          /* #262626 */

    /* Video sizing — height-driven so it fits inside the row without
       overflowing. Width follows from aspect-ratio. */
    --video-h-collapsed:      clamp(110px, 14vh, 170px);                    /* fills row minus row-pad-y */
    --video-h-expanded:       clamp(200px, calc(32vh - 48px), 270px);       /* fills expanded row minus row-pad-y */

    --service-color:          var(--color-white);          /* default; overridden per row/box */

    position: relative;
    background: var(--color-black2);
    color: var(--color-white);
}


/* ============================================================
   STICKY BAR
   ============================================================
   Two stacked elements form the sticky chrome:
     1. .services-cards__bar-blur  — 14vh frosted strip above the
        cream bar (10% cream + backdrop-blur). Positioned ABSOLUTE
        so it doesn't push the cream bar down when not sticky.
        Fades in only once the bar pins (is-sticky).
     2. .services-cards__bar-inner — the solid cream chrome.

   When NOT sticky → previous section's bottom touches the cream
   bar's top with no gap (blur layer is hidden).
   When sticky → bar pins at top: 14vh; blur fills the 14vh strip
   between viewport top and cream bar; the section behind it is
   visible blurred.
   ============================================================ */
.services-cards__bar {
    position: sticky;
    top: var(--bar-blur-h);                  /* leave 14vh of viewport above the bar for the blur strip */
    z-index: var(--z-elevated);
    height: var(--bar-h);
    /* z-index sits below nav (1000) so nav overlaps the blur. */
}

/* Frosted blur layer — sits ABOVE the cream bar (in the 14vh
   gap above it). Absolutely positioned so it adds no flow
   height when not sticky → previous section touches cream bar
   directly. Fades in via opacity once the bar is sticky. */
.services-cards__bar-blur {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;                             /* sit immediately above the cream bar */
    height: var(--bar-blur-h);
    background: var(--bar-blur-fill);
    backdrop-filter: blur(var(--bar-blur-amount));
    -webkit-backdrop-filter: blur(var(--bar-blur-amount));
    opacity: 0;
    pointer-events: none;
    /* Opacity fade-in transition deferred to animation pass — same
       cascade-stuck-mid-frame issue as the row min-height transition.
       For now, opacity flips instantly on .is-sticky toggle. */
}

.services-cards__bar.is-sticky .services-cards__bar-blur {
    opacity: 1;
    pointer-events: auto;                    /* once visible, the blur strip blocks hover/click on the rows/boxes beneath it (nav floats above it via higher z-index, so nav still works) */
}

/* Solid cream chrome of the bar */
.services-cards__bar-inner {
    position: relative;
    height: 100%;
    padding: 0 var(--bar-pad-x);
    background: var(--bar-fill);
    color: var(--color-black2);
    display: flex;
    align-items: center;
    justify-content: center;        /* initial: button centered */
    /* Use overflow: clip on X only — clips horizontally without
       forcing the Y axis to scroll/clip. This lets the toggle
       button's hover scale and font ascenders extend slightly
       above/below the bar without being cut off, while still
       hiding the "Our Services" title's left-side slide-in. */
    overflow-x: clip;
    overflow-y: visible;
}

/* "Our Services" — hidden initially, slides in from the left
   when the bar becomes sticky. Matches the toggle button's 950ms
   FLIP timing in BOTH directions so the exit is a true inverse
   of the entry — same duration, same easing, just reversed. */
.services-cards__bar-title {
    position: absolute;
    left: var(--bar-pad-x);
    top: 50%;
    transform: translate(-30px, -50%);
    font-family: var(--font-heading);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-tight);
    font-size: clamp(20px, 2vw, 28px);
    line-height: 1;
    opacity: 0;
    filter: blur(10px);
    transition:
        opacity 950ms var(--ease-out-expo),
        filter 950ms var(--ease-out-expo),
        transform 950ms var(--ease-out-expo);
    pointer-events: none;
}

.services-cards__bar.is-sticky .services-cards__bar-title {
    opacity: 1;
    filter: blur(0);
    transform: translate(0, -50%);
}

/* Toggle button — initial: dead-centered. Sticky: slides to right edge. */
.services-cards__bar-toggle {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--btn-pad-y) var(--btn-pad-x);
    background: var(--color-black2);
    color: var(--color-white);
    border-radius: var(--radius);
    font-family: var(--font-btn);
    font-weight: var(--font-weight-btn);
    letter-spacing: var(--tracking-btn);
    font-size: var(--text-btn);
    line-height: 1;
    cursor: pointer;
    /* No transition on transform — GSAP FLIP owns all transform changes
       between sticky / non-sticky positions. CSS would fight GSAP otherwise. */
    transition:
        background-color 200ms ease,
        color 200ms ease;
}

.services-cards__bar-toggle:hover {
    background: var(--color-white);
    color: var(--color-black2);
    /* No scale here — sticky-bar button keeps its position locked.
       Marching-dashes border still appears (handled by the shared
       .cta-marching-border SVG rules below). */
}

.services-cards__bar-toggle-icon {
    display: inline-flex;
    width: 18px;
    height: 18px;
    align-items: center;
    justify-content: center;
}

.services-cards__bar-toggle-icon svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* Sticky-state position — keep the button as a flex child (NOT
   position: absolute) so it inherits the parent's align-items: center
   for vertical centering. This is critical: switching to absolute
   positioning would force JS to also handle vertical centering via
   transform: translateY(-50%), which would fight the FLIP's
   transform-based slide animation.

   Right-alignment is achieved with margin-left: auto, which pushes
   the button to the end of the flex line. The JS FLIP captures the
   button's positions (left-center vs right-aligned) and animates
   between them with a transform-based slide. Both states use the
   SAME vertical centering, so no Y bobble can occur. */
.services-cards__bar.is-sticky .services-cards__bar-toggle {
    margin-left: auto;
}


/* ============================================================
   VIEW VISIBILITY (rows ↔ boxes)
   Both views remain in DOM. Show only the active one.
   ============================================================ */
.services-cards__rows,
.services-boxes {
    transition: opacity 150ms ease;
}

.services-cards.is-view-rows .services-boxes,
.services-cards.is-view-boxes .services-cards__rows {
    display: none;
}

/* During the toggle crossfade, both views may be briefly hidden
   while videos are reparented. JS adds .is-toggling. */
.services-cards.is-toggling .services-cards__rows,
.services-cards.is-toggling .services-boxes {
    opacity: 0;
}


/* ============================================================
   SLIDES VIEW (.services-cards__rows)
   ============================================================ */
.services-cards__rows {
    /* Block layout — rows stack naturally, each sized by its own height
       declaration. Avoid flex here so children aren't subject to flex's
       size-distribution + shrink behaviour, which conflicts with the
       expand-on-hover height transition. */
    display: block;

    /* ── Layout-stable expansion buffer ──────────────────────
       Reserves bottom space equal to one row's expansion delta
       while no row is expanded. When a row expands, JS adds
       .has-expanded to .services-cards and the buffer collapses
       to 0 — the expanded row fills exactly the reserved space,
       so the section's outer height stays constant. Result: zero
       layout shift below this section, no ScrollTrigger churn,
       no lag from row hover/click.

       NO TRANSITION on padding-bottom — the row's min-height change
       is instant (transitioning min-height inside the grid+sticky
       parent caused jitter, see comment on .service-row), so the
       buffer must also collapse instantly. If they were mismatched
       in timing, the section would briefly grow/shrink during the
       transition window, pushing content below. Symmetric instant
       changes keep the section's outer height frame-perfect.

       Value source order:
         1. --rows-buffer  — set by JS after measuring the actual
            max expanded-row delta on this viewport (pixel-perfect)
         2. Fallback calc  — used during first paint before JS runs,
            and as a hard fallback if JS measurement fails. Built
            from --row-h / --row-h-expanded with ~72px safety. */
    padding-bottom: var(--rows-buffer, calc(var(--row-h-expanded) - var(--row-h) + var(--space-9) + var(--space-3)));
}

/* No CSS override on has-expanded any more — JS now writes the
   buffer value (--rows-buffer) directly per-row for pixel-perfect
   compensation. The resting buffer = MAX expansion delta across all
   rows; when row N is expanded, buffer = (MAX − delta(N)), so the
   section's outer height is identical no matter which row is open. */

/* Mobile (<=767px) — rows are content-sized (min-height: 0) so the
   desktop calc doesn't apply. JS measures the actual delta and writes
   it to --rows-buffer; the clamp here is the pre-measurement fallback
   (and a backstop for any worst-case content). */
@media (max-width: 767px) {
    .services-cards__rows {
        padding-bottom: var(--rows-buffer, clamp(240px, 36vh, 340px));
    }
}

.service-row {
    --service-color: var(--color-white);
    position: relative;
    display: grid;
    align-items: center;
    background: var(--row-bg);
    border-bottom: var(--guide-border);
    padding: var(--row-pad-y) var(--pad-x);  /* equal top/bottom so text + video share the same vertical breathing room */
    min-height: var(--row-h);                /* min-height (not height) so the row can grow on expand */
    cursor: pointer;                          /* clickable on touch devices (no-hover); overridden below for hover-capable devices */
    /* Transitions deferred to the animation pass — transitioning min-height
       in a grid + sticky parent caused jitter where the post-transition
       min-height read back to the collapsed value mid-frame. */
    transition: background-color 200ms ease;

    /* Default (desktop) collapsed grid:
       text column | number | video (auto-width, height-constrained) */
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "text num video";
    gap: var(--row-col-gap);
    overflow: hidden;                        /* clip video if it slightly overshoots */
}

/* On hover-capable devices, clicking the row body does nothing
   (the JS click handler returns early — hover is what expands).
   Reset the cursor so it doesn't lie about being interactive.
   The Learn More CTA inside still gets pointer via its own rule. */
@media (hover: hover) and (pointer: fine) {
    .service-row {
        cursor: default;
    }
}


/* Per-service accent assignment (ordered 01..07) */
.service-row[data-service="0"], .service-box[data-service="0"] { --service-color: var(--color-red-brand); }
.service-row[data-service="1"], .service-box[data-service="1"] { --service-color: var(--color-orange-comms); }
.service-row[data-service="2"], .service-box[data-service="2"] { --service-color: var(--color-blue-web); }
.service-row[data-service="3"], .service-box[data-service="3"] { --service-color: var(--color-purple-app-platform); }
.service-row[data-service="4"], .service-box[data-service="4"] { --service-color: var(--color-pink-name-ideation); }
.service-row[data-service="5"], .service-box[data-service="5"] { --service-color: var(--color-green-packaging); }
.service-row[data-service="6"], .service-box[data-service="6"] { --service-color: var(--color-yellow-graphic-design); }


/* ── Row children ─────────────────────────────────────────── */

.service-row__text {
    grid-area: text;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;             /* both title + tagline at top in collapsed state */
    align-self: start;                       /* override row's align-items: center so text sits at top of row */
    /* Vertical breathing room comes from the row's --row-pad-y (shared with the video, so both align). */
    gap: var(--space-2);
    min-width: 0;
}

.service-row__title {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-tight);
    font-size: clamp(28px, 3.2vw, 48px);
    line-height: 1;
    color: var(--color-white);
}

.service-row__tagline {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--color-greyish);
    letter-spacing: var(--tracking-body);
}

.service-row__chevron {
    grid-area: num;                         /* slides into the same area as number on desktop (hidden) */
    display: none;
    align-items: center;
    justify-content: center;
    color: var(--service-color);
    width: 18px;
    height: 26px;
    flex: 0 0 auto;
}

.service-row__chevron-icon {
    width: 100%;
    height: 100%;
    display: block;
    transform: rotate(90deg);               /* source arrow points right; rotate to point down */
    transition: transform 250ms var(--ease-primary);
}

.service-row.is-expanded .service-row__chevron-icon {
    transform: rotate(-90deg);              /* up when expanded */
}

.service-row__number {
    grid-area: num;
    font-family: var(--font-heading);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-tight);
    font-size: clamp(28px, 3vw, 48px);
    line-height: 1;
    color: var(--service-color);
    justify-self: end;
    margin-inline-end: var(--space-9);       /* desktop+small: extra breathing room before the video (sits on top of grid gap; tablet override below brings this back down) */
}

.service-row__description,
.service-row__foot {
    display: none;                          /* hidden on collapsed; revealed via .is-expanded */
}

.service-row__description {
    grid-area: desc;
    font-family: var(--font-body);
    font-size: var(--text-xs);
    line-height: var(--leading-relaxed);
    color: var(--color-greyish);
    max-width: 56ch;
}

.service-row__foot {
    grid-area: foot;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
}

.service-row__cta {
    position: relative;                       /* anchor for the marching-dashes overlay */
    display: inline-flex;
    align-items: center;
    padding: var(--btn-pad-y) var(--btn-pad-x);
    background: var(--color-white);
    color: var(--color-black2);
    border-radius: var(--radius);
    font-family: var(--font-btn);
    font-weight: var(--font-weight-btn);
    letter-spacing: var(--tracking-btn);
    font-size: var(--text-btn);
    line-height: 1;
    transform-origin: center;
    transition:
        background-color 200ms ease,
        color 200ms ease,
        transform 220ms var(--ease-primary);
}

.service-row__cta:hover {
    background: var(--color-black2);
    color: var(--color-white);
    transform: scale(1.08);
}

.service-row__number-small {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-tight);
    font-size: clamp(22px, 2.4vw, 36px);
    line-height: 1;
    color: var(--service-color);
}

.service-row__video-slot {
    grid-area: video;
    height: var(--video-h-collapsed);
    aspect-ratio: 16 / 9;                    /* width derives from height */
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--color-black);
    /* Height/aspect transition deferred to animation pass — same cascade issue
       observed on the row's min-height transition. */
}

.service-row__video-slot video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
}


/* ── Slides view: EXPANDED state (desktop/wide) ──────────── */
.service-row.is-expanded {
    min-height: var(--row-h-expanded);
    background: var(--row-bg-expanded);
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
    grid-template-rows: 1fr auto;
    grid-template-areas:
        "text desc video"
        "text foot video";
    gap: var(--space-5) var(--row-col-gap);
    /* row-pad-y inherited from the base rule — keeps padding equal top/bottom in expanded too */
}

.service-row.is-expanded .service-row__text {
    grid-row: 1 / span 2;
    align-self: stretch;                     /* override row's align-items: center so the wrapper fills the full row height */
    justify-content: space-between;          /* title top, tagline bottom */
}

.service-row.is-expanded .service-row__number { display: none; }
.service-row.is-expanded .service-row__description { display: block; align-self: start; }   /* description pinned to top of its cell */
.service-row.is-expanded .service-row__foot { display: flex; align-self: end; }              /* foot (CTA + number-small) pinned to bottom */

.service-row.is-expanded .service-row__video-slot {
    grid-row: 1 / span 2;
    height: var(--video-h-expanded);
    aspect-ratio: 16 / 9;                    /* desktop+small: keep 16:9 even when expanded (tablet override below uses 16:11) */
    align-self: center;
}


/* ============================================================
   BOXES VIEW (.services-boxes)
   ============================================================
   Single grid with explicit grid-area placement per breakpoint.
   Lets us reflow 3+4 ↔ 2+3+2 without restructuring markup.

   Wide/Desktop/Small: 8 cols × 2 rows
       row 1:  .  s0 s0 s1 s1 s2 s2 .       (3 boxes centered)
       row 2:  s3 s3 s4 s4 s5 s5 s6 s6      (4 boxes fill)

   Tablet: 6 cols × 3 rows
       row 1:  .  s0 s0 s1 s1 .             (2 boxes centered)
       row 2:  s2 s2 s3 s3 s4 s4            (3 boxes fill)
       row 3:  .  s5 s5 s6 s6 .             (2 boxes centered)

   Each box spans 2 cols, so:
       wide   box width = container / 4   (4 in widest row)
       tablet box width = container / 3   (3 in widest row)

   With aspect-ratio: 1, the height is the same.
   ============================================================ */
.services-boxes {
    padding: var(--space-8) var(--pad-x) var(--space-10);
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    grid-template-rows: auto auto;
    grid-template-areas:
        ".  s0 s0 s1 s1 s2 s2 ."
        "s3 s3 s4 s4 s5 s5 s6 s6";
}

.service-box[data-service="0"] { grid-area: s0; }
.service-box[data-service="1"] { grid-area: s1; }
.service-box[data-service="2"] { grid-area: s2; }
.service-box[data-service="3"] { grid-area: s3; }
.service-box[data-service="4"] { grid-area: s4; }
.service-box[data-service="5"] { grid-area: s5; }
.service-box[data-service="6"] { grid-area: s6; }


/* ── .service-box ─────────────────────────────────────────── */
.service-box {
    --service-color: var(--color-white);
    position: relative;
    display: flex;
    flex-direction: column;
    aspect-ratio: 1 / 1;                     /* perfect square */
    background: var(--row-bg);
    border: var(--guide-border);
    padding: 20px;                           /* spec: 20px on all sides */
    cursor: pointer;
    transition: background-color 200ms ease;
}

.service-box:hover {
    background: var(--row-bg-expanded);
}

/* Dashed border dedup — Wide/Desktop/Small (2 rows: 3 + 4)
   ───────────────────────────────────────────────────────
   Horizontal: drop LEFT border on non-leading boxes per row.
       Top row leaders: 0     → drop on 1, 2
       Bottom row leaders: 3  → drop on 4, 5, 6
   Vertical: drop BOTTOM border on the top row so the bottom row's
       top borders provide the shared line, no overlap.
       Top row: 0, 1, 2 → drop bottom */
.service-box[data-service="1"],
.service-box[data-service="2"],
.service-box[data-service="4"],
.service-box[data-service="5"],
.service-box[data-service="6"] {
    border-left: none;
}
.service-box[data-service="0"],
.service-box[data-service="1"],
.service-box[data-service="2"] {
    border-bottom: none;
}


/* ── Box internals ────────────────────────────────────────── */
.service-box__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    flex: 0 0 auto;
}

.service-box__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

.service-box__title {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-tight);
    font-size: clamp(18px, 1.8vw, 28px);
    line-height: 1;
    color: var(--color-white);
}

.service-box__tagline {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    color: var(--color-greyish);
    letter-spacing: var(--tracking-body);
}

.service-box__info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    background: none;
    cursor: pointer;
    flex: 0 0 auto;
    padding: 0;
}

.service-box__info-icon {
    width: 20px;
    height: auto;
    display: block;
}

/* Default: outer "tag" white, inner "i" dark. */
.service-box__info-icon .info-shape  { fill: var(--color-white);  transition: fill 180ms ease; }
.service-box__info-icon .info-letter { fill: var(--color-black2); transition: fill 180ms ease; }

/* Hover + active: outer becomes grid grey, inner becomes cream. */
.service-box__info:hover .service-box__info-icon .info-shape,
.service-box__info[aria-pressed="true"] .service-box__info-icon .info-shape {
    fill: var(--color-grid);
}
.service-box__info:hover .service-box__info-icon .info-letter,
.service-box__info[aria-pressed="true"] .service-box__info-icon .info-letter {
    fill: var(--color-white);
}

/* When the description is active (aria-pressed="true") AND the user hovers
   the icon again, swap BACK to the default white/dark scheme — signals
   "click to return to the video". */
.service-box__info[aria-pressed="true"]:hover .service-box__info-icon .info-shape {
    fill: var(--color-white);
}
.service-box__info[aria-pressed="true"]:hover .service-box__info-icon .info-letter {
    fill: var(--color-black2);
}

.service-box__media {
    flex: 1 1 auto;
    margin-top: var(--space-7);              /* 36px gap between head row (title + info) and the video */
    margin-bottom: var(--space-4);
    position: relative;
    overflow: hidden;
    border-radius: var(--radius);
    min-height: 0;                           /* let it shrink in flex parent */
}

.service-box__video-slot {
    position: absolute;
    inset: 0;
    /* Crossfade between video and description deferred to animation pass.
       (Transitions on opacity were not settling after class toggle.) */
}

.service-box__video-slot video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
}

.service-box__description {
    position: absolute;
    inset: 0;
    padding: 0;
    font-family: var(--font-body);
    font-size: var(--text-xs);
    line-height: var(--leading-relaxed);
    color: var(--color-greyish);
    overflow: auto;
    opacity: 0;
    pointer-events: none;
}

/* When info is active, swap media: hide video slot, show description. */
.service-box.is-info-active .service-box__video-slot {
    opacity: 0;
    pointer-events: none;
}
.service-box.is-info-active .service-box__description {
    opacity: 1;
    pointer-events: auto;
}

/* When info is active, the box itself does NOT change BG on hover
   (info-active disables the box-as-link affordance per spec Q55b). */
.service-box.is-info-active:hover {
    background: var(--row-bg);
}

/* When info is active, the box body is no longer clickable — reset
   the cursor to default so it doesn't lie about being interactive.
   Only the info button and the Learn More CTA stay clickable, so
   their cursors stay pointer (re-asserted below). */
.service-box.is-info-active {
    cursor: default;
}
.service-box.is-info-active .service-box__info,
.service-box.is-info-active .service-box__cta {
    cursor: pointer;
}

.service-box__foot {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.service-box__cta {
    position: relative;                       /* anchor for the marching-dashes overlay */
    display: inline-flex;
    align-items: center;
    padding: var(--btn-pad-y) var(--btn-pad-x);
    background: var(--color-white);
    color: var(--color-black2);
    border-radius: var(--radius);
    font-family: var(--font-btn);
    font-weight: var(--font-weight-btn);
    letter-spacing: var(--tracking-btn);
    font-size: var(--text-btn);
    line-height: 1;
    transform-origin: center;
    transition:
        background-color 200ms ease,
        color 200ms ease,
        transform 220ms var(--ease-primary);
}

.service-box__cta:hover {
    background: var(--color-black2);
    color: var(--color-white);
    transform: scale(1.08);
}

/* When the box is in info-active mode (description showing), the
   CTA's hover background swaps to --color-black (#262626) instead
   of --color-black2 (#191716) so it sits a touch lighter than the
   box behind it. Higher specificity wins over the rule above. */
.service-box.is-info-active .service-box__cta:hover {
    background: var(--color-black);
    color: var(--color-white);
}

.service-box__number {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-tight);
    font-size: clamp(20px, 2vw, 32px);
    line-height: 1;
    color: var(--service-color);
}


/* ============================================================
   RESPONSIVE — TABLET (768–1023px)
   ============================================================ */
@media (max-width: 1023px) {

    /* ── Slides: tablet collapsed ────────────────────────── */
    .service-row {
        grid-template-columns: minmax(0, 1fr) auto auto auto;
        grid-template-areas: "text chev num video";
        gap: var(--row-col-gap);
    }

    /* Tablet keeps the smaller num→video margin (the extra desktop bump
       is too much when the row is narrower). */
    .service-row__number {
        margin-inline-end: var(--space-5);
    }

    .service-row__chevron {
        display: inline-flex;
        grid-area: chev;
        justify-self: center;
    }

    /* ── Slides: tablet expanded ─────────────────────────── */
    .service-row.is-expanded {
        min-height: var(--row-h-expanded);
        /* Five-column grid: text | filler (eats slack) | chev | num | video.
           The empty filler column pushes chevron + number to the right edge,
           with chev sitting immediately left of num. Description spans
           filler + chev + num, so its right edge still lines up with the
           number's right edge. */
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto auto;
        grid-template-rows: auto 1fr auto;
        grid-template-areas:
            "text  .     chev  num   video"
            "text  desc  desc  desc  video"
            "foot  desc  desc  desc  video";
        gap: var(--space-4) var(--space-6);
        padding: var(--space-5) var(--pad-x);
    }

    .service-row.is-expanded .service-row__text {
        grid-row: 1 / span 2;
        justify-content: flex-start;
        /* gap stays at --space-2 (inherited from base) so tagline doesn't shift on expand */
    }

    .service-row.is-expanded .service-row__chevron {
        grid-area: chev;
        align-self: center;
    }
    .service-row.is-expanded .service-row__number {
        display: block;
        grid-area: num;
        align-self: center;
        margin-inline-end: 0;                /* sits at the right edge of its own auto-sized column */
    }

    .service-row.is-expanded .service-row__description {
        align-self: start;
    }

    .service-row.is-expanded .service-row__foot {
        grid-area: foot;
        justify-content: flex-start;
    }

    .service-row.is-expanded .service-row__foot .service-row__number-small {
        display: none;                      /* on tablet, the big number stays in chev-num cell */
    }

    .service-row.is-expanded .service-row__video-slot {
        grid-row: 1 / span 3;
        height: var(--video-h-expanded);
        aspect-ratio: 16 / 11;
        align-self: center;
    }


    /* (Boxes layout + border dedup for the 3-row 2+3+2 layout has been
       moved to a wider breakpoint below — it now applies to small
       screens AND tablet, while only Wide/Desktop keeps the 2-row 3+4. */
}


/* ============================================================
   RESPONSIVE — BOXES VIEW (small screens + tablet)
   Applies the 3-row 2+3+2 layout from <=1199px so small screens
   match tablet. Wide/Desktop (>=1200px) keeps the 2-row 3+4.
   Slides-view tablet behaviour is gated separately at <=1023px.
   ============================================================ */
@media (max-width: 1199px) {

    /* ── Boxes: 2 + 3 + 2 layout ─────────────────────────
       All boxes same size = container_width / 3.
       Top row (2 boxes) and bottom row (2 boxes) are centered. */
    .services-boxes {
        grid-template-columns: repeat(6, 1fr);
        grid-template-rows: auto auto auto;
        grid-template-areas:
            ".  s0 s0 s1 s1 ."
            "s2 s2 s3 s3 s4 s4"
            ".  s5 s5 s6 s6 .";
    }

    /* Dashed border dedup for the 3-row layout.
       Horizontal:
         Row 1 leaders: 0 → drop left on 1
         Row 2 leaders: 2 → drop left on 3, 4
         Row 3 leaders: 5 → drop left on 6
       Vertical:
         Row 1 (s0, s1) → drop BOTTOM (middle row's top draws the shared line)
         Row 2 (s2, s3, s4) → keep all borders (full middle row)
         Row 3 (s5, s6) → drop TOP (middle row's bottom draws the shared line)

       We use [data-service] selectors here (not a generic .service-box reset)
       because [data-service] has higher specificity than the desktop dedup
       and would otherwise leak through. Each box gets its 4 borders set
       explicitly. */

    /* Reinstate left borders that were dropped at desktop */
    .service-box[data-service="2"],
    .service-box[data-service="5"] {
        border-left: var(--guide-border);
    }

    /* Drop left where shared with the box to the left (per 3-row layout) */
    .service-box[data-service="1"],
    .service-box[data-service="3"],
    .service-box[data-service="4"],
    .service-box[data-service="6"] {
        border-left: none;
    }

    /* Vertical: row 1 → drop bottom; row 2 → keep all (re-add bottom on s2);
       row 3 → drop top */
    .service-box[data-service="0"],
    .service-box[data-service="1"] {
        border-bottom: none;
    }
    .service-box[data-service="2"] {
        border-bottom: var(--guide-border);   /* re-add bottom dropped at desktop (s2 is now in middle row) */
    }
    .service-box[data-service="5"],
    .service-box[data-service="6"] {
        border-top: none;
    }
}


/* ============================================================
   RESPONSIVE — MOBILE (≤767px)
   ============================================================ */
@media (max-width: 767px) {

    /* On mobile, the toggle is hidden and the title is always centered. */
    .services-cards {
        --bar-h: clamp(36px, 4.5vh, 48px);    /* shorter cream bar on mobile */
    }
    .services-cards__bar-inner {
        justify-content: center;
    }
    .services-cards__bar-toggle {
        display: none !important;
    }
    .services-cards__bar-title {
        position: static;
        transform: none;
        opacity: 0;                           /* hidden by default — only revealed once the bar is sticky on mobile */
        text-align: center;
        font-size: clamp(16px, 4vw, 20px);    /* shrink with the bar */
        line-height: 1.2;                     /* a slightly taller line-box so Indivisible's tall cap-height sits visually centered (line-height: 1 made it appear top-aligned) */
        transition: none;                     /* mobile: flip opacity instantly on .is-sticky toggle so the title doesn't visually lag behind when scrolling out of the section */
    }
    /* Reveal "Our Services" only when the bar is sticky on mobile.
       Override the desktop sticky rule's translate so it doesn't shift here. */
    .services-cards__bar.is-sticky .services-cards__bar-title {
        opacity: 1;
        transform: none;
    }

    /* ── Mobile typography overrides ─────────────────────── */
    .service-row__title {
        font-size: clamp(18px, 5vw, 22px);   /* smaller heading on mobile */
    }
    .service-row__tagline {
        font-size: 12px;                      /* even smaller than --text-xs on mobile (xs is 14px, this brings it to 12px) */
    }

    /* Chevron — top-aligned and slightly smaller on mobile, in both
       collapsed and expanded states. */
    .service-row__chevron {
        align-self: start;
        width: 14px;
        height: 20px;
    }

    /* Beat the tablet expanded rule (higher specificity) so the chevron
       stays top-aligned on mobile expanded too. */
    .service-row.is-expanded .service-row__chevron {
        align-self: start;
    }

    /* ── Slides: mobile collapsed ────────────────────────── */
    .service-row {
        grid-template-columns: minmax(0, 1fr) auto auto;
        grid-template-areas: "text chev video";
        gap: var(--space-4);
        /* Vertical padding inherited from base (--row-pad-y = 24px) so
           mobile gets the same breathing room desktop does. */
        min-height: 0;                        /* hug content — row height = max(text, video) so the title and video tops visually align */
    }

    /* Tighten the video so it doesn't dominate the row width on mobile. */
    .service-row__video-slot {
        height: clamp(70px, 11vh, 95px);     /* ~25% narrower than desktop default */
    }

    .service-row__chevron {
        grid-area: chev;
        display: inline-flex;
    }

    .service-row__number {
        display: none;                       /* number hidden on mobile collapsed */
    }

    /* ── Slides: mobile expanded ─────────────────────────── */
    .service-row.is-expanded {
        min-height: 0;                       /* let content drive height fully */
        grid-template-columns: minmax(0, 1fr) auto auto;
        grid-template-rows: auto auto auto;
        grid-template-areas:
            "text   chev  video"
            "desc   desc  desc"
            "foot   foot  foot";
        /* padding inherited from base (--row-pad-y) — keeps mobile expanded in step with collapsed */
        gap: var(--space-4);
    }

    .service-row.is-expanded .service-row__text {
        grid-row: 1;
        justify-content: center;
    }

    .service-row.is-expanded .service-row__description {
        display: block;
        align-self: start;
    }

    .service-row.is-expanded .service-row__foot {
        display: flex;
        justify-content: space-between;
        align-items: center;
    }

    .service-row.is-expanded .service-row__foot .service-row__number-small {
        display: inline;                     /* shows on mobile, in the foot row */
    }

    /* Re-hide the big number on mobile expanded — the tablet rule
       (max-width: 1023px) sets it to display:block and cascades down here.
       Mobile only uses the small purple number in the foot row. */
    .service-row.is-expanded .service-row__number {
        display: none;
    }

    /* Override the desktop/tablet expanded text-wrapper rules so on mobile
       the title + tagline stay pinned to the top of row 1 (matching the
       collapsed state's position), not stretched/centered across cells. */
    .service-row.is-expanded .service-row__text {
        grid-row: 1;
        align-self: start;
        justify-content: flex-start;
    }

    .service-row.is-expanded .service-row__video-slot {
        grid-row: 1;
        height: clamp(70px, 11vh, 95px);     /* match the smaller mobile size */
        aspect-ratio: 16 / 9;                /* stay 16:9 on mobile */
    }


    /* ── Boxes view is unavailable on mobile ─────────────── */
    .services-boxes {
        display: none !important;
    }
}


/* ============================================================
   Fallback when backdrop-filter isn't supported
   (Per spec Q9: no solid fallback wanted, but ensure the bar
   still has minimum readability if blur fails.)
   ============================================================ */
@supports not (backdrop-filter: blur(1px)) {
    .services-cards__bar-blur {
        background: rgba(255, 251, 240, 0.0);  /* invisible — let it stay transparent */
    }
}


/* ============================================================
   ENTRY ANIMATIONS — initial states
   ============================================================
   Elements start invisible/transformed and JS plays them in via
   GSAP when the section enters the viewport. The .is-revealed
   class on the section root flips them back to visible — used
   as a guard so reloading mid-page doesn't show pre-reveal state
   for a frame before JS picks up.
   ============================================================ */

/* Section starts in pre-reveal state. The JS handles per-element
   hidden states via gsap.set(...), but we need the OUTER row/box
   to start invisible too so users don't see content "pop" before
   the per-row trigger fires.

   Once each row's individual ScrollTrigger fires, JS removes the
   row-specific opacity by tweening it to 1. The .is-revealed class
   on the section root is also removed only via JS so reload mid-page
   doesn't show pre-reveal state. */
.services-cards.is-pre-reveal .service-row {
    opacity: 0;
}

.services-cards.is-pre-reveal .service-box {
    opacity: 0;
}

.services-cards.is-pre-reveal .services-cards__bar-toggle {
    opacity: 0;
}

/* SplitType character + word containers — set overflow hidden on
   word/char wrappers so the yPercent slide-up creates a clean reveal
   (characters seem to rise from beneath an invisible mask). */
.service-row__title .char,
.service-row__number .char,
.service-box__title .char,
.service-box__number .char {
    display: inline-block;
    will-change: transform, opacity;
}
.service-row__tagline .word,
.service-box__tagline .word {
    display: inline-block;
    will-change: transform, opacity;
}

/* Mask containers for the title + number to give the chars a
   "ceiling" to slide from. Without overflow:hidden, characters
   would visibly slide from below the line (which is fine but less
   polished). With it, they appear to materialize at the baseline. */
.service-row__title,
.service-box__title,
.service-row__number,
.service-box__number,
.service-row__tagline,
.service-box__tagline {
    overflow: hidden;
}


/* Bottom dashed border on rows: implemented as a pseudo-element
   so we can scaleX it from 0→1 (matching services-hero's
   reveal pattern). Replaces the static border-bottom for the
   reveal moment, then JS removes the .is-pre-reveal class and
   the rule below takes over. */
.service-row {
    position: relative;
}

.service-row::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 0;
    border-bottom: var(--guide-border);
    transform-origin: left center;
    transform: scaleX(0);
    pointer-events: none;
}

.services-cards.is-revealed .service-row::after {
    transform: scaleX(1);
    transition: transform 0.9s var(--ease-primary);
}

/* Stagger the dashed-line draw-in so each row's line follows the
   row's content reveal by ~120ms. We do this via JS for precision,
   but a CSS fallback gets us close. */
.service-row[data-service="0"]::after { transition-delay: 0.45s; }
.service-row[data-service="1"]::after { transition-delay: 0.55s; }
.service-row[data-service="2"]::after { transition-delay: 0.65s; }
.service-row[data-service="3"]::after { transition-delay: 0.75s; }
.service-row[data-service="4"]::after { transition-delay: 0.85s; }
.service-row[data-service="5"]::after { transition-delay: 0.95s; }
.service-row[data-service="6"]::after { transition-delay: 1.05s; }

/* The pseudo (::after) is the only divider between rows now —
   the row's own border-bottom is removed entirely. Removing it
   (rather than making it transparent) eliminates the sub-pixel
   rendering gap that some browsers showed between the expanded
   row's dark background and the dashed line above it. */
.services-cards .service-row {
    border-bottom: none;
}


/* Boxes: SVG dashed border draw-in.
   The boxes still use the existing CSS dashed border for layout,
   but we add a layered SVG border for the reveal animation. */
.service-box {
    position: relative;
}


/* Toggling state — used for the masked-sweep view-swap animation.
   When .is-toggling, the active view fades + scales slightly out. */
.services-cards.is-toggling .services-cards__rows,
.services-cards.is-toggling .services-boxes {
    opacity: 0;
    transform: scale(0.97);
    transition: opacity 320ms var(--ease-primary), transform 320ms var(--ease-primary);
}


/* ============================================================
   AMBIENT MICRO-INTERACTIONS
   ============================================================ */

/* Service number on hover (collapsed row, desktop only) — gentle
   pulse to make the number feel "alive". Disabled on mobile/tablet
   so it doesn't run when there's no hover. */
@media (hover: hover) and (pointer: fine) {
    @keyframes service-number-pulse {
        0%, 100% { transform: scale(1); }
        50%      { transform: scale(1.04); }
    }

    .service-row:hover .service-row__number {
        animation: service-number-pulse 2s var(--ease-gentle) infinite;
    }
}


/* ============================================================
   MARCHING DASHES — shared button hover overlay
   ============================================================
   An absolutely-positioned <svg> sibling inside each interactive
   button (.service-row__cta, .service-box__cta, .services-cards__bar-toggle).

   The <rect> matches the button's outer geometry (border-radius
   inherited via CSS variable) and animates stroke-dashoffset on
   hover for a clockwise "marching dashes" effect.

   Default state: invisible (opacity 0, no animation).
   Hover state:   opacity 1, animation runs continuously.

   Why SVG (not CSS border): a CSS dashed border has fixed dash
   spacing that can't be animated to "march" without hacks. SVG's
   stroke-dasharray + stroke-dashoffset gives perfect control,
   honors the rounded corners, and is GPU-friendly via transform.
   ============================================================ */
.cta-marching-border {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    overflow: visible;
    opacity: 0;
    transition: opacity 180ms ease;
}

.cta-marching-border rect {
    fill: none;
    stroke: var(--color-grid);                 /* dark grid grey by default — bar toggle overrides to greyish below */
    stroke-width: 1;
    stroke-dasharray: 2.5 2.5;                 /* slightly longer than --guide-border (2 2) — fractional values are valid in SVG stroke-dasharray */
    vector-effect: non-scaling-stroke;         /* keep the dashes 1px crisp regardless of button scale (1.08 hover) */
}

/* Sticky bar toggle uses a lighter dash color so the marching
   line is visible against its dark default background and reads
   correctly when the button hovers to cream. */
.services-cards__bar-toggle .cta-marching-border rect {
    stroke: var(--color-greyish);
}

/* Reveal + animate the dashes on hover for ALL three button types. */
.service-row__cta:hover .cta-marching-border,
.service-box__cta:hover .cta-marching-border,
.services-cards__bar-toggle:hover .cta-marching-border {
    opacity: 1;
}

.service-row__cta:hover .cta-marching-border rect,
.service-box__cta:hover .cta-marching-border rect,
.services-cards__bar-toggle:hover .cta-marching-border rect {
    animation: cta-march-cw 30s linear infinite;
}

/* Marches clockwise: stroke-dashoffset goes negative, which
   shifts the dash pattern in the direction the path was drawn
   (rect path is drawn clockwise by default in SVG). The total
   offset (-1000) is large enough that a single loop is invisible. */
@keyframes cta-march-cw {
    to { stroke-dashoffset: -1000; }
}
