/* Components/Shared/AboveFooterPanel.razor — the band above the footer.
 *
 * The presentation layer for the band MainLayout renders on every page the
 * header menu reaches, plus any page with a testimonial bound to it: quotes,
 * the "QHRM in Action" video library, and Success Stories. The qtm- prefix is
 * older than the last two and kept anyway — renaming forty selectors would say
 * nothing the file's own sections do not. The same two rules every sibling
 * sheet calls out apply, and the first one matters more here than anywhere else
 * in the codebase:
 *
 * 1. This component can appear above the footer of ANY page, so a leaking
 *    selector would leak everywhere at once. Every selector below is qtm-
 *    prefixed or a descendant of `.qtm`. No bare element selectors, and the
 *    @keyframes carry the prefix too.
 * 2. These rules are unlayered and Tailwind v4's utilities live inside @layer,
 *    so they win regardless of specificity.
 *
 * `.boxed-container` is the one class taken from the compiled bundle, and
 * deliberately: it is the site's responsive gutter, so this band lines up with
 * every other band on whatever page it lands under. Nothing here restyles it.
 *
 * WHY IT LOOKS LIKE THE HOME PAGE. The section reuses the vocabulary the rest of
 * the site already speaks — the two blurred aura discs and masked dot field of
 * .qh-sec, the pill eyebrow, the clamp()ed h2, the #4f46e5 → #7c3aed ramp — so
 * that arriving at it from any of forty different page designs feels like the
 * same site rather than a widget someone bolted on. It is reimplemented here
 * under its own prefix rather than reusing the qh- classes, because those belong
 * to home.css and this band must not break when that page is restyled.
 *
 * MOBILE FIRST, and by decision rather than habit: the base rules are the single
 * column, and the two min-width blocks add tracks. A card that has not been told
 * otherwise is full width and stacks, which is the right fallback for a section
 * whose card count is whatever an editor happened to bind.
 */

.qtm {
    position: relative;
    overflow: hidden;
    width: 100%;
    padding: 64px 0 68px;
    background: linear-gradient(180deg, #fbfaff 0%, #f6f5fc 58%, #f3f1fb 100%);
    border-top: 1px solid #ece9f8;
}

/* ── ambient background ─────────────────────────────────────────────── */

.qtm__aura,
.qtm__dots {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.qtm__aura i {
    position: absolute;
    display: block;
    border-radius: 50%;
    filter: blur(90px);
}

.qtm__aura i:nth-child(1) {
    width: min(70vw, 520px);
    height: min(60vw, 440px);
    top: -190px;
    right: -150px;
    background: radial-gradient(circle, rgba(129, 140, 248, .20), transparent 64%);
}

.qtm__aura i:nth-child(2) {
    width: min(60vw, 420px);
    height: min(52vw, 380px);
    bottom: -200px;
    left: -160px;
    background: radial-gradient(circle, rgba(167, 139, 250, .16), transparent 64%);
}

/* Masked at the edges so the dot field never meets the section boundary as a
   visible rectangle. */
.qtm__dots {
    opacity: .45;
    background-image: radial-gradient(circle at 1px 1px, rgba(79, 70, 229, .22) 1.1px, transparent 0);
    background-size: 26px 26px;
    -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000, transparent 72%);
    mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000, transparent 72%);
}

.qtm__inner {
    position: relative;
    z-index: 2;
}

/* ── heading ────────────────────────────────────────────────────────── */

.qtm__head {
    max-width: 680px;
    margin: 0 auto 34px;
    text-align: center;
}

.qtm__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 16px;
    padding: 7px 15px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .19em;
    text-transform: uppercase;
    color: #5b21b6;
    background: rgba(124, 58, 237, .07);
    border: 1px solid rgba(124, 58, 237, .16);
}

.qtm__title {
    margin: 0 0 14px;
    font-size: clamp(26px, 3.1vw, 40px);
    line-height: 1.14;
    letter-spacing: -.032em;
    font-weight: 800;
    color: #0b1020;
}

