/* Components added for pages rebuilt in the OM design that the original OM page did not need.
   Kept separate from design-om.css because that file is generated. */

/* Point cards reuse the om-card-grid look but relax its geometry: the original is a rigid
   36px-icon + 3-row grid at fixed per-card heights, built for icon/h3/p/link. These cards
   carry only a label and a line of text, and have no icon. */
.om-points-grid article,
.om-points-grid article:nth-child(n+4){display:block;height:auto;min-height:0;width:auto;padding:24px 22px;border-radius:10px}
.om-points-grid h3{margin:0 0 6px;font-size:16px;line-height:22px}
.om-points-grid p{margin:0;font-size:14px;line-height:20px;color:#616b76;min-height:0}

/* Lead paragraph under a section heading. */
.om-section-lead{max-width:760px;margin:0 auto 28px;color:#616b76;font-size:15.5px;line-height:1.55;text-align:center}
.om-ai-block h2{text-align:center}


/* The OM hero is a fixed 1070px tall because it contains a 410px video and a stats row.
   Pages reusing the design without those need the height to collapse. Two classes so this
   beats the breakpoint rule in the generated design-om.css. */
/* height AND min-height: the generated sheet sets height:1070px at desktop and
   min-height:860px below 600px, both sized for OM's own hero (a 410px video plus a stats
   row). A compact hero has neither, so the min-height left ~300px of empty navy under the
   review logos on a phone. */
.om-hero.om-hero--compact{height:auto;min-height:0}

/* om-btn is height:56px with line-height:56px, so a label that wraps spills out of the box.
   Allow growth while keeping the same single-line appearance. */
.om-hero--compact .om-btn{height:auto;min-height:56px;line-height:1.25;padding:15px 26px;text-align:center}

/* .om-hero__inner is a column flex with align-items:center, so .om-hero-actions is sized
   fit-content, not stretched. OM's hero holds two buttons with explicit widths, which resolves
   cleanly; a compact hero with a single button collapsed to roughly its own text width and the
   label spilled out of the orange box. The generated width comes from
   `.om-hero-actions .om-btn:first-child`, so it is child-count sensitive -- sized explicitly here
   instead. Desktop only, so the generated mobile treatment (a full-width stacked column) stands. */
@media (min-width: 768px) {
    .om-hero--compact .om-hero-actions { width: auto; height: auto; }
    .om-hero--compact .om-hero-actions .om-btn:first-child {
        width: auto;
        min-width: 300px;
        padding: 0 30px;
    }
}

/* .om-intro is a fixed 277px because OM's intro carries a heading AND a paragraph.
   Used with a heading alone it leaves a large void. */
.om-intro.om-intro--compact{height:auto;min-height:0;padding:56px 24px 8px}

/* ------------------------------------------------------------------------------------------
   Components introduced by the Mortgage CRM comp (templates/BNTouch_CRM_Page_v8-commented.pdf).
   They live here rather than in design-om.css, which is generated from the live OM page and is
   overwritten on every run of om-extract.php.
   ------------------------------------------------------------------------------------------ */

/* Small orange label above a section heading. Centred by default, because four of its five
   uses in the CRM comp sit above a centred heading; --left is for the Integrations hero. */
.om-eyebrow {
    margin: 0 0 10px;
    text-align: center;
    color: var(--orange, #f7941e);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
}
.om-eyebrow--onDark { color: #f9b233; }
.om-eyebrow--left { text-align: left; }

.om-review-sites {
    list-style: none;
    padding: 0;
    justify-content: center;
}

.om-hero-lead {
    max-width: 640px;
    margin: 0 auto 30px;
    font-size: 16px;
    line-height: 1.6;
    color: #cbd3dd;
}

/* OM's hero actions are two buttons; this comp pairs a button with a plain text link. */
.om-hero-actions .om-text-link {
    color: #f9b233;
    font-weight: 600;
    align-self: center;
}

.om-stats div { min-width: 200px; }

.om-section--pale { background: var(--pale, #f6f7f9); }

.om-section--dark {
    background: #032d60;
    color: #fff;
}
.om-section--dark h2 { color: #fff; }

/* OM's card is a grid built for a 36px icon column beside the text. These cards stack the icon
   above the heading, like the comp, so they opt out of the grid the same way .om-points-grid does. */
.om-card-grid--dark article {
    display: block;
    height: auto;
    min-height: 0;
    background: #0a3973;
    border: 1px solid #154d90;
    box-shadow: none;
    color: #cfd8e3;
}
.om-card-grid--dark h3 { color: #fff; }
.om-card-grid--dark p { color: #b9c4d1; }
.om-card-grid--dark i {
    display: flex;
    margin-bottom: 14px;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: 10px;
    background: var(--orange, #f7941e);
    color: #032d60;
    font-size: 22px;
}

/* The cycle ring. The stages are an <ol> so they stay a readable sequence for screen readers
   and on narrow screens, where the ring collapses to a plain list. */
.om-cycle {
    position: relative;
    /* The 740px box is set by the SVG, but the outermost the ring actually paints is the halo at
       r=224 plus its 22px stroke -- 235 of 296, leaving ~61px of empty margin on each edge. The
       negative bottom margin reclaims part of that so the band does not end in a dead strip. */
    margin: 40px 0 -34px;
    min-height: 592px;
    display: grid;
    place-items: center;
}
.om-cycle__ring {
    list-style: none;
    margin: 0;
    padding: 0;
    width: 100%;
    max-width: 592px;
    aspect-ratio: 1;
    position: relative;
}
.om-cycle__ring li {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 240px;
    margin: -40px 0 0 -120px;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 20px;
    background: #fff;
    border: 1px solid var(--line, #dbe0e6);
    border-radius: 10px;
    box-shadow: 0 1px 4px #15304c0d;
    font-size: 14.5px;
    line-height: 1.35;
    font-weight: 600;
    color: #032d60;
}
/* Five nodes at 72deg intervals, starting at the top. translate() puts the node on the ring;
   the second translate() is undone per-node so the card itself stays upright. */
.om-cycle__ring li:nth-child(1) { transform: translate(0, -224px); }
.om-cycle__ring li:nth-child(2) { transform: translate(213px, -69px); }
.om-cycle__ring li:nth-child(3) { transform: translate(132px, 181px); }
.om-cycle__ring li:nth-child(4) { transform: translate(-132px, 181px); }
.om-cycle__ring li:nth-child(5) { transform: translate(-213px, -69px); }
.om-cycle__ring b {
    flex: 0 0 auto;
    display: flex;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--orange, #f7941e);
    color: #fff;
    align-items: center;
    justify-content: center;
    font-size: 14px;
}
.om-cycle__hub {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 180px;
    text-align: center;
    text-wrap: balance;
    font-size: 13px;
    line-height: 1.5;
    color: #616b76;
    margin: 0;
}
.om-cycle__brand {
    display: block;
    font-size: 30px;
    font-weight: 700;
    color: var(--orange, #f7941e);
    margin-bottom: 4px;
}
.om-cycle__brand em { font-style: normal; color: #032d60; }
.om-cycle__feed {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-435px, -245px);
    width: 200px;
    margin: 0;
    color: var(--orange, #f7941e);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.4;
    text-align: right;
}

/* ---- Graphic ground for the stages ---------------------------------------------------------
   Colour, not shapes. This section has had three goes at a background: five concentric arcs,
   then a dot grid, then each stage's number set at 150px. Every one was rejected for the same
   reason -- they had edges, they sat near the copy, and they pulled the eye off it. The numerals
   were the clearest case: perfectly placed, clear of every line of text by 31px or more, and
   still competing, because a 150px glyph reads as something to look at.

   So the background carries no shape at all now. Four soft radial washes, no edge anywhere in
   them, nothing to resolve. They are far stronger than the arcs ever were -- the warm one runs
   at roughly four times the alpha -- and they cost nothing to read past, because there is nothing
   in them to read. The eye registers a tinted ground and moves on.

   Weighted left and to the top and bottom edges, so the strongest colour falls on the label
   column and the section's margins rather than behind the cards. Percentage sizes, not pixels,
   so the wash scales with the section instead of bunching at one width. */
.om-stages-section { position: relative; overflow: hidden; }
/* Content above the wash. The section's own children are the two .om-container blocks. */
.om-stages-section > .om-container { position: relative; z-index: 1; }
.om-stages-section::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        radial-gradient(60% 34% at 4% 2%,   #f7941e3d, #0000 72%),
        radial-gradient(46% 26% at 0% 42%,  #2f6ea81f, #0000 70%),
        radial-gradient(52% 30% at 10% 82%, #f7941e2e, #0000 72%),
        radial-gradient(70% 22% at 88% 98%, #16263b1a, #0000 72%);
}

/* One stage of the cycle: a numbered label column with the 1-to-5 rail running through it,
   then that stage's features as cards.

   Two things were unreadable before. The five stages were separated only by a hairline, so
   nothing said "these are steps in an order" -- the numerals were the only clue and they are
   32px of pale orange 300px apart. And the features were bare headings on the section
   background, two columns of loose text with no edges, so 26 of them read as one grey field.

   The rail fixes the first: it is the same device the cycle graphic above uses on mobile, so
   the two sections now explain the sequence the same way. The cards fix the second -- each
   feature gets an edge, and because grid rows stretch, every row of cards squares off at the
   same height instead of ending ragged. */
.om-stage {
    --label-col: 296px;
    --stage-gap: 44px;
    --stage-pad: 44px;
    position: relative;
    display: grid;
    grid-template-columns: var(--label-col) 1fr;
    gap: var(--stage-gap);
    padding: var(--stage-pad) 0;
}
/* The rail hangs off the list, not off each stage. Five segments butted together carried a
   flat colour fine, but a gradient on each would restart five times; on the list it is one
   element and the colour can run the whole length. .om-container has no padding and each stage
   spans its full content width, so left:21px lands on the badge centres exactly -- measured, at
   every breakpoint.
   Trimmed by 66px at both ends (44px stage padding + half the 44px badge) so it starts and
   stops inside the first and last badge rather than in mid-air.
   Orange at the top, grey by the third stage: the sequence visibly begins somewhere. */
.om-stage-list { position: relative; }
.om-stage-list::before {
    content: "";
    position: absolute;
    left: 21px;
    top: 66px;
    bottom: 66px;
    width: 2px;
    background: linear-gradient(var(--orange, #f7941e), #c8d0da 34%, #dbe0e6);
}

.om-stage__label {
    position: relative;
    align-self: start;
    padding-left: 64px;
}
.om-stage__label b {
    position: absolute;
    left: 0;
    top: 0;
    display: flex;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--orange, #f7941e);
    color: #fff;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    font-weight: 700;
    /* First ring is the section's own background, so the rail passes behind the badge instead
       of butting into its edge. The two after it are the ripple the ring above the section
       makes, picked up at the point the rail passes through. */
    box-shadow:
        0 0 0 7px var(--pale, #f6f7f9),
        0 0 0 8px #f7941e40,
        0 0 0 17px #f7941e1a;
}
.om-stage__label h3 {
    margin: 0 0 8px;
    font-size: 21px;
    line-height: 1.25;
    letter-spacing: -.01em;
}
/* line-height is set, not inherited: the section's 1.8 made a two-line lede look like two
   unrelated sentences floating away from the title they belong to.

   #4a5464, not the #616b76 the rest of the page uses for muted text. This has moved twice, both
   times for the same reason: the ledes are the smallest text in the section and they sit in the
   label column, which is where the wash is most saturated and where the chart's grid and columns
   cross. #616b76 measured 4.54:1 there -- passing AA by 0.04 -- and #566070 4.80:1 once the
   chart went in. This holds above 5.5:1 with the wash and the chart both under it, which is the
   margin to keep if the background is going to keep being worked on. It reads better on white
   too, and is still clearly secondary against the #032d60 headings. */
.om-stage__label p {
    margin: 0;
    font-size: 14.5px;
    line-height: 1.5;
    color: #4a5464;
}


/* The section's background: one business chart across the whole of it.
   It is one drawing rather than one per stage because five separate compositions read as five
   pictures -- each began and ended at its own stage boundary, so the boundaries became part of
   the composition. This crosses all five without acknowledging any of them.

   Both dimensions stated. An absolutely positioned SVG is a replaced element with an intrinsic
   aspect ratio, so whichever one is left auto is derived from the other and that axis's offsets
   are ignored -- inset:0 alone silently sizes it from the viewBox rather than from the section.

   It runs behind the heading and the label column as well as the cards, so it is held where text
   can sit on it: verified with src/tools/contrast-onwash.sh, which reads the pixels actually
   painted under each line rather than walking ancestors for a background colour. */
.om-stages-art {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: .18;
    pointer-events: none;
}
/* Slate, not the near-black navy the rest of the page uses. What costs contrast under a mark is
   how far it drags the background down, not how opaque it is: in navy the heaviest element took
   a stage lede to 4.84:1, a third of a point off failing AA, where the same weight in slate
   holds above 5. Alpha alone could not buy that back without fading the chart to nothing. */
.om-art__col { fill: #5b6b7d; fill-opacity: .13; }
/* The one warm element, so the chart sits in the page's palette rather than reading as grey
   furniture, and so what the eye picks out is the line going up. Held lower than the rest wants
   to be: the line runs diagonally, which means it crosses the label column at whatever height it
   happens to be passing, and at .5 it took a stage lede to 4.80:1. The grid and columns are
   axis-aligned and can be placed around text; a diagonal cannot, so it pays for that in alpha. */
.om-art__trend {
    fill: none;
    stroke: var(--orange, #f7941e);
    stroke-opacity: .36;
    stroke-width: 8;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.om-art__node { fill: var(--orange, #f7941e); fill-opacity: .42; }

.om-stage__items {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}
.om-stage__items > div {
    border: 1px solid var(--line, #e4e7ee);
    border-radius: 3px;
    padding: 16px 18px;
    /* Not quite opaque, so a whisper of the panel carries under the card rather than stopping
       dead at its edge. 90%, not the 82% this started at: at 82% the artwork was legible enough
       through the card to put a visible bar behind a heading, which reads as a rendering fault.
       The panel is seen around the cards now, not through them. */
    background: #ffffffe6;
    /* The same shadow .om-cycle__ring li carries, so the nodes of the ring above and the cards
       that unroll it read as one surface rather than two unrelated card styles. */
    box-shadow: 0 1px 4px #15304c0d;
}
/* 700, not 400: these are the names someone scans the section for. */
.om-stage__items h4 {
    margin: 0 0 6px;
    font-size: 15px;
    font-weight: 700;
    line-height: 1.3;
    color: #032d60;
}
.om-stage__items p {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.55;
    color: #4a5464;
}

.om-payoff { text-align: center; }
.om-payoff h2 em { font-style: normal; color: var(--orange, #f7941e); }
/* ---- Signal chips ---------------------------------------------------------------------------
   The five things Opportunity Mining watches, one at a time, in the band that argues for it.

   All five are stacked in the same spot and shown in turn by delay -- 15s loop, 3s each, so a
   chip is up for 3s and the set repeats every 15. No JS and nothing moves in layout: the row is
   a fixed height and the chips are absolutely positioned inside it, so a longer label cannot
   shift the button underneath.

   Positive delays with fill-mode both, not the negative ones the scan used. Negative delays
   start a loop mid-flight, which is right for ambient motion that should already be running;
   here the five are a sequence, and it should begin at the first one.
   ---------------------------------------------------------------------------------------------- */
.om-signals {
    position: relative;
    height: 42px;
    margin: 0 0 26px;
}
.om-signal {
    position: absolute;
    left: 50%;
    top: 50%;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 8px 17px;
    border: 1px solid #ffffff2e;
    border-radius: 999px;
    background: #ffffff0f;
    font-size: 14.5px;
    font-weight: 600;
    white-space: nowrap;
    opacity: 0;
    /* The centring translate lives in the keyframes too -- an animation on transform replaces
       this one outright, and leaving it out of the frames throws every chip off centre. */
    transform: translate(-50%, -50%);
    animation: om-signal-cycle 15s linear infinite both;
    animation-delay: calc(var(--i) * 3s);
}
.om-signal svg {
    width: 17px;
    height: 17px;
    flex: 0 0 auto;
    fill: none;
    stroke: var(--orange, #f9b233);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
@keyframes om-signal-cycle {
      0% { opacity: 0; transform: translate(-50%, calc(-50% + 7px)); }
      3% { opacity: 1; transform: translate(-50%, -50%); }
     17% { opacity: 1; transform: translate(-50%, -50%); }
     20% { opacity: 0; transform: translate(-50%, calc(-50% - 7px)); }
    100% { opacity: 0; transform: translate(-50%, calc(-50% - 7px)); }
}

/* Reduced motion shows all five at once rather than freezing on one. Unlike the cycle artwork,
   which is decorative and simply goes, these carry information -- stopping the loop on chip one
   would hide four capabilities. */
@media (prefers-reduced-motion: reduce) {

    .om-signals {
        height: auto;
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        justify-content: center;
    }
    .om-signal {
        position: static;
        opacity: 1;
        transform: none;
        animation: none;
    }
}

@media (max-width: 900px) {
    /* The wash stays on mobile. The three shaped backgrounds before it had to be dropped
       here, because with the label column gone they landed under the cards; colour with no
       edge sits under a card perfectly happily. */
    .om-stage { grid-template-columns: 1fr; gap: 18px; padding: 32px 0; }
    /* The rail stays on mobile -- it is the only thing left carrying the sequence once the
       label sits above its own cards instead of beside them. Both columns clear it by the
       same 46px, so badge, title and cards share one left edge. 49px = 32px stage padding +
       half the 34px badge. */
    .om-stage-list::before { left: 16px; top: 49px; bottom: 49px; }
    .om-stage__label { padding-left: 46px; }
    .om-stage__label b {
        width: 34px; height: 34px; font-size: 16px;
        box-shadow: 0 0 0 5px var(--pale, #f6f7f9), 0 0 0 6px #f7941e40, 0 0 0 12px #f7941e1a;
    }
    .om-stage__label h3 { font-size: 19px; }
    .om-stage__items { margin-left: 46px; }
    .om-stage__items > div { padding: 14px 16px; }
    /* Below the ring's natural size the absolute positioning stops being legible, so the
       stages fall back to the ordered list they already are. */
    /* The -34px bottom margin reclaims dead space left by the ring's halo. With the ring
       collapsed to a list there is no halo, so it was pulling the closing line down onto the
       section's edge -- no gap at all between it and the band below. */
    .om-cycle { min-height: 0; margin-bottom: 0; }
    .om-cycle__ring {
        aspect-ratio: auto;
        max-width: none;
        display: grid;
        gap: 12px;
    }
    /* relative, not static: the rail below is absolutely positioned, so it paints in the
       positioned layer and would sit ON TOP of static cards -- a 2px line straight across every
       numeral. Positioning the cards puts them in the same layer, where DOM order wins and the
       rail falls behind them. Layout is unaffected with no offsets set. */
    .om-cycle__ring li {
        position: relative;
        /* The base rule carries left:50%; top:50% for the absolute ring placement. `static`
           ignored those; `relative` honours them, which shoved every card half the container
           right and half down. Reset explicitly. */
        left: 0;
        top: 0;
        width: auto;
        margin: 0;
    }
    /* The ring positions are set per-node with :nth-child, which scores (0,2,1). A plain
       .om-cycle__ring li here only scores (0,1,1) and loses even inside the media query, so
       the nodes stayed absolutely positioned and shot off the side of the screen. */
    .om-cycle__ring li:nth-child(1),
    .om-cycle__ring li:nth-child(2),
    .om-cycle__ring li:nth-child(3),
    .om-cycle__ring li:nth-child(4),
    .om-cycle__ring li:nth-child(5) { transform: none; }
    /* The wordmark and its tagline only earn their place at the centre of the ring. Stacked
       under the list they read as an unrelated footer, so they go. */
    .om-cycle__hub { display: none; }
    /* .om-page, not a bare .om-cycle__feed: design-om.css carries `.om-page p { margin-top: 0 }`
       at (0,1,1), which beat this rule at (0,1,0) and zeroed the margin every time. The gap that
       was there came from grid auto-placement, not from the margin -- which is why raising the
       value from 20 to 30 to 44 changed nothing on the page.

       64, because the card stack has 12px between cards: anything under roughly 3x that still
       reads as a caption hanging off card five rather than as the sequence's closing line. The
       section's own 45px of bottom padding is trimmed to 22 so the line is not then left in a
       void underneath. */
    .om-page .om-cycle__feed { position: static; transform: none; width: auto; max-width: none;
                               text-align: center; margin: 64px auto 0; font-size: 17px; }
    .om-cycle-section { padding-bottom: 22px; }

    /* Without the ring, nothing said the five stages were a sequence. A rail runs down the badge
       column joining 1 to 5. Painted by the list's own ::before so it is one continuous line
       rather than a stub per gap, and it sits behind the cards -- their 6% white fill and 6px
       backdrop blur turn it into a soft glow where it passes behind glass, and the opaque badges
       hide it completely, so the numerals stay clean.

       left:35px is the badge centre: 20px of card padding plus half of the 30px badge. The 30px
       top/bottom insets land it inside the first and last cards, where any overshoot is hidden
       behind the badge rather than poking out of the stack. */
    .om-cycle__ring { position: relative; }
    .om-cycle__ring::before {
        content: '';
        position: absolute;
        left: 35px;
        top: 30px;
        bottom: 30px;
        width: 2px;
        margin-left: -1px;
        border-radius: 1px;
        background: linear-gradient(
            180deg,
            rgba(247, 148, 30, .12),
            rgba(247, 148, 30, .75) 10%,
            rgba(247, 148, 30, .75) 90%,
            rgba(247, 148, 30, .12)
        );
    }
    /* The orbit is aligned to the ring's absolute node positions, which no longer apply.
       The badge pulse that syncs to it is switched off further down -- see the note there. */
    .om-cycle__orbit { display: none; }
}

/* Two columns of cards survive the label moving above them; one column of the full width
   is a 130-character line at 13.5px. This is the width where two stop fitting. */
@media (max-width: 600px) {
    .om-stage__items { grid-template-columns: 1fr; }
}

/* ---- Hero review row: the five platform logos ----------------------------------------------
   Opportunity Mining ships these as white/grey marks already sized for a dark hero, so they need
   no greyscale filter -- only a cap so nothing upscales past its native height and goes soft.
   Native heights differ on purpose (22/20/16/14/16px): the marks are optically balanced, not
   box-matched, so forcing one height would make Software Advice tower over Meta. */
.om-review-sites--logos {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 10px 26px;
    height: auto;
    margin: 20px 0 0;
    padding: 0;
    list-style: none;
}
.om-review-sites--logos li { display: flex; align-items: center; }
.om-review-sites--logos img {
    display: block;
    width: auto;
    max-height: 22px;
    opacity: .8;
}

/* The generated mobile rules size .om-review-sites for the OM page, where it is a one-at-a-time
   carousel in a fixed box (width:220px then 282px, overflow:hidden, font-size:0) driven by arrows
   this page does not have. Unclipped here so all five logos wrap instead of being cut off.
   Scoped under .om-page to out-specify those rules rather than depend on file order. */
@media (max-width: 767px) {
    .om-page .om-review-sites {
        width: auto;
        height: auto;
        overflow: visible;
        flex-wrap: wrap;
        gap: 6px 14px;
        font-size: 11px;
        line-height: 1.4;
    }
    .om-page .om-review-sites--logos img { max-height: 16px; }

    /* ---- One sliding line, not three wrapped rows -----------------------------------------
       Five marks of five different widths wrapped onto three ragged rows on a phone, which is
       what "funky" meant. They now run as a single line that drifts left.

       Mechanics: the strip clips, the list is the track, and the track holds the five logos
       TWICE (bnt_review_logos prints the second run aria-hidden). Translating the track by
       exactly -50% lands run two where run one started, so the loop has no seam and no jump.

       The gap is margin-right on each item, not flex `gap`. With flex gap the track is ten items
       plus NINE gaps, so -50% is half a gap short of one run and the line stutters once a cycle.
       As a margin every item carries its own trailing space and the two halves are identical.

       The strip spans the container and clips there. A full-bleed 100vw version was tried first,
       so the logos would enter and leave at the screen edge -- it lands in the wrong place,
       because .om-hero__inner is a centred flex column, so the strip is a centred flex item and
       a 50% margin resolves against the 350px container while the box is 390px wide. Not worth
       the arithmetic for 20px on each side. */
    .om-review-strip {
        width: 100%;
        align-self: stretch;
        overflow: hidden;
        /* Fade both edges so a logo is not guillotined at the boundary. */
        -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
        mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
    }
    .om-page .om-review-sites--logos {
        flex-wrap: nowrap;
        justify-content: flex-start;
        width: max-content;
        /* The generated sheet caps this at max-width:282px, sized for OM's one-at-a-time logo
           box. Setting width alone was not enough: the cap held, the ten items had to fit 282px
           with nowrap, and every one of them shrank to zero. The row rendered as nothing at all. */
        max-width: none;
        gap: 0;
        animation: om-review-slide 28s linear infinite;
    }
    .om-review-sites--logos li { margin-right: 34px; }
    .om-review-sites--logos .om-review-sites__dup { display: flex; }
}

@keyframes om-review-slide {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}

/* Off the mobile track the second run is just the same five logos again, so it is hidden -- but
   min-width:768px, not an unscoped rule: unscoped it sits after the mobile block, wins on file
   order, and hides the half of the track that makes the loop seamless. */
@media (min-width: 768px) {
    /* (0,2,0), not (0,1,0): `.om-review-sites--logos li { display: flex }` above already scores
       (0,1,1), so a bare .om-review-sites__dup lost to it and the second run printed on desktop
       -- ten logos wrapping onto two rows. */
    .om-review-sites--logos .om-review-sites__dup { display: none; }
}

/* No drift for anyone who has asked not to have any. Back to the wrapped row, which is static
   and complete -- a parked marquee would leave whichever logos happen to be off-screen
   unreachable. This has to sit AFTER the mobile block: it is the same specificity, so file order
   is what decides it. */
@media (prefers-reduced-motion: reduce) {
    .om-review-strip {
        width: auto;
        margin-left: 0;
        transform: none;
        -webkit-mask-image: none;
        mask-image: none;
    }
    .om-page .om-review-sites--logos {
        flex-wrap: wrap;
        justify-content: center;
        gap: 10px 18px;
        width: auto;
        animation: none;
    }
    .om-review-sites--logos li { margin-right: 0; }
    .om-review-sites--logos .om-review-sites__dup { display: none; }
}

/* ---- The cycle orbit -----------------------------------------------------------------------
   Sized to a fixed 592px square so the SVG radius (224 user units) and the node offsets in
   .om-cycle__ring li (fixed px translates) stay in lockstep. Both are centred in .om-cycle, and
   the whole orbit is hidden below 900px where the ring collapses to a plain list. */
.om-cycle__orbit {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 592px;
    height: 592px;
    pointer-events: none;
}
.om-cycle__track {
    fill: none;
    stroke: var(--line, #dbe0e6);
    stroke-width: 2;
    stroke-dasharray: 2 7;
}
.om-cycle__comet {
    fill: var(--orange, #f7941e);
    /* transform-box so the origin below is read in viewBox units, not the element's own bbox. */
    transform-box: view-box;
    transform-origin: 296px 296px;
    animation: om-orbit 20s linear infinite;
}
.om-cycle__comet path {
    fill: none;
    stroke: var(--orange, #f7941e);
    stroke-width: 3;
    stroke-linecap: round;
}
.om-cycle__spark { filter: drop-shadow(0 0 6px rgba(247, 148, 30, .8)); }

@keyframes om-orbit { to { transform: rotate(360deg); } }

/* Each stage lights as the spark reaches it. The spark leads the arc by 60deg and the loop is
   20s, so stage k (at (k-1)*72deg from the top) is reached at ((k-1)*72 - 60) / 18 seconds.
   The delays are that value minus 20, i.e. negative, so the animation is already mid-flight on
   load and the first pass is not up to 17s late. */
.om-cycle__ring li b { animation: om-cycle-pulse 20s linear infinite; }
.om-cycle__ring li:nth-child(1) b { animation-delay: -3.33s; }
.om-cycle__ring li:nth-child(2) b { animation-delay: -19.33s; }
.om-cycle__ring li:nth-child(3) b { animation-delay: -15.33s; }
.om-cycle__ring li:nth-child(4) b { animation-delay: -11.33s; }
.om-cycle__ring li:nth-child(5) b { animation-delay: -7.33s; }

@keyframes om-cycle-pulse {
      0% { box-shadow: 0 0 0 0 rgba(247, 148, 30, .55); transform: scale(1); }
      4% { box-shadow: 0 0 0 9px rgba(247, 148, 30, 0);  transform: scale(1.18); }
     10% { box-shadow: 0 0 0 0 rgba(247, 148, 30, 0);    transform: scale(1); }
    100% { box-shadow: 0 0 0 0 rgba(247, 148, 30, 0);    transform: scale(1); }
}

/* Motion is decorative, so it goes entirely rather than being slowed. The comet stays put at the
   top of the ring, which still reads as a direction of travel. */
@media (prefers-reduced-motion: reduce) {
    .om-cycle__comet,
    .om-cycle__ring li b { animation: none; }
}

/* Part of the 900px collapse above, but it has to sit here, after the animation is declared:
   `.om-cycle__ring li b` scores the same in both places, so a reset inside that earlier block
   loses to this later rule on source order and the badges would keep pulsing with nothing
   travelling the ring. Same trap as the :nth-child transforms in the collapse block. */
@media (max-width: 900px) {
    .om-cycle__ring li b { animation: none; }
}

/* ---- Page body type ------------------------------------------------------------------------
   .om-page was pinned to Arial for the same reason the shell was: design-om.css flips it to
   `'Open Sans', Arial` at one breakpoint and `Arial, 'Open Sans'` at another, a difference that
   is invisible on the live OM page because that page loads no webfont and both stacks land on
   Arial. We self-host Open Sans, so the two stacks diverge.

   The pin is gone -- the site standardises on Open Sans (Yuri, 2026-09-18). The generated
   breakpoint rule at design-om.css line 132 already asks for 'Open Sans' first; line 4 asks for
   Arial first, so this states it once for the whole page rather than leaving the result to which
   breakpoint is active. */
.om-page { font-family: 'Open Sans', Arial, Helvetica, sans-serif; }


/* ==========================================================================================
   The cycle, on navy. A thin dotted ring on white read as a flowchart; inverted, with a lit
   gradient comet and glass nodes, it reads as a product visual. Scoped to .om-cycle-section so
   the light version stays available to any other page that wants the plain diagram.
   ========================================================================================== */

/* Grey rather than the navy .om-section--dark carries, so the band reads as neutral ground for
   the ring instead of a second brand-coloured block. Still a dark field -- the ring's halo,
   comet, spark and glass node fills are white and need one to read at all -- but a step up from
   the #24282d it started at, which sat almost black next to the navy hero. */
.om-cycle-section { background: #3d434c; }

/* A wide, very soft ring sitting under the track: it is what makes the orbit look lit rather
   than drawn. Blur via a large stroke at low opacity instead of a filter, which would cost a
   separate raster pass on a 620px box. */
.om-cycle-section .om-cycle__halo {
    fill: none;
    stroke: #f9b233;
    stroke-width: 22;
    opacity: .07;
}
.om-cycle-section .om-cycle__track {
    stroke: rgba(255, 255, 255, .17);
    stroke-width: 1.5;
    stroke-dasharray: 1 8;
}

/* Ties the "new opportunities feed the next cycle" label to the arc it describes -- the span
   between the last stage and the first, which is the whole point of the diagram. Drawn inside
   the SVG rather than beside it so it stays registered to the ring at any scale. The marker
   inherits strokeWidth units, so its 5x5 box paints at 10px against this 2px stroke. */
.om-cycle-section .om-cycle__pointer {
    fill: none;
    stroke: var(--orange, #f7941e);
    stroke-width: 2;
    stroke-linecap: round;
    opacity: .9;
}
.om-cycle-section .om-cycle__tail {
    fill: none;
    stroke-width: 5;
    stroke-linecap: round;
}
/* The gradient has to out-specify the flat orange stroke on `.om-cycle-section .om-cycle__comet
   path` (0,2,1), so it is qualified by the element as well -- (0,3,1). A bare .om-cycle__tail
   rule scores (0,2,0) and silently loses, which left the comet as a lone white dot. */
.om-cycle-section .om-cycle__comet path.om-cycle__tail { stroke: url(#om-cycle-tail); }
.om-cycle-section .om-cycle__spark {
    fill: #fff;
    filter: none;
}

/* Glass cards. The blur is a progressive enhancement -- without backdrop-filter the translucent
   fill over navy still reads correctly, just flatter. */
.om-cycle-section .om-cycle__ring li {
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .15);
    box-shadow: 0 10px 28px rgba(4, 12, 24, .35);
    color: #fff;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}
.om-cycle-section .om-cycle__ring li b {
    background: #f9b233;
    color: #042854;
    box-shadow: 0 0 0 0 rgba(249, 178, 51, .6);
}

/* The hub wordmark, with a halo behind it so the centre holds against the lit ring. */
.om-cycle-section .om-cycle__hub { color: #9fb0c4; }
.om-cycle-section .om-cycle__hub::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 300px;
    height: 300px;
    margin: -150px 0 0 -150px;
    border-radius: 50%;
    background: radial-gradient(closest-side, rgba(249, 178, 51, .17), rgba(249, 178, 51, 0) 70%);
    z-index: -1;
}
.om-cycle-section .om-cycle__brand { color: #f9b233; }
.om-cycle-section .om-cycle__brand em { color: #fff; }
.om-cycle-section .om-cycle__feed { color: #f9b233; }

/* Brighter, wider pulse than the light version needs, to match the lit ring. */
.om-cycle-section .om-cycle__ring li b { animation-name: om-cycle-pulse-dark; }
@keyframes om-cycle-pulse-dark {
      0% { box-shadow: 0 0 0 0 rgba(249, 178, 51, .7);  transform: scale(1); }
      4% { box-shadow: 0 0 0 14px rgba(249, 178, 51, 0); transform: scale(1.22); }
     10% { box-shadow: 0 0 0 0 rgba(249, 178, 51, 0);   transform: scale(1); }
    100% { box-shadow: 0 0 0 0 rgba(249, 178, 51, 0);   transform: scale(1); }
}


/* ==========================================================================================
   Section sub-headers match the hero's sub-header.

   The hero paragraph renders at 20px/28px -- design-om.css sizes it through `.om-hero p`, which
   at (0,1,1) out-specifies the `.om-hero-lead` rule above -- while the section leads were coming
   out at 16px (.om-lead) and 15.5px (.om-section-lead).

   Desktop only. Below 768px the generated rules already put `.om-hero p` and `.om-lead` both at
   16px/22px, so there is nothing to reconcile and forcing 20px would only overflow narrow
   columns.

   Opportunity Mining is excluded: it is the reference design the rest of the site is built
   against, and keeps its own 16px/26px.
   ========================================================================================== */
@media (min-width: 768px) {
    /* :where() around the id so the exclusion costs no specificity -- (0,2,0), not (1,2,0).
       Otherwise the plain-class colour rules below could never override it. */
    .om-page:not(:where(#opportunity-mining)) .om-lead,
    .om-section-lead {
        font-size: 20px;
        line-height: 28px;
    }
}

/* ==========================================================================================
   Sub-header colour: ink on light fields, white on dark.

   It was grey on both -- #616a75 on light, #b9c4d1 on dark -- which at 20px reads as washed
   out rather than secondary. The dark rules are one class more specific than the light one, so
   they win wherever a section sets a dark field.
   ========================================================================================== */
.om-page:not(:where(#opportunity-mining)) .om-lead,
.om-section-lead {
    color: var(--ink, #2e3033);
}
/* Not #fff on the dark bands: the h2 there is already white, so a white lead read as a second
   heading and the two competed for first attention. A light grey is one step down the hierarchy
   while still clearing 4.5:1 against every dark field we use -- verified by
   src/tools/contrast.sh, not by eye. The hero is left white: its h1 is far larger, so the
   hierarchy is carried by size there and nothing is competing. */
.om-page .om-section--dark .om-lead,
.om-page .om-section--dark .om-section-lead,
.om-page .om-cycle-section .om-lead,
.om-page .om-cta .om-lead,
.int-page .om-cta .om-lead {
    color: #c3cbd6;
}

/* The hero sits on navy everywhere except the two pages that set a light hero themselves. */
.om-hero-lead { color: #fff; }


/* ==========================================================================================
   Star rating: 4.7 of 5, shown as 4.7 stars.

   Every page was printing five solid stars beside a 4.7 score, which claims a perfect rating.
   Two identical glyph runs stacked, the filled one clipped by width -- see bnt_stars().

   The track takes currentColor at low opacity rather than a fixed grey, so the same component
   works on the navy hero, the dark grey cycle band and the light Integrations hero without a
   per-field variant.
   ========================================================================================== */
.om-stars {
    position: relative;
    display: inline-block;
    white-space: nowrap;
    /* 1.2, not 1: at line-height 1 the line box is exactly the font size, but the star glyph's
       em box is taller than that, so the filled layer -- the only one with overflow:hidden --
       had its bottom 3px cut off while the track behind it did not. The two runs are identical
       glyphs, so the mismatch showed as a ragged edge along the filled stars. */
    line-height: 1.2;
    vertical-align: -.16em;
}
.om-stars__track {
    color: currentColor;
    opacity: .26;
}
.om-stars__fill {
    position: absolute;
    left: 0;
    top: 0;
    overflow: hidden;
    color: var(--orange, #f9b233);
}

/* The rating line was 24px with a 30px score -- heading-sized, and it pulled attention off the
   h1. Laid out as a single wrapping row so the generated mobile rule (flex-direction:column,
   which puts the stars, the score and the note on three separate lines) does not apply.

   Opportunity Mining was excluded here, on the principle that it keeps its own type. Yuri's call
   (2026-09-19) is that the rating is the exception: OM's version was a plain "4,7 ★" -- a comma
   decimal, and five solid stars for a 4.7 score -- so it now takes the same component as every
   other page. The type exclusions elsewhere in this file stand. */
.om-page .om-rating {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 3px 8px;
    font-size: 15px;
    line-height: 1.4;
}
.om-page .om-rating strong {
    margin: 0;
    font-size: 16px;
    font-weight: 700;
}
/* Every .om-hero centres its text. While the row fits on one line the box shrink-wraps and the
   hero centres it, so this does nothing; once the note wraps (White Glove on a phone, where it
   carries the "as of" date) the box is full width and its lines started at the left edge under
   a centred headline. */
.om-page .om-hero .om-rating { justify-content: center; text-align: center; }
.om-page .om-stars { font-size: 17px; }


/* ------------------------------------------------------------------------------------------
   FAQ: let it grow.

   design-om.css sizes this block by fixed height at every breakpoint -- the section at 630px, an
   open question at 130px, a closed one at 57px. That holds only while the answers stay as short
   as the ones on the page it was generated from. The CRM page has eight questions and longer
   answers: the section measured 630px against 643px of content, so the last two questions
   rendered outside it and over the CTA band below. Same failure the CTA band had with the
   testimonial, and it does not clip -- it draws in the wrong place.

   Heights only. min-height rather than height:auto alone, so a page whose FAQ already fits inside
   630px keeps the whitespace it was designed with instead of collapsing to its content. An
   earlier version of this also tuned summary padding and answer margins, which moved
   /opportunity-mining/ by 25px -- a page nobody asked to change. Spacing that only the CRM page
   needs is scoped to it below.
   ------------------------------------------------------------------------------------------ */
.om-page .om-faq {
    height: auto;
    min-height: 630px;
}
.om-page .om-faq details,
.om-page .om-faq details[open],
.om-page .om-faq details:not([open]) {
    height: auto;
    min-height: 57px;
}
.om-page .om-faq summary {
    height: auto;
    min-height: 57px;
}
/* The fixed height used to supply the space under the last question. With the section sized to
   its content there is none, so the CRM page states it. Scoped: adding it globally would push
   /opportunity-mining/ past its 630px and change a page this has no business changing. */
#mortgage-crm .om-faq { padding-bottom: 76px; }
/* The generated rule sets the answers to margin:0, which puts the last line of an answer flush
   on the divider under it -- visibly cramped, and it drags the line box onto the 1px rule, where
   contrast against it measures 4.27:1. Both go away with a margin. Scoped, because the other
   FAQ's answers are one or two lines and were drawn that way deliberately. */
#mortgage-crm .om-faq details p { margin: 2px 0 18px; }
/* The CRM answers carry links, which the other FAQs do not. Without this they inherit the muted
   answer colour and stop looking clickable. */
.om-page .om-faq details p a {
    color: var(--navy, #032d60);
    font-weight: 700;
    text-decoration: underline;
}
/* Its own hover, because the rule above is (0,2,3) and the site-wide a:hover in theme.css is
   (0,2,1) -- a component that declares a link colour has to declare the hover too. */
.om-page .om-faq details p a:hover { color: var(--orange-d, #dd8210); }

@media (max-width: 900px) {
    .om-page .om-faq { min-height: 0; }
}

/* ------------------------------------------------------------------------------------------
   Testimonial in the closing CTA band.

   No card, rule or quotation mark of its own. The band is the page's one saturated field and
   the button sits directly under the quote -- anything with an edge competes with it. The
   stars, the measure and the caption carry it.

   .om-cta p is a fixed 695px in the generated stylesheet; this is a <figure>/<blockquote>, so
   none of that applies and the measure is set here instead. 720px at 19px is about 75
   characters, which is where a centred paragraph stops being comfortable.
   ------------------------------------------------------------------------------------------ */
/* The band is a fixed height in the generated stylesheet -- 400px at desktop, 365px below --
   sized for a heading, two lines of copy and a button. The testimonial is taller than that: the
   content measured 457px against the 400px box, and because the overflow is visible the caption
   and the button simply rendered outside the orange and over the footer. Height auto, with the
   bottom padding the fixed height used to stand in for. */
/* .int-page as well as .om-page: the integrations page is not an OM page but it ends on this
   band, and without the release it inherited the 400px box -- the caption and the button
   rendered outside the orange, over the footer. */
.om-page .om-cta,
.int-page .om-cta {
    height: auto;
    min-height: 0;
    padding: 64px 0 60px;
}
/* White, per Yuri, so this inherits the band's colour rather than setting one. Recorded because
   it was briefly navy: white on this orange measures 2.41:1 against the 4.5 AA wants, and navy
   on the same orange is 6.28:1. The decision is white; this note is here so the next person does
   not "fix" it by accident.

   620px at 18px is about 79 characters. The measure comes down with the size each time -- 840px
   was set for 26px text and runs to 105 characters at this one. */
.om-cta__quote {
    max-width: 620px;
    margin: 0 auto 30px;
    text-align: center;
}
.om-cta__quote .om-stars { font-size: 19px; }
/* .om-stars__fill is the brand orange, which is the colour of this band -- the filled stars
   would vanish into it. currentColor, so the component still needs no second markup variant;
   the track already derives from currentColor the same way. */
.om-cta .om-stars__fill { color: currentColor; }
/* Italic, and only on the quote -- the attribution under it stays roman, which is what keeps
   the two readable as the words and then the person who said them. */
.om-cta__quote blockquote {
    margin: 16px 0 20px;
    font-size: 18px;
    font-style: italic;
    line-height: 1.6;
}
/* Real quotation marks around it. Stars, a name and a company already say testimonial, but
   only the marks say the words are someone else's -- which is the whole point of putting them
   here instead of writing the same claim as copy. Generated rather than typed into the
   template so the quote stays clean to lift back out. */
.om-cta__quote blockquote::before { content: "\201C"; }
.om-cta__quote blockquote::after  { content: "\201D"; }
.om-cta__quote figcaption b {
    display: block;
    font-size: 15px;
    font-weight: 700;
}
.om-cta__quote figcaption span {
    display: block;
    font-size: 14px;
    opacity: .85;
}

@media (max-width: 768px) {
    .om-cta__quote blockquote { font-size: 16px; line-height: 1.55; }
    .om-cta__quote .om-stars { font-size: 17px; }
}

/* ==========================================================================================
   Hero peek: abstract product panels rising out of the foot of the hero.

   The panels are cut in half by the hero's own overflow rather than by a mask, so the crop is
   exactly the section boundary -- they read as sitting half inside the section and half below
   it. That means .om-hero--peek must own both the positioning context and the clip.

   Held at .34 opacity: enough to register as content behind the message, not enough to pull the
   eye off the h1 or the button. Never interactive.
   ========================================================================================== */
.om-hero--peek {
    position: relative;
    overflow: hidden;
}
.om-hero__peek {
    position: absolute;
    left: 50%;
    bottom: 0;
    /* 46% of the artwork's height sits below the hero's edge -- the "half cut off" crop. */
    transform: translate(-50%, 46%);
    width: min(1100px, 94%);
    height: auto;
    opacity: .34;
    pointer-events: none;
}

/* Panel shells and their contents. Everything is currentColor-independent and explicit, because
   the SVG inherits nothing useful from a hero whose text colour is white. */
.om-peek__panel > rect:first-child {
    fill: rgba(255, 255, 255, .05);
    stroke: rgba(255, 255, 255, .13);
    stroke-width: 1.5;
}
.om-peek__row {
    fill: rgba(255, 255, 255, .045);
    stroke: rgba(255, 255, 255, .09);
}
.om-peek__title { fill: rgba(255, 255, 255, .34); }
.om-peek__bar   { fill: rgba(255, 255, 255, .2); }
.om-peek__dot   { fill: #f9b233; }
.om-peek__ring  {
    fill: none;
    stroke: #f9b233;
    stroke-width: 2;
}
.om-peek__col      { fill: rgba(255, 255, 255, .16); }
.om-peek__col--on  { fill: #f9b233; }
.om-peek__area     { fill: url(#om-peek-area); }
.om-peek__line {
    fill: none;
    stroke: #f9b233;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Room for the visible upper half: the artwork is 320 tall in a 1100-wide box, so at 1100px
   roughly 173px shows. Padding rather than a height, so the hero still grows with its copy. */
@media (min-width: 768px) {
    .om-hero--peek { padding-bottom: 210px; }
}

/* Below 768px three panels across 390px would be illegible smudges, and the hero is already the
   tallest thing on the page. Dropped entirely, along with the room made for it. */
/* On a phone the artwork is not dropped -- an empty navy block below the logos reads as a
   missing image -- but three 300-wide panels across a 360px viewport would be illegible
   smudges. So the SVG is blown up to 260% of the hero and centred, which crops it to the middle
   panel: one chart, at roughly the size it has on desktop. The side panels overflow into the
   hero's existing `overflow:hidden`.

   Padding is the visible upper half again: at 260% of a 360px hero the artwork is ~936x272, and
   46% of it sits below the edge, so ~147px shows. */
@media (max-width: 767px) {
    /* A fixed width, not a percentage: the crop has to be the same at 360px and at 760px, and a
       percentage makes the artwork -- and so the height of the half sticking out -- grow with the
       viewport, which at 640px pushed the panel up over the review logos. At 980px the middle
       panel lands at ~338px wide and ~155px of the artwork shows below the edge. */
    .om-hero__peek { width: 980px; max-width: none; }
    .om-hero--peek { padding-bottom: 160px; }
}

/* ==========================================================================================
   Opportunity Mining intro: copy beside an illustration.

   The section was a centred 705px paragraph with nothing else in it, which on a page whose next
   band is a six-card grid read as a gap rather than as a section. Copy on the left, artwork on
   the right.

   .om-intro is a fixed 277px in the generated sheet, sized for a heading plus one centred
   paragraph, so the split variant has to release the height as well as the text alignment.
   ========================================================================================== */
.om-intro--split {
    height: auto;
    min-height: 0;
    padding: 64px 0;
    text-align: left;
}
.om-intro__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr);
    gap: 56px;
    align-items: center;
}
/* .om-intro h2 and .om-intro p are both centred and width-pinned by the generated sheet
   (`.om-intro p { width: 705px; margin: auto }`), at (0,1,1). Two classes to beat it. */
.om-intro--split h2 { text-align: left; margin-bottom: 18px; }
.om-intro--split .om-intro__copy p { width: auto; max-width: 52ch; margin: 0; }

.om-intro__art { display: flex; justify-content: center; }
/* 30% down from 460, at Yuri's direction: the drawing is a note beside the copy, not a
   second column of equal weight. */
.om-mining { width: 100%; max-width: 322px; height: auto; overflow: visible; }

/* --- The drawing ---------------------------------------------------------------------------
   Outline only, grey, struck through. Nothing in here should ever gain a fill: the moment the
   megaphone looks switched on, the drawing says the opposite of the headline above it.
   ------------------------------------------------------------------------------------------- */
.om-mining__horn path {
    fill: none;
    stroke: #aeb9c6;
    stroke-width: 3;
    stroke-linejoin: round;
    stroke-linecap: round;
}

/* The sound it is not making. The arcs swell and fade and never complete, on a 4.6s cycle, the
   outer one a beat behind the inner -- the shape of something about to be said. They are dotted
   as well as animated, so in a still frame (or under reduced motion) they still read as absent
   rather than as sound. */
.om-mining__wave {
    stroke-dasharray: 3 8;
    opacity: 0;
    transform-origin: 128px 118px;   /* the horn's mouth, so they grow out of it */
    transform-box: view-box;
    animation: om-mining-wave 4.6s ease-out infinite;
    animation-delay: calc(var(--i, 0) * .45s);
}
@keyframes om-mining-wave {
    0%   { opacity: 0;  transform: scale(.88); }
    18%  { opacity: .7; transform: scale(1); }
    42%  { opacity: 0;  transform: scale(1.06); }
    100% { opacity: 0;  transform: scale(1.06); }
}

/* The strike. The cut is the section's own field colour, painted wider than the slash and
   underneath it, so the line reads as passing over the megaphone instead of joining it. It has
   to match whatever the section background is -- white here.

   The slash brightens as the arcs fade, on the same clock: the announcement gets as far as being
   about to happen, and the mute is what lands. */
.om-mining__slash-cut {
    fill: none;
    stroke: #fff;
    stroke-width: 13;
    stroke-linecap: round;
}
.om-mining__slash {
    fill: none;
    stroke: #98a5b4;
    stroke-width: 5;
    stroke-linecap: round;
    animation: om-mining-slash 4.6s ease-in-out infinite;
}
@keyframes om-mining-slash {
    0%, 18%   { stroke: #b6c0cb; }
    44%       { stroke: #7b8a9c; }
    70%, 100% { stroke: #98a5b4; }
}

@media (prefers-reduced-motion: reduce) {
    /* Parked where the arcs are visible but faint, which is the state the drawing reads best in.
       Nothing is hidden by stopping. */
    .om-mining__wave  { animation: none; opacity: .55; transform: none; }
    .om-mining__slash { animation: none; }
}

@media (max-width: 899px) {
    .om-intro--split { padding: 44px 0; text-align: center; }
    .om-intro__inner { grid-template-columns: minmax(0, 1fr); gap: 34px; }
    .om-intro--split h2 { text-align: center; }
    .om-intro--split .om-intro__copy p { max-width: none; }
    /* Artwork under the copy, and capped: at full width on a phone the rows stretch to 400px
       long and the drawing stops reading as a list. */
    .om-mining { max-width: 252px; }
}

/* ==========================================================================================
   Engine cards: let the tall ones grow.

   .om-card-grid pins every card to 225px (210px from the fourth) because the comp's cards hold a
   fixed amount of copy. Two of the six engine cards do not -- "Credit Check Alerts" carries a
   permissions disclaimer and "Listed Alerts by Partner" runs to four lines -- and their text ran
   straight through the "See it in action" link underneath it.

   Height released, min-height kept, so the short cards still square off at the comp's 225px and
   only the two long ones grow. Scoped to this section: the same component on the CRM and AI
   pages holds copy written to the comp's measure and should keep its fixed geometry.
   ========================================================================================== */
#opportunity-mining .om-engines .om-card-grid article,
#opportunity-mining .om-engines .om-card-grid article:nth-child(n+4) {
    height: auto;
    min-height: 225px;
    /* More room under the link than the comp's 27px. With the card sizing to its content the
       link is the last thing in the box, and at 27px it sat closer to the edge than the heading
       does to the top. */
    padding-bottom: 36px;
}
/* The 82px floor on the paragraph existed to push the link to the bottom of a fixed box. With
   the box sizing to its content it only adds dead space under the short cards, and the grid's
   own `minmax(0, 1fr)` middle row does the pushing. */
#opportunity-mining .om-engines .om-card-grid p { min-height: 0; }
/* The section is `height: 785px; padding: 62px 0 0` above 1200px -- a fixed box with no bottom
   padding at all, which worked only while every card was pinned to 225px. With the cards sizing
   to their content the grid ran to the section's edge and the last row sat flush against the
   band below it. Height released, and a real bottom padding to match the 70px the sheet already
   uses in the tablet range. */
#opportunity-mining .om-engines {
    height: auto;
    min-height: 0;
    padding-bottom: 70px;
}

/* ------------------------------------------------------------------------------------------
   Engine cards: hover lift, and a staggered reveal as the section arrives.

   The reveal is armed by om.js (.is-armed) rather than declared here, so a browser with no
   IntersectionObserver -- or a page where the script failed -- shows the cards instead of
   hiding them permanently. Nothing below applies until that class exists.

   The stagger is transition-delay keyed off each card's --i, which the template sets. 70ms is
   deliberately short: six cards at 70ms finish in a third of a second, which reads as the row
   settling rather than as a sequence the reader has to wait out.
   ------------------------------------------------------------------------------------------ */
#opportunity-mining .om-engine-card {
    transition: transform .18s ease, box-shadow .18s ease;
}
#opportunity-mining .om-engine-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 12px 28px -18px #15304c73;
}
/* The icon warms up with the card. i is the icon slot; the svg inside takes the stroke. */
#opportunity-mining .om-engine-card i svg {
    transition: transform .18s ease;
}
#opportunity-mining .om-engine-card:hover i svg { transform: scale(1.08); }

#opportunity-mining .om-engines.is-armed .om-engine-card {
    opacity: 0;
    transform: translateY(14px);
}
#opportunity-mining .om-engines.is-armed.is-in .om-engine-card {
    opacity: 1;
    transform: none;
    transition: opacity .5s ease, transform .5s cubic-bezier(.22, .61, .36, 1),
                box-shadow .18s ease;
    transition-delay: calc(var(--i, 0) * 70ms);
}

/* The engines lead is a single sentence in a wide column, and it was breaking with "by." alone
   on the second line. balance splits it evenly instead of filling the first line. */
#opportunity-mining .om-engines .om-lead { text-wrap: balance; }

/* ------------------------------------------------------------------------------------------
   Checklist, in place of the bullets on the alerts section.

   The marker is a pseudo-element rather than a list-style-image or an inline SVG per item: it
   scales with the text, takes the brand orange from the token, and leaves the markup a plain
   <ul> that still reads as a list to a screen reader. list-style:none would silently strip that
   role in Safari, so the role is restored with a zero-width space in the marker.

   The generated sheet sets .om-surface ul { padding-left: 17px } for the bullet indent. The
   check sits in that space instead, so the padding goes and each item pads itself.
   ------------------------------------------------------------------------------------------ */
#opportunity-mining .om-checks {
    padding-left: 0;
    list-style: none;
}
#opportunity-mining .om-checks li {
    position: relative;
    padding-left: 34px;
    margin-bottom: 10px;
}
#opportunity-mining .om-checks li::marker { content: '\200B'; }
#opportunity-mining .om-checks li::before {
    content: '';
    position: absolute;
    left: 0;
    /* Centred on the first LINE BOX, so the disc lines up with the first line's text on a
       two-line item instead of drifting toward the middle of the block. 3px is the answer at the
       desktop 16px/26px; the lh calculation is the same sum written so it survives the two
       breakpoints where the sheet changes the line-height. */
    top: 3px;
    top: calc((1lh - 20px) / 2);
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fdf1dc
        /* The tick, as an SVG rather than two rotated borders. The border trick was 3px off
           centre inside the disc and its ends poked past the bottom-left edge: a rotated L is not
           centred on its own box, and correcting for that by eye is how it got there. The stroke
           colour is baked in here because a data URI cannot read a custom property -- if
           --orange changes, change it here too. */
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.4 6.2 L4.9 8.8 L9.6 3.4' fill='none' stroke='%23f9b233' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
        center / 12px 12px no-repeat;
}

/* ------------------------------------------------------------------------------------------
   Live alert feed: the alerts fly in, hold, and do it again.

   One 4.4s cycle: the three rows fly in from the right 0.16s apart, the full set holds for three
   seconds, then all three leave TOGETHER and the sequence restarts.

   "Together" is the whole design. The first version gave each row the same keyframes and a
   different animation-delay, which staggers the exit as well as the entrance: measured across a
   cycle, the panel showed two of its three alerts for about three and a half seconds in every
   twelve and the rows left one at a time, so it read as a list LOSING items. Sharing one clock
   and building the stagger into the keyframes instead means the entrance is staggered and the
   exit is not -- the panel refreshes as a unit rather than leaking rows.

   Hence three near-identical @keyframes rather than one. They differ only in when the row's
   entrance starts; every exit percentage is the same in all three, and they have to stay that
   way. If this ever needs retiming, move all three exits together or the leak comes back.

   .om-feed already carries overflow:hidden for its rounded corners, so the rows are clipped by
   the panel edge and genuinely come from outside it.

   Armed by om.js (.is-armed), never by this file -- see revealOnView(). Nothing below applies
   until that class exists, so a reader with no JS sees three alerts sitting still.
   ------------------------------------------------------------------------------------------ */
#opportunity-mining .om-feed.is-armed .om-alert {
    opacity: 0;
}
#opportunity-mining .om-feed.is-armed.is-in .om-alert {
    animation: 6.4s cubic-bezier(.22, .61, .36, 1) infinite;
}
/* nth-child rather than a --i on the markup: the three rows are written inline in the template
   and there is no reason for the copy to carry animation state. The head is child 1. */
#opportunity-mining .om-feed.is-armed.is-in .om-alert:nth-child(2) { animation-name: om-alert-fly-1; }
#opportunity-mining .om-feed.is-armed.is-in .om-alert:nth-child(3) { animation-name: om-alert-fly-2; }
#opportunity-mining .om-feed.is-armed.is-in .om-alert:nth-child(4) { animation-name: om-alert-fly-3; }

/* Row 1: arrives at 0, over 0.7s. */
@keyframes om-alert-fly-1 {
    0%      { opacity: 0; transform: translateX(76px); }
    10.94%  { opacity: 1; transform: translateX(0); }
    92.19%  { opacity: 1; transform: translateX(0); }
    99.22%  { opacity: 0; transform: translateX(-24px); }
    99.23%, 100% { opacity: 0; transform: translateX(76px); }
}
/* Row 2: arrives at 1.1s. */
@keyframes om-alert-fly-2 {
    0%, 17.19% { opacity: 0; transform: translateX(76px); }
    28.13%  { opacity: 1; transform: translateX(0); }
    92.19%  { opacity: 1; transform: translateX(0); }
    99.22%  { opacity: 0; transform: translateX(-24px); }
    99.23%, 100% { opacity: 0; transform: translateX(76px); }
}
/* Row 3: arrives at 2.2s. The three-second hold starts at 2.9s, when this one lands. */
@keyframes om-alert-fly-3 {
    0%, 34.38% { opacity: 0; transform: translateX(76px); }
    45.31%  { opacity: 1; transform: translateX(0); }
    92.19%  { opacity: 1; transform: translateX(0); }
    99.22%  { opacity: 0; transform: translateX(-24px); }
    99.23%, 100% { opacity: 0; transform: translateX(76px); }
}

/* The icon pops as its row lands, so it needs the row's own timing -- three keyframe sets
   again, on the same 6.4s clock. */
#opportunity-mining .om-feed.is-armed.is-in .om-alert i {
    animation: 6.4s ease-out infinite;
}
#opportunity-mining .om-feed.is-armed.is-in .om-alert:nth-child(2) i { animation-name: om-alert-icon-1; }
#opportunity-mining .om-feed.is-armed.is-in .om-alert:nth-child(3) i { animation-name: om-alert-icon-2; }
#opportunity-mining .om-feed.is-armed.is-in .om-alert:nth-child(4) i { animation-name: om-alert-icon-3; }

@keyframes om-alert-icon-1 {
    0%, 5%   { transform: scale(.86); }
    13%      { transform: scale(1.1); }
    20%, 100% { transform: scale(1); }
}
@keyframes om-alert-icon-2 {
    0%, 22%  { transform: scale(.86); }
    30%      { transform: scale(1.1); }
    37%, 100% { transform: scale(1); }
}
@keyframes om-alert-icon-3 {
    0%, 39%  { transform: scale(.86); }
    47%      { transform: scale(1.1); }
    54%, 100% { transform: scale(1); }
}

/* ------------------------------------------------------------------------------------------
   The process row is three steps, not four.

   Every breakpoint in the generated sheet lays .om-steps out for four -- `repeat(4, 255px)`
   above 1200px, `repeat(4, minmax(0,1fr))` below it -- so three left a column of empty space on
   the right and the row read as a missing step rather than as a shorter sequence.

   The 255px track goes with it: three fixed 255px columns in a 1108px container sit bunched to
   the left. 1fr each instead, so three steps use the width four used to.

   Between 600 and 767 the sheet drops to two columns, which with three steps leaves one on a row
   of its own. The stacked single-column list it already uses below 600 reads better there.
   ------------------------------------------------------------------------------------------ */
@media (min-width: 768px) {
    #opportunity-mining .om-steps {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
@media (min-width: 600px) and (max-width: 767px) {
    #opportunity-mining .om-steps {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ------------------------------------------------------------------------------------------
   "Set up once" graphic: a machine, and a conveyor of alerts coming out of it.

   The rig is the only filled shape and the only static one -- it is the thing you do once. The
   cards are the thing that repeats, so the cards are what moves. A reader should get it without
   reading anything.

   It replaced a closed circuit with a comet running round it, which was accurate and
   unreadable. If this is ever redesigned, the test is the same: does someone who has not read
   the heading know what it means?
   ------------------------------------------------------------------------------------------ */
.om-loop { margin: 52px auto 0; width: min(640px, 100%); }
.om-loop__art { display: block; width: 100%; height: auto; overflow: hidden; }

.om-loop__lane {
    fill: none;
    stroke: #dbe0e6;
    stroke-width: 2;
    stroke-dasharray: 4 7;
}

.om-loop__panel {
    fill: #fff;
    stroke: #dbe0e6;
    stroke-width: 2;
}
.om-loop__switch { fill: var(--orange, #f9b233); }
.om-loop__knob   { fill: #fff; }
.om-loop__bar    { fill: #e6eaf0; }
.om-loop__done circle { fill: var(--orange, #f9b233); }
.om-loop__tick {
    fill: none;
    stroke: #fff;
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.om-loop__card-box {
    fill: #fff;
    stroke: #ecc986;
    stroke-width: 1.5;
    filter: drop-shadow(0 4px 10px #15304c1a);
}
.om-loop__card-dot { fill: var(--orange, #f9b233); }
.om-loop__card-bar { fill: #d7dde5; }
.om-loop__card-bar--sub { fill: #e6eaf0; }

/* One lane, three cards, a third of a cycle apart, so one is always in view and the stream never
   looks like it stopped. The card leaves the frame rather than fading on the spot -- the svg
   clips at its own edge, which is why .om-loop__art is overflow:hidden. */
.om-loop__card {
    animation: om-loop-travel 5.1s linear infinite;
    animation-delay: calc(var(--i, 0) * -1.7s);
}
/* 444px of travel, not 340: the three cards are a third of a cycle apart, so the gap between
   them is travel/3 minus the 124px card. At 340 that is negative and they ran as one train. */
@keyframes om-loop-travel {
    0%   { transform: translateX(0);     opacity: 0; }
    7%   { transform: translateX(31px);  opacity: 1; }
    100% { transform: translateX(444px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    /* Parked with the three cards spread down the lane, which still reads as a stream. */
    .om-loop__card { animation: none; }
    .om-loop__card:nth-of-type(2) { transform: translateX(148px); }
    .om-loop__card:nth-of-type(3) { transform: translateX(296px); }
}

@media (max-width: 767px) {
    .om-loop { margin-top: 34px; }
}

/* .om-process is `height: 590px; padding: 72px 0 0` in the generated sheet -- another fixed box
   with no bottom padding, sized for four steps and the note that used to close the section. The
   loop graphic that replaced the note is taller, so it hung 56px into the dark band below.
   Same fix as .om-engines: release the height, give it a real bottom padding. */
#opportunity-mining .om-process {
    height: auto;
    min-height: 0;
    padding-bottom: 76px;
}

/* .om-faq is `height: 630px; padding: 73px 0 0` above 1200px and 805px on mobile -- both sized
   for the six questions this page shipped with. It carries thirteen now, and a fourth fixed-
   height section on this page was about to run into the band below it. Same fix as .om-engines
   and .om-process: release the height, give it a real bottom padding.

   Worth noting as a pattern rather than three accidents: the generated sheet pins a height on
   every section on this page, because it was measured from a live page whose content never
   changes. Anything that adds a line to a section here needs this. */
#opportunity-mining .om-faq {
    height: auto;
    min-height: 0;
    padding-bottom: 72px;
}

/* --- About Us (template-about.php) ------------------------------------------------------------
   The page is three sections of stock OM components, so this is everything it adds: a prose link
   that is not the browser's blue, and a closing line with a little more weight than the run of
   the section it ends. Two rules do not warrant a stylesheet.
   --------------------------------------------------------------------------------------------- */
.om-about .om-container p a {
    color: var(--orange-d, #dd8210);
    font-weight: 700;
}
/* Left, not centred: three sections of prose with centred headings over left-set body read as
   two layouts stacked. The column stays centred on the page; its contents start at its edge. */
.om-about .om-about-section .om-container { text-align: left; }
.om-about .om-about-section h2 { text-align: left; }
.om-about .om-about-close {
    margin-top: 26px;
    font-size: 19px;
    font-weight: 600;
    color: #032d60;
}
/* Headline only, so the hero does not need the height a hero with four stacked components does.
   The generated .om-hero--compact still reserves room for the rating and the logo strip. */
.om-about-hero .om-hero__inner { padding-bottom: 0; }
.om-about-hero h1 { margin-bottom: 0; }

/* --- Security (template-security.php) ---------------------------------------------------------
   Shares .om-about's prose treatment; these are the two things it adds. The card grid opts out
   of .om-card-grid's rigid 3x225px geometry the same way the integrations page does -- these
   cards have no icon and their bodies are different lengths.
   --------------------------------------------------------------------------------------------- */
.om-sec-list {
    margin: 18px 0 0;
    padding: 0;
    list-style: none;
}
.om-sec-list li {
    position: relative;
    padding-left: 28px;
    margin-bottom: 10px;
    color: #4a5561;
    line-height: 1.6;
}
.om-sec-list li::before {
    content: '';
    position: absolute;
    left: 2px;
    top: .62em;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--orange-d, #dd8210);
}
.om-card-grid.om-sec-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
.om-sec-grid article,
.om-sec-grid article:nth-child(n+4) {
    display: block;
    width: auto;
    height: auto;
    min-height: 0;
    padding: 26px 28px;
    border: 1px solid #e4e8ee;
    border-radius: 12px;
    background: #fff;
}
.om-sec-grid h3 {
    margin: 0 0 10px;
    color: var(--navy, #042854);
    font-size: 18px;
    line-height: 1.3;
    font-weight: 700;
}
.om-sec-grid p { margin: 0; min-height: 0; color: #616b76; font-size: 15px; line-height: 1.6; }
@media (max-width: 900px) { .om-card-grid.om-sec-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .om-card-grid.om-sec-grid { grid-template-columns: minmax(0, 1fr); } }

/* A section with no artwork keeps the same left edge as one that has it -- the measure comes
   from a max-width on the text, not from centring a narrower column. */
.om-sec-solo > div { max-width: 760px; }

/* Security artwork. The live page's own PNGs: one large safe dial beside the security statement,
   grey icons beside the remaining sections and above the cards. Two columns that collapse to one
   below 780px, with the image first only where the row is flipped. */
.om-sec-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 48px;
    align-items: center;
}
.om-sec-row--flip { grid-template-columns: 260px minmax(0, 1fr); }
.om-sec-art { width: 100%; height: auto; }
.om-sec-art--icon { width: 160px; justify-self: center; }
.om-sec-grid article img { display: block; width: 44px; height: auto; margin: 0 0 14px; }
@media (max-width: 780px) {
    .om-sec-row,
    .om-sec-row--flip { grid-template-columns: minmax(0, 1fr); gap: 28px; }
    /* Text first on a phone whichever side the image sits on at desk width. */
    .om-sec-row--flip > img { order: 2; }
    .om-sec-art { max-width: 220px; justify-self: center; }
}

/* --- Customer Stories (template-customer-stories.php) -----------------------------------------
   58 quotes of wildly different lengths -- 46 characters to 1,447 -- so the layout is CSS
   columns rather than a grid. Every card is its own height, nothing is cropped, and there is no
   script measuring anything. break-inside on the card, and inline-block on top of it, because
   Safari still splits a block across a column break without the second.
   --------------------------------------------------------------------------------------------- */
.om-stories__grid {
    column-count: 3;
    column-gap: 24px;
}
.om-story {
    display: inline-block;
    width: 100%;
    margin: 0 0 24px;
    padding: 26px 28px;
    border: 1px solid #e4e8ee;
    border-radius: 12px;
    background: #fff;
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
}
.om-story .om-stars { font-size: 15px; color: var(--orange-d, #dd8210); }
.om-story blockquote {
    margin: 12px 0 18px;
    color: #3b4650;
    font-size: 15px;
    line-height: 1.65;
}
/* The quotes are set as speech, so they take quotation marks the caption does not. */
.om-story blockquote::before { content: '\201C'; }
.om-story blockquote::after  { content: '\201D'; }
.om-story figcaption { display: flex; flex-direction: column; gap: 2px; }
.om-story__logo {
    display: block;
    max-width: 120px;
    max-height: 38px;
    width: auto;
    height: auto;
    margin: 0 0 10px;
}
.om-story__who { color: var(--navy, #042854); font-size: 14px; font-weight: 700; line-height: 1.4; }
.om-story__where { color: #6b7681; font-size: 13px; line-height: 1.4; }

@media (max-width: 1000px) { .om-stories__grid { column-count: 2; } }
@media (max-width: 640px)  { .om-stories__grid { column-count: 1; } }

/* Customer Stories hero: the homepage's light wash instead of the product pages' navy, with a
   drawn conversation behind it. The generated .om-hero sets a navy background and white type
   inside its own breakpoints, so each of these has to out-score a single-class rule. */
.om-hero.om-stories-hero {
    position: relative;
    overflow: hidden;
    /* Taller than the compact hero, because the artwork needs somewhere to be: at the compact
       height the viewBox is cropped to a band and the bubbles arrive as fragments. */
    padding: 74px 0 86px;
    background: radial-gradient(1000px 460px at 50% -80px, #fff7ec 0%, #fff 55%, #eef1f6 100%);
}
.om-hero.om-stories-hero h1 { color: #032d60; }
.om-stories-hero__art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
.om-stories-hero__bubbles use {
    fill: rgba(21, 48, 76, .045);
    stroke: rgba(21, 48, 76, .09);
    stroke-width: 1.5;
}
.om-stories-hero__mark {
    fill: rgba(249, 178, 51, .26);
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 200px;
    font-weight: 700;
}
.om-stories-hero__mark--sm { font-size: 130px; fill: rgba(21, 48, 76, .10); }
.om-stories-hero__stars text {
    fill: rgba(249, 178, 51, .28);
    font-size: 22px;
    letter-spacing: 3px;
}
/* On a phone the hero is a third of the height and the artwork lands on the words. */
@media (max-width: 767px) {
    .om-stories-hero__art { opacity: .55; }
}

/* --- Legal (template-legal.php) ---------------------------------------------------------------
   Five documents as full-width rows rather than a card grid: the titles run from two words to
   four, and in a grid the short ones sit in a lot of empty card. A row gives every one of them
   the same shape and puts the file type in the same place.
   --------------------------------------------------------------------------------------------- */
.om-legal__list {
    max-width: 820px;
    margin: 0 auto;
    padding: 0;
    list-style: none;
}
.om-legal__list li + li { margin-top: 12px; }
.om-legal__list a {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 20px 24px;
    border: 1px solid #e4e8ee;
    border-radius: 12px;
    background: #fff;
    text-decoration: none;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.om-legal__list a:hover {
    border-color: var(--orange-d, #dd8210);
    box-shadow: 0 10px 24px -18px rgba(21, 48, 76, .5);
    transform: translateY(-1px);
}
.om-legal__icon { flex: 0 0 32px; width: 32px; height: 40px; }
.om-legal__page { fill: #f3f6f9; stroke: #cfd8e2; stroke-width: 1.5; }
.om-legal__fold { fill: #dbe3ec; }
.om-legal__rules rect { fill: var(--orange-d, #dd8210); opacity: .55; }
.om-legal__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.om-legal__text b { color: var(--navy, #042854); font-size: 17px; font-weight: 700; }
.om-legal__note { color: #6b7681; font-size: 14px; line-height: 1.5; }
/* The file type sits at the end of the row, where the eye ends up anyway. */
.om-legal__meta {
    margin-left: auto;
    flex: 0 0 auto;
    padding: 5px 11px;
    border-radius: 999px;
    background: #f3f6f9;
    color: #6b7681;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .06em;
}
@media (max-width: 600px) {
    .om-legal__list a { flex-wrap: wrap; gap: 14px; padding: 18px; }
    .om-legal__meta { margin-left: 0; }
}

/* --- The closing CTA band, shared -------------------------------------------------------------
   This started on the homepage and now ends several pages, so it lives here rather than in
   page-home.css. It is NOT .om-cta: that one is the product pages' flat orange section with a
   fixed height and a testimonial in it. This is the gradient band with a heading, a sentence and
   a white button.

   `.cta .btn { margin-top: 14px }` and `.cta .btn { background: #fff }` came with the original
   and never applied to anything -- every page using this band draws its button as .om-btn -- so
   the rules that matter are written for that.
   --------------------------------------------------------------------------------------------- */
.cta {
    padding: 60px 28px;
    background: linear-gradient(135deg, var(--orange, #f7941e), #e8820b);
    text-align: center;
}
.cta h2 { color: #fff; font-size: 34px; }
/* A measure: the band is full width, and one line of a sentence would run the whole screen. */
.cta p {
    max-width: 620px;
    margin: 0 auto;
    color: #fff8ef;
    font-size: 16px;
    line-height: 25px;
}
.cta .micro { color: #ffe9cd; }
.cta .om-btn { margin-top: 34px; }

/* --- Long legal documents (template-privacy.php) ----------------------------------------------
   A reading column, not a marketing section. 760px at 17px is about 78 characters, which is the
   width a long document is actually read at; the OM container is 1108px and would run to 115.
   --------------------------------------------------------------------------------------------- */
.om-legaldoc__body { max-width: 780px; }
.om-legaldoc__body p,
.om-legaldoc__body li {
    color: #3f4a55;
    font-size: 16px;
    line-height: 1.7;
}
.om-legaldoc__updated {
    margin: 0 0 30px;
    padding-bottom: 18px;
    border-bottom: 1px solid #e4e8ee;
    color: #6b7681;
    font-size: 14px;
    font-weight: 700;
}
.om-page .om-legaldoc__body h2 {
    margin: 40px 0 12px;
    color: var(--navy, #042854);
    font-size: 21px;
    line-height: 1.35;
    font-weight: 700;
    text-align: left;
}
/* The number hangs in the margin, so fifteen headings line up on their titles rather than on
   their digits -- "1." and "15." are different widths. */
.om-legaldoc__num { display: inline-block; min-width: 34px; color: var(--orange-d, #dd8210); }
.om-legaldoc__items { margin: 0; padding: 0; list-style: none; }
.om-legaldoc__items li { margin: 0 0 14px; padding-left: 34px; }
.om-legaldoc__sub { padding-left: 64px !important; }
.om-legaldoc__body a { color: var(--orange-d, #dd8210); font-weight: 600; word-break: break-word; }
.om-legaldoc__contacts {
    margin-top: 44px;
    padding: 24px 28px;
    border: 1px solid #e4e8ee;
    border-radius: 12px;
    background: #f7f9fb;
}
.om-legaldoc__contacts p { margin: 0 0 12px; }
.om-legaldoc__contacts p:last-child { margin-bottom: 0; }
@media (max-width: 600px) {
    .om-legaldoc__items li { padding-left: 20px; }
    .om-legaldoc__sub { padding-left: 38px !important; }
    .om-legaldoc__num { min-width: 26px; }
}

/* --- .om-hero--wash: the light hero -----------------------------------------------------------
   The homepage's radial wash, for pages that should not open on navy. Everything here exists to
   undo the navy hero, which design-om.css re-declares inside its own breakpoints -- hence the
   doubled selectors, which score (0,2,0) and hold at every width.

   The rating and the review marks are the shared components, so their light-field treatment
   belongs here rather than being written again per page: the marks ship white-on-navy and need
   inverting, not recolouring, because they are images.
   --------------------------------------------------------------------------------------------- */
.om-hero.om-hero--wash {
    background: radial-gradient(1000px 460px at 50% -80px, #fff7ec 0%, #fff 55%, #eef1f6 100%);
}
.om-hero--wash h1 { color: #032d60; }
.om-hero--wash .om-hero-lead { color: #4a5561; }
.om-hero--wash .om-rating {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 3px 8px;
    margin: 0 auto;
    color: #032d60;
    font-size: 15px;
    line-height: 1.4;
}
.om-hero--wash .om-rating strong { font-size: 16px; font-weight: 700; }
.om-hero--wash .om-stars { font-size: 17px; }
.om-hero--wash .om-rating__note { color: #616b76; font-size: 15px; font-weight: 400; }
.om-hero--wash .om-review-sites--logos { margin-top: 18px; }
.om-hero--wash .om-review-sites--logos img { filter: invert(1) grayscale(1) contrast(1.4); opacity: .62; }

/* --- Thank-you page ---------------------------------------------------------------------------
   Production's layout (bntouch_theme main.css, .thankyou-wrapper), rebuilt rather than restyled:
   pale band + heading, one white panel, video 40% / calendar 60%. Below 1200px the panel stacks
   in reverse so the calendar leads; below 768px the panel goes edge to edge and the step headings
   are hidden.
   Every rule carries .om-page.om-thanks: the design system styles `.om-page h2` and `.om-page p`,
   and a single class loses to those. */
.om-page.om-thanks { background: #f3f3f3; text-align: center; padding-bottom: 60px; }
.om-page.om-thanks .om-thanks__title {
    max-width: 1140px; margin: 0 auto; padding: 55px 15px;
    font-size: 30px; line-height: 1.35; font-weight: 400; color: #4a5561;
}
.om-page.om-thanks .om-thanks__panel { display: flex; margin: 0 40px; background: #fff; text-align: left; }
.om-page.om-thanks .om-thanks__step { flex: 1 1 0; min-width: 0; box-sizing: border-box; }
.om-page.om-thanks .om-thanks__step--video { padding: 40px; }
.om-page.om-thanks .om-thanks__step--book { flex: 0 0 60%; max-width: 60%; border-left: 1px solid #dadada; }
.om-page.om-thanks .om-thanks__stephead {
    text-align: left; margin: 0 0 40px; padding-bottom: 40px; border-bottom: 1px solid #dadada;
    font-size: 25px; line-height: 1; font-weight: 400; color: #5c5f67;
}
.om-page.om-thanks .om-thanks__step--book .om-thanks__stephead { margin: 0; padding: 40px 40px 40px 20px; }
.om-page.om-thanks .om-thanks__video { position: relative; aspect-ratio: 16 / 9; background: #032d60; }
.om-page.om-thanks .om-thanks__video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.om-page.om-thanks .om-thanks__text { width: 60%; margin: 50px auto 0; text-align: center; font-size: 18px; line-height: 1.9; font-weight: 300; color: #616161; }
/* The height here is only the starting point and the fallback: the page script sets the box to
   the height Calendly reports for its content (see template-thank-you.php). */
.om-page.om-thanks .om-thanks__calendar { min-width: 320px; width: 100%; height: 1000px; overflow: auto; -webkit-overflow-scrolling: touch; transition: height .2s ease; }
/* display:block matters more than it looks. An iframe is display:inline by default, so it
   sits on the text baseline and leaves a few pixels of descender space underneath - 7px here.
   The box is sized to the iframe, so those 7px overflowed it and, with overflow:auto above,
   drew a second scrollbar inside the page for a gap nobody could see. */
.om-page.om-thanks .om-thanks__calendar iframe { width: 100%; height: 100% !important; display: block; }
@media (max-width: 1200px) {
    .om-page.om-thanks .om-thanks__panel { flex-direction: column-reverse; }
    .om-page.om-thanks .om-thanks__step--book { flex: 0 0 auto; max-width: none; width: 100%; border-left: 0; border-bottom: 1px solid #dadada; }
    .om-page.om-thanks .om-thanks__step--book .om-thanks__stephead { padding-left: 40px; }
}
@media (max-width: 768px) {
    .om-page.om-thanks .om-thanks__panel { margin: 0; }
    .om-page.om-thanks .om-thanks__stephead { display: none; }
    .om-page.om-thanks .om-thanks__step--video { padding: 30px 20px 40px; }
    .om-page.om-thanks .om-thanks__text { width: 100%; margin-top: 30px; }
}
@media (max-width: 480px) { .om-thanks__title { font-size: 20px; padding: 35px 15px; } }
/* The badge band closing the page. On the homepage it is white, pale until hovered, and leaves
   its bottom to the next section; here it ends the page on the pale ground, in full colour as
   production shows them, with room underneath for the captions that hang out of its flow. */
.om-page.om-thanks { padding-bottom: 0; }
.om-page.om-thanks .badgeband { background: transparent; padding: 56px 20px 84px; }
.om-page.om-thanks .badges img { filter: none; opacity: 1; }