.qtm__lede {
    margin: 0;
    font-size: clamp(15px, 1.15vw, 17px);
    line-height: 1.65;
    color: #64748b;
}

/* ── slider ─────────────────────────────────────────────────────────── */

/* Arrows, the track, and the dots under it. Laid out by grid areas rather than
   by source order, which is what lets the same three children sit at the sides
   of the track on a desktop and under it on a phone without a second copy of
   the markup:
 *
 *   phone            desktop
 *   ┌───────────┐    ┌─┬───────┬─┐
 *   │   track   │    │‹│ track │›│
 *   ├─┬───────┬─┤    ├─┼───────┼─┤
 *   │‹│ dots  │›│    │ │ dots  │ │
 *   └─┴───────┴─┘    └─┴───────┴─┘
 *
 * The arrow columns are `auto`, so on a page with one page of quotes — where
 * neither arrow nor dot is rendered — they collapse and the track is the full
 * width of the container. Nothing has to know whether the slider is a slider. */
.qtm__slider {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
        "track track track"
        "prev  pips  next";
    align-items: center;
    row-gap: 22px;
    column-gap: 10px;
}

/* THE SCROLL PORT. One strip of pages, one page wide each, and moving between
   them is a scroll of this box — so the animation is the browser's own smooth
   scrolling and nothing here has a transform to keep in step with a page number.
 *
 * It is also what makes the strip work with the script blocked or still loading:
 * a real overflow container with snap points can be swiped, shift-wheeled and
 * arrow-keyed (hence tabindex) whether or not the buttons are alive.
 *
 * overflow-y CANNOT BE VISIBLE HERE — the moment one axis scrolls, `visible` on
 * the other computes to `auto` — so the cards' hover lift and drop shadow would
 * be clipped at the top and bottom edges. The vertical padding is the room they
 * need, inside the scrolling box rather than around it. */
.qtm__viewport {
    grid-area: track;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    /* Stops a horizontal swipe that runs off the end of the strip from becoming
       the browser's back gesture — which on a testimonial band would take the
       visitor off the page they were reading. */
    overscroll-behavior-x: contain;
    padding: 12px 0 20px;
    /* Firefox and the WebKit/Blink pseudo-element. The strip has its own arrows
       and dots, so a scrollbar under it is a third control saying the same
       thing — and on a desktop it would sit permanently across the cards. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.qtm__viewport::-webkit-scrollbar {
    display: none;
}

/* The focus ring belongs on the box a keyboard actually lands on, and it is
   drawn inside because an outline outside a scroll port is clipped by whatever
   contains it. */
.qtm__viewport:focus-visible {
    outline: 3px solid #4f46e5;
    outline-offset: -3px;
    border-radius: 20px;
}

.qtm__track {
    display: flex;
    align-items: stretch;
}

.qtm__nav--prev {
    grid-area: prev;
}

.qtm__nav--next {
    grid-area: next;
}

.qtm__pips {
    grid-area: pips;
}

/* ── grid ───────────────────────────────────────────────────────────── */

/* One page of quotes, and one slide of the strip.
 *
 * flex: 0 0 100% against a flex container whose own width is the scroll port's,
 * so a page is exactly one viewport wide however many cards are on it — which is
 * what lets the script scroll by `index * clientWidth` and never measure a card.
 * flex-shrink 0 is the load-bearing part: without it the pages would compress to
 * fit side by side and there would be nothing to scroll. */
.qtm__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
    align-items: stretch;
    flex: 0 0 100%;
    scroll-snap-align: start;
    /* The snap point is the page edge, and a drag that stops between two pages
       must land on one of them rather than resting mid-strip. */
    scroll-snap-stop: always;
}

/* A lone card is centred at reading width instead of spanning the container.
   Capped here rather than in the desktop block because the cap is about the
   line length of the quote, which is a property of the card and not of the
   viewport — at 1200px an 80-character measure is what makes it readable, and
   below 820px the rule simply never applies.
 *
 * Safe against the flex-basis above because --one means one quote, which means
 * one page: a cap that narrows a slide below the port would break the scroll
 * arithmetic, and there is nothing to scroll in the only case that sets it. */
.qtm__grid--one {
    max-width: 820px;
    margin: 0 auto;
}

/* ── arrows and dots ────────────────────────────────────────────────── */

.qtm__nav {
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    padding: 0;
    border-radius: 50%;
    color: #4a4569;
    background: #fff;
    border: 1px solid rgba(15, 23, 42, .09);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 8px 20px rgba(15, 23, 42, .06);
    cursor: pointer;
    /* -webkit-appearance for iOS Safari, which otherwise draws its own rounded
       button chrome over all of the above. */
    -webkit-appearance: none;
    appearance: none;
    transition: color .18s ease, border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}

.qtm__nav:hover:not(:disabled) {
    color: #4f46e5;
    border-color: rgba(79, 70, 229, .3);
    box-shadow: 0 2px 4px rgba(15, 23, 42, .06), 0 12px 26px rgba(49, 34, 148, .16);
    transform: scale(1.06);
}

.qtm__nav:focus-visible {
    outline: 3px solid #4f46e5;
    outline-offset: 3px;
}

/* At either end of the strip. The real attribute rather than a class, so the
   button is out of the tab order and announced as unavailable without any of
   this having to say so. */
.qtm__nav:disabled {
    opacity: .35;
    cursor: default;
}

.qtm__pips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 9px;
}

/* Buttons with no text, so the hit area is the box and nothing else — which at
   9px would be a target only a mouse can hit. The dot is drawn by ::before
   inside a 24px square, so a thumb gets the whole square. */
.qtm__pip {
    position: relative;
    display: block;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.qtm__pip::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 9px;
    height: 9px;
    margin: -4.5px 0 0 -4.5px;
    border-radius: 999px;
    background: #d9d3ef;
    transition: background-color .18s ease, width .18s ease, margin-left .18s ease;
}

.qtm__pip:hover::before {
    background: #b3a8e2;
}

/* The current page is a longer capsule rather than a bigger circle: at 9px a
   size change is two pixels nobody notices, and the width change is legible
   from across the section. */
.qtm__pip.is-current::before {
    width: 24px;
    margin-left: -12px;
    background: linear-gradient(90deg, #4f46e5, #7c3aed);
}

.qtm__pip:focus-visible {
    outline: 3px solid #4f46e5;
    outline-offset: 0;
}

/* ── card ───────────────────────────────────────────────────────────── */

/* One published quote: the words, the company that said them, and its mark.
 *
 * IT IS A PARAGRAPH, NOT A MEDIA CARD. There is no poster, no photo and no
 * person — so nothing is full-bleed, the padding is even on all four sides, and
 * the card is built around a column of text. The badge, the quote and the
 * company footer are the three bands, and only the middle one has a height that
 * depends on its content.
 *
 * WHICH IS WHY THE QUOTE TAKES THE FREE SPACE. Bound quotes are 40 words and
 * 400 words in the same row; align-items:stretch on the grid gives every card
 * the tallest one's height, and flex:1 on the quote spends the difference above
 * the rule rather than below it. Three logos in a row then sit on one line. */

.qtm__card {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 26px 26px 24px;
    overflow: hidden;
    background: #fff;
    border: 1px solid rgba(15, 23, 42, .06);
    /* 18px, matching the poster cards and the story cards further down. Two
       radii in one band is the kind of difference nobody names and everybody
       sees. */
    border-radius: 18px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 10px 30px rgba(49, 34, 148, .06);
    transition: box-shadow .22s ease, transform .22s ease, border-color .22s ease;
}

/* THE CORNER WAVE. Two curved bands sweeping out of the bottom-right corner: a
   pale one behind a saturated one, which is what gives the edge its depth
   instead of one flat purple wedge.
 *
 * Drawn as a background image and not as markup, because it is the same shape on
 * every card and putting an <svg> in the card would repeat it once per bound
 * quote — and any gradient inside it would need an id, which cannot be repeated
 * in one document. A data URI costs one parse and no DOM at all.
 *
 * preserveAspectRatio="none" so the two paths stretch to whatever box the card
 * gives them rather than keeping a ratio nobody chose. */
.qtm__card::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    z-index: 0;
    /* A corner accent and not a band across the foot of the card. The footer
       reserves the right 56px for it below, and past about this size the solid
       purple starts arriving under the company name — which no z-index fixes,
       because dark ink on #7c3aed is unreadable whichever one is on top. */
    width: min(46%, 152px);
    height: 68px;
    pointer-events: none;
    background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 100' preserveAspectRatio='none'><path fill='%23c4b5fd' fill-opacity='.5' d='M0 100h200V2C152 60 80 96 0 100z'/><path fill='%237c3aed' d='M26 100h174V44C158 76 98 96 26 100z'/></svg>") no-repeat right bottom / 100% 100%;
}

.qtm__card:hover {
    border-color: rgba(79, 70, 229, .2);
    box-shadow: 0 2px 4px rgba(15, 23, 42, .05), 0 22px 48px rgba(49, 34, 148, .15);
    transform: translateY(-4px);
}

/* The wave is a positioned pseudo-element, so it paints over the card's static
   children — including their text. Lifting the three bands above it is what
   keeps a long company name legible where the two overlap. */
.qtm__top,
.qtm__quote,
.qtm__by {
    position: relative;
    z-index: 1;
}

/* ── quote ──────────────────────────────────────────────────────────── */

/* The glyph and the stars share the card's first line, pushed to opposite
   edges: the quote mark opens the sentence and the stars are the verdict on it,
   and putting them on one row is what stops either from costing a band of its
   own above a paragraph that needs the height. */
.qtm__top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
}

.qtm__mark {
    display: block;
    flex: none;
    color: #7c3aed;
}

.qtm__stars {
    display: block;
    flex: none;
    /* Amber and not the band's indigo: a five-star row in the section's own
       purple reads as another piece of chrome, and the one thing it has to read
       as is a rating. */
    color: #f59e0b;
}

.qtm__quote {
    flex: 1;
    margin: 0;
    padding: 0;
    border: 0;
}

/* CLAMPED, AND THAT IS THE TRADE. Bound quotes run to a full paragraph — the
   longest on the site today is 90 words — and three of those side by side make a
   card twice as tall as the section around it. Six lines is what the design
   allows, so a long quote is cut with an ellipsis and the whole of it lives on
   the page the testimonial is bound to.
 *
 * line-clamp needs the -webkit- prefixed box for every browser that ships it,
 * including current Chrome and Safari; the unprefixed property is listed after
 * it for the ones that have moved on. */
.qtm__quote p {
    margin: 0;
    font-size: 14.5px;
    line-height: 1.75;
    color: #4b4668;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 6;
    line-clamp: 6;
    overflow: hidden;
    /* The quote is free text from the panel and can carry a URL or an unbroken
       string, which would otherwise push the card wider than its track. */
    overflow-wrap: anywhere;
}

/* ── company ────────────────────────────────────────────────────────── */

.qtm__by {
    display: flex;
    align-items: center;
    gap: 13px;
    margin-top: 22px;
    /* The right pad is the wave's lane. Without it the company name runs into
       the corner and a long one lands on solid purple. */
    padding: 26px 56px 0 0;
}

/* A short gradient bar instead of a rule across the card. A full-width hairline
   cuts the card in two and competes with the wave in the corner it would run
   into; a 34px mark reads as a break in the same paragraph. Absolute, because
   .qtm__by is a flex row and a ::before in flow would become a third item in
   it. */
.qtm__by::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 34px;
    height: 3px;
    border-radius: 999px;
    background: linear-gradient(90deg, #6d28d9, #a855f7);
}

/* A fixed disc the logo is fitted inside. companyLogo is whatever an editor
   uploaded — today's rows are a wide wordmark, a round emblem and a faint
   square — and three raw <img>s in a row each set their own height, which
   leaves the company names beside them on three different baselines. The white
   disc also gives a pale or transparent logo something to sit on. */
.qtm__logowrap,
.qtm__mono {
    flex: none;
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: 50%;
}

.qtm__logowrap {
    padding: 7px;
    background: #fff;
    border: 1px solid #eeeaf9;
    box-shadow: 0 2px 8px rgba(49, 34, 148, .1);
}

.qtm__logo {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
}

/* No logo uploaded: initials on the band's ramp, matching the story covers
   below. An empty disc reads as an image that failed to load. */
.qtm__mono {
    font-size: 14.5px;
    font-weight: 700;
    letter-spacing: .02em;
    color: #fff;
    background: linear-gradient(135deg, #4f46e5, #7c3aed);
}

.qtm__who {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

/* Both lines are capped so the footer is the same height on every card in a row
   — a three-line company name would push its own card taller than its
   neighbours, which is the one thing the clamped quote above is there to
   prevent. Two lines for the company because truncating a company name is worse
   than wrapping it; one for the job title, which is the shorter of the two and
   the less load-bearing. */
.qtm__company {
    font-size: 14.5px;
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: -.012em;
    color: #0b1020;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    overflow-wrap: anywhere;
}

.qtm__role {
    font-size: 12.5px;
    line-height: 1.35;
    color: #7b7699;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── QHRM in Action ─────────────────────────────────────────────────── */

/* ── the video facade ───────────────────────────────────────────────── */

/* Shared by the poster cards in the grid below and by the iframe that replaces
   one on click. It sat with the quote cards while those opened with a video of
   their own; the grid is the only thing left that plays anything, so the rules
   moved down here to it. Unchanged otherwise — the ratio box, the scrim, the
   disc and the duration pill are what .qtm__actcard overrides further down.
 *
 * A ratio box, because neither the poster nor the swapped-in iframe has an
 * intrinsic size the card should inherit. Fixed heights letterbox at one
 * breakpoint and crop at the next. */
.qtm__frame {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #0b1020;
    border: 0;
}

.qtm__poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* YouTube's hqdefault is 4:3 with black bars baked in; cover on a 16:9 box
       crops them back off rather than showing bars inside bars. */
    transition: transform .4s ease;
}

/* Darkens the poster from the bottom so the play button and the duration hold
   their contrast over a bright frame. */
.qtm__scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(11, 16, 32, .10) 0%, rgba(11, 16, 32, .48) 100%);
}

.qtm__playbtn {
    position: absolute;
    top: 50%;
    left: 50%;
    display: grid;
    place-items: center;
    width: 58px;
    height: 58px;
    margin: -29px 0 0 -29px;
    border-radius: 50%;
    color: #fff;
    background: linear-gradient(135deg, #4f46e5, #7c3aed);
    box-shadow: 0 10px 26px rgba(49, 34, 148, .42);
    transition: transform .22s ease, box-shadow .22s ease;
}

/* The glyph is optically centred, not geometrically: a triangle's visual centre
   sits left of its bounding box. */
.qtm__playbtn svg {
    margin-left: 3px;
}

.qtm__duration {
    position: absolute;
    right: 10px;
    bottom: 10px;
    padding: 3px 8px;
    border-radius: 6px;
    font-size: 11.5px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: #fff;
    background: rgba(11, 16, 32, .78);
}

/* ── the strip ──────────────────────────────────────────────────────── */

/* The video library, slid three at a time. The poster, scrim, play button and
   duration pill are the shared facade defined just above; what is new here is
   the card built around them — the sentence the clip is of and the person who
   said it, under the poster rather than beside it.
 *
 * WHY IT IS A SLIDER AND NOT A PAGER ANY MORE. The grid used to be pages of
 * three swapped by `hidden`, driven by real "?av=N" links — so every arrow and
 * every number wrote a query parameter and a fragment into the address bar. The
 * brief is that moving through the videos must leave the URL alone, so this
 * block now borrows the quote slider's construction wholesale: the same
 * .qtm__slider grid, the same .qtm__viewport scroll port, the same arrows and
 * dots. Almost nothing below is a second copy of those rules — only the slide
 * and the card are new. */

.qtm__act {
    margin-top: 52px;
    padding-top: 44px;
    border-top: 1px solid #e8e4f6;
}

/* ── head ───────────────────────────────────────────────────────────── */

/* Centred, with a pill over it and a lede under it — the head qhrm.io gives
   this strip, and the same three parts .qtm__head gives the quotes. It replaced
   a left-aligned h2 with a gradient rule above it because the strip stopped
   being a row of thumbnails hanging off the quotes: it is a block of its own
   now, and the eyebrow is what says so. */
.qtm__acthead {
    max-width: 680px;
    margin: 0 auto 34px;
    text-align: center;
}

/* The same pill as .qtm__eyebrow, plus the camera glyph the live design puts in
   front of the words. Not a shared class with it, because the two are never on
   the same page as each other and the copy inside them differs — this one names
   the block, that one names the section. */
.qtm__acteyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 16px;
    padding: 7px 15px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .19em;
    text-transform: uppercase;
    color: #5b21b6;
    background: rgba(124, 58, 237, .07);
    border: 1px solid rgba(124, 58, 237, .16);
}

.qtm__acteyebrow svg {
    flex: none;
    color: #7c3aed;
    /* The pill's letter-spacing is on the text, and an inline SVG inherits it as
       a gap after itself — which pushes the glyph a full letter away from the
       word it belongs to. */
    margin-right: -5px;
}

.qtm__acttitle {
    margin: 0 0 14px;
    font-size: clamp(26px, 3.1vw, 40px);
    line-height: 1.14;
    letter-spacing: -.032em;
    font-weight: 800;
    color: #0b1020;
}

/* The product name in the section's ramp, which is how the live head sets it.
   background-clip on the span alone rather than on the h2, so only the one word
   takes the gradient and the rest stays ink.
 *
 * The colour is restated as a fallback before the clip: a browser that supports
 * neither leaves `color: transparent` on a transparent background and the word
 * disappears entirely. */
.qtm__actword {
    color: #4f46e5;
    background: linear-gradient(90deg, #4f46e5, #7c3aed);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

.qtm__actlede {
    margin: 0;
    font-size: clamp(15px, 1.15vw, 17px);
    line-height: 1.65;
    color: #64748b;
}

/* ── slide ──────────────────────────────────────────────────────────── */

/* One slide of the strip, and the same construction as .qtm__grid: exactly one
   viewport wide however many cards are on it, which is what lets the script
   scroll by `index * clientWidth` and never measure a card. flex-shrink 0 is the
   load-bearing part — without it the slides would compress to sit side by side
   and there would be nothing to scroll.
 *
 * align-items:stretch is what keeps three cards with a one-line, a two-line and
 * a three-line quote the same height as each other. */
.qtm__actgrid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
    align-items: stretch;
    flex: 0 0 100%;
    scroll-snap-align: start;
    scroll-snap-stop: always;
}

/* ── card ───────────────────────────────────────────────────────────── */

/* A figure and not one big link: the poster is the anchor, and the words under
   it are not. Which is also what makes click-to-play safe — the script replaces
   the anchor with an iframe, and anything inside it would go with it. */
.qtm__actcard {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: 0;
    overflow: hidden;
    background: #fff;
    border: 1px solid rgba(15, 23, 42, .06);
    /* 18px, the same radius as the quote cards above rather than qhrm.io's 16.
       Two radii in one band is the kind of difference nobody names and everybody
       sees. */
    border-radius: 18px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 12px 28px rgba(49, 34, 148, .08);
    transition: box-shadow .24s ease, transform .24s ease, border-color .24s ease;
}

.qtm__actcard:hover {
    transform: translateY(-4px);
    border-color: rgba(79, 70, 229, .2);
    box-shadow: 0 2px 4px rgba(15, 23, 42, .06), 0 22px 46px rgba(49, 34, 148, .2);
}

/* ── the poster half ────────────────────────────────────────────────── */

/* A ratio box, because neither the poster nor the iframe that replaces it has an
   intrinsic size the card should inherit. Fixed heights letterbox at one
   breakpoint and crop at the next.
 *
 * Black and not the section's ink: the thumbnail is cropped to fill, but a frame
 * that fails to load should letterbox against the same black the player uses. */
.qtm__actmedia {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: #000;
    text-decoration: none;
    cursor: pointer;
}

/* An inner hairline over the poster, so the black rectangle does not read as a
   hole punched in the top of a white card. Drawn above the image, which is what
   makes it survive the image loading. */
.qtm__actmedia::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .1);
    transition: box-shadow .24s ease;
}

.qtm__actcard:hover .qtm__actmedia::after {
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22);
}

.qtm__actcard:hover .qtm__poster {
    transform: scale(1.04);
}

.qtm__actmedia:focus-visible {
    outline: 3px solid #4f46e5;
    /* Inside, because the card clips its overflow and an outline drawn outward
       would be cut off along the two edges the poster shares with it. */
    outline-offset: -3px;
}

/* The facade keeps qhrm.io's white disc and black glyph — that is the part of
   this section a visitor recognises, and the quote cards above keep their
   indigo pill. What changed is the wash under it. A flat 30% black over the
   whole frame greys out twenty-two thumbnails at once; instead the tint is
   nearly nothing at the top, deepens towards the bottom, and a soft radial
   pool sits behind the button. The disc keeps its contrast, the faces in the
   posters keep their colour. */
.qtm__actmedia .qtm__scrim {
    background:
        radial-gradient(circle at 50% 50%, rgba(0, 0, 0, .4), transparent 46%),
        linear-gradient(180deg, rgba(11, 16, 32, .06) 0%, rgba(11, 16, 32, .34) 100%);
    transition: opacity .24s ease;
}

.qtm__actcard:hover .qtm__scrim {
    opacity: .82;
}

.qtm__actmedia .qtm__playbtn {
    z-index: 1;
    width: 58px;
    height: 58px;
    margin: -29px 0 0 -29px;
    color: #4f46e5;
    background: rgba(255, 255, 255, .95);
    /* The halo is a spread-only shadow, so it reads as light coming off the
       disc rather than as a second border. It widens on hover, which is what
       makes the button feel pressed towards you. */
    box-shadow: 0 8px 22px rgba(0, 0, 0, .3), 0 0 0 7px rgba(255, 255, 255, .14);
}

.qtm__actcard:hover .qtm__playbtn {
    transform: scale(1.07);
    background: #fff;
    box-shadow: 0 12px 28px rgba(0, 0, 0, .34), 0 0 0 12px rgba(255, 255, 255, .18);
}

.qtm__actmedia .qtm__playbtn svg {
    width: 26px;
    height: 26px;
}

/* The company mark in the corner of the poster, as the live cards carry it. A
   white tile rather than a bare image, because these are uploaded logos — a
   dark wordmark on a dark frame would vanish, and every one of them has to sit
   on something. */
.qtm__actbadge {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    padding: 5px;
    border-radius: 10px;
    background: rgba(255, 255, 255, .94);
    box-shadow: 0 2px 10px rgba(11, 16, 32, .28);
}

.qtm__actbadge img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
}

/* The swapped-in player takes the poster's box, so nothing reflows the moment
   someone presses play — and the top corners only, because the card's own
   radius is what rounds the outside. */
.qtm__actgrid .qtm__frame {
    border-radius: 18px 18px 0 0;
}

/* ── the words half ─────────────────────────────────────────────────── */

.qtm__actbody {
    display: flex;
    flex: 1;
    flex-direction: column;
    padding: 20px 20px 18px;
}

/* The glyph beside the words rather than above them, so a two-line quote does
   not cost three lines of card. It is an inline SVG and not a CSS "\201C" for
   the same reason the quote cards' is: a pseudo-element's content is read aloud
   by some screen readers, and "left double quotation mark" ahead of every card
   is noise. */
.qtm__actquote {
    display: flex;
    gap: 10px;
    margin: 0;
    padding: 0;
    border: 0;
}

.qtm__actglyph {
    flex: none;
    display: block;
    margin-top: 1px;
    color: #7c3aed;
    opacity: .55;
}

/* CLAMPED, AND THAT IS THE TRADE. These captions run from four words to a full
   sentence, and three cards side by side have to end level with each other.
   Three lines is what the design allows; the whole of a long one is on the page
   the clip is bound to.
 *
 * line-clamp needs the -webkit- prefixed box for every browser that ships it,
 * including current Chrome and Safari; the unprefixed property is listed after
 * it for the ones that have moved on. */
.qtm__actquote p {
    margin: 0;
    font-size: 14px;
    line-height: 1.7;
    color: #4b4668;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
    /* Captions are free text from the panel and can carry a URL or an unbroken
       string, which would otherwise push the card wider than its track. */
    overflow-wrap: anywhere;
}

/* When there is no quote bound to the page the videos open the band, so the
   rule and the space that separated them from the cards above have nothing to
   separate. */
.qtm__act--lead {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* ── tablet ─────────────────────────────────────────────────────────── */

@media (min-width: 640px) {
    .qtm {
        padding: 76px 0 80px;
    }

    .qtm__head,
    .qtm__acthead {
        margin-bottom: 40px;
    }

    .qtm__card {
        padding: 28px 28px 24px;
    }

    .qtm__grid--two,
    .qtm__grid--many {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 22px;
    }

    .qtm__actgrid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 22px;
    }
}

/* ── laptop ─────────────────────────────────────────────────────────── */

/* Where the arrows move out of the row under the track and into the gutters
   either side of it. Held back to 900px because below that the three columns
   are 42px + cards + 42px and the cards lose more width than the arrows are
   worth. */
@media (min-width: 900px) {
    .qtm__slider {
        grid-template-areas:
            "prev track next"
            ".    pips  .";
        column-gap: 18px;
    }
}

/* ── desktop ────────────────────────────────────────────────────────── */

@media (min-width: 1024px) {
    .qtm {
        padding: 96px 0 100px;
    }

    .qtm__head,
    .qtm__acthead {
        margin-bottom: 52px;
    }

    /* A lone card — already capped and centred — is the one place there is room
       to set the quote as a pull quote rather than as body copy. Nothing else
       about it changes: it is the same card, given the size it earns by being
       the only thing in the row. */
    .qtm__grid--one .qtm__card {
        padding: 40px 44px 34px;
    }

    .qtm__grid--one .qtm__quote p {
        font-size: 17px;
        line-height: 1.78;
        /* One card across the full measure fits far more in six lines than three
           cards do, and there is no row for it to stay level with. */
        -webkit-line-clamp: 10;
        line-clamp: 10;
    }

    .qtm__grid--one .qtm__card::after {
        width: min(46%, 260px);
        height: 96px;
    }

    /* Two stays two — a pair centred in three tracks leaves a hole. Only
       three-or-more opens the third column. */
    .qtm__grid--two {
        max-width: 940px;
        margin: 0 auto;
    }

    .qtm__grid--many {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 24px;
    }

    /* Three across is what makes a page one full row, which is why the pager
       counts in threes. */
    .qtm__actgrid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 24px;
    }

    .qtm__act {
        margin-top: 60px;
        padding-top: 48px;
    }

    .qtm__act--lead {
        margin-top: 0;
        padding-top: 0;
    }
}

/* ── reduced motion ─────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {

    /* The slide becomes a jump. testimonial-video.js reads the same query before
       it calls scrollTo, because scroll-behavior only governs the CSS side and a
       scripted `behavior: 'smooth'` overrides it. */
    .qtm__viewport {
        scroll-behavior: auto;
    }

    .qtm__card,
    .qtm__nav,
    .qtm__pip::before,
    .qtm__poster,
    .qtm__playbtn,
    .qtm__actcard,
    .qtm__actmedia::after,
    .qtm__scrim {
        transition: none;
    }

    .qtm__card:hover,
    .qtm__nav:hover,
    .qtm__actcard:hover {
        transform: none;
    }

    .qtm__actcard:hover .qtm__poster {
        transform: none;
    }

    .qtm__actcard:hover .qtm__playbtn {
        transform: none;
    }
}
