/* What the texting page needs that the Opportunity Mining system does not already have: the
   loan-life rail, icons inside the feature cards, and the two or three places a stock OM rule is
   sized for different content.

   Everything else on the page is plain .om-* reuse - hero, card grid, steps, dark payoff and
   the closing band are untouched. Nothing here belongs in design-om.css (generated from the
   live OM page) or design-om-extras.css (shared), so the file is page-scoped under #texting
   and every override out-specifies the shared rule it beats rather than relying on load order. */

/* ------------------------------------------------------------------------------------------
   The feature band.

   These rules are page-home.css's, lifted as they are - .omgrid, .eng, .eic and the bleed
   arithmetic from the homepage's Opportunity Mining band - after two attempts to build something
   that matched it were told, correctly, that they did not. Yuri, 2026-10-03: "just take the css
   from index".

   Copied rather than shared, and the copy is deliberate: page-home.css is generated from the
   homepage comp and this page does not load it. If that band is ever restyled, this does not
   follow on its own.

   What is the homepage's, exactly: the flat full-bleed columns, the three tints, no border, no
   radius, no shadow, 30px/26px padding, the 28px transparent icon with the orange stroke, the
   16px dark title and 16px grey body.

   Three columns, which is the homepage's, and that is why the tile list next door is six long:
   four columns gave each tile 266px against the homepage's 355px and the copy was visibly
   cramped, so the band went back to three and the content came down to fit it. Any count that is
   not a multiple of three leaves a hole at the end of the last row, because the fills are per
   tile.
   ------------------------------------------------------------------------------------------ */
#texting .tx-band { overflow-x: clip; padding: 62px 0 46px; }
#texting .omgrid {
    /* The homepage's arithmetic, at four columns: every tile keeps the width it had inside the
       1064px content column, and the end tiles of each row carry the leftover viewport on their
       outside edge - so the band runs to the screen edge while the copy stays on the column. */
    --om-col: calc(min(100vw, 1064px) / 3);
    --om-bleed: max(0px, (100vw - 1064px) / 2);
    display: grid;
    grid-template-columns:
        calc(var(--om-col) + var(--om-bleed)) var(--om-col) calc(var(--om-col) + var(--om-bleed));
    gap: 0;
    width: 100vw;
    max-width: none;
    margin: 30px calc(50% - 50vw) 0;
}
#texting .eng {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    background: var(--fill);
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 30px 26px;
    text-align: left;
}
/* The bleed is padding, so the copy starts where the column starts. */
#texting .eng:nth-child(3n+1) { padding-left: calc(var(--om-bleed) + 26px); }
#texting .eng:nth-child(3n)   { padding-right: calc(var(--om-bleed) + 26px); }
#texting .eng:nth-child(3n+1) { --fill: #fdf2e2; }
#texting .eng:nth-child(3n+2) { --fill: #eaf1f9; }
#texting .eng:nth-child(3n+3) { --fill: #f1f3f5; }
#texting .eic {
    flex: 0 0 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border-radius: 0;
}
#texting .eic svg {
    width: 26px;
    height: 26px;
    fill: none;
    stroke: #f9b233;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
#texting .eng b { color: #2e3033; font-size: 16px; font-weight: 700; }
#texting .eng p { color: #616b76; font-size: 16px; line-height: 22px; margin: 6px 0 0; }
/* The one addition to the homepage's tile: it has no link in it, these do. Small and quiet, so
   it reads as a way out of the tile rather than as a second heading. */
#texting .eng a {
    display: inline-block;
    margin-top: 10px;
    color: #9a6b12;
    font-size: 13px;
    font-weight: 700;
}

/* Stacked layouts have no bleed to distribute. */
@media (max-width: 980px) {
    #texting .omgrid { grid-template-columns: repeat(2, 1fr); }
    #texting .eng:nth-child(3n+1) { padding-left: 26px; }
    #texting .eng:nth-child(3n)   { padding-right: 26px; }
    #texting .eng:nth-child(2n+1) { padding-left: calc(var(--om-bleed) + 26px); }
    #texting .eng:nth-child(2n)   { padding-right: calc(var(--om-bleed) + 26px); }
}
@media (max-width: 680px) {
    #texting .omgrid { grid-template-columns: 1fr; }
    #texting .eng {
        padding-left: calc(var(--om-bleed) + 26px) !important;
        padding-right: calc(var(--om-bleed) + 26px) !important;
    }
}

/* ------------------------------------------------------------------------------------------
   The hero's texture.

   The navy is a flat fill everywhere on the site, and on a hero this tall it reads as a slab.
   This was a pair of CSS gradients for an hour on 2026-10-03 and Yuri preferred a large image,
   so it is one now - generated rather than photographed, and generated by a script in the repo
   rather than drawn in an editor, so it can be regenerated and argued with.

   Page-scoped and on a pseudo-element, so the hero's own background is untouched and switching
   this off is deleting one rule.
   ------------------------------------------------------------------------------------------ */
#texting .om-hero {
    position: relative;
    overflow: hidden;
}
#texting .om-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    /* 2400x1000, built by src/tools/build-texting-hero.js: one phone cropped off the right edge
       with a thread inside it, at 12% white, lit from that side, with the centre washed back down
       so the headline sits on clean ground. Yuri picked it from the five in
       build-texting-hero-variants.js and asked for it fainter than the preview. The file is
       generated, not drawn by hand: re-run the script to change it, do not edit the JPEG. */
    /* ?v= is a cache-buster and it has to be BUMPED EVERY TIME the image is regenerated. The
       filename does not change when the artwork does, so without this a browser that has seen
       the page keeps the old picture - which is exactly what happened on 2026-10-03: the second
       version was on staging, byte for byte, and looked unchanged in the browser. */
    background-image: url('../img/texting/hero-texture.jpg?v=6');
    background-size: cover;
    background-position: center;
    /* The navy under it, for the moment before the image arrives and for the case where it 404s
       after a half-synced deploy. */
    background-color: var(--navy);
    pointer-events: none;
}
/* The content sits over both layers. .om-hero__inner is not positioned in the shared sheet, so
   without this the wash paints across the headline. */
#texting .om-hero__inner {
    position: relative;
    z-index: 1;
}

/* ------------------------------------------------------------------------------------------
   The loan-life rail.

   One line down the page with the six stages of a file hung off it, alternating sides. No boxes:
   this was a row of six columns, then three-across cards, and both were wrong - six columns made
   every stage a strip too narrow to rank anything inside it, and the cards turned a path into a
   product grid. Yuri, 2026-10-03: no cards.

   The line is real and continuous, a single gradient on .tx-journey, and the nodes sit on it with
   the section's own background as their halo, so nothing has to be measured against anything. The
   grid is three columns - left half, the line, right half - and odd stages take the left cell with
   their text ranged right, even ones the right cell ranged left. Both sides stay in document
   order, so what a screen reader hears is stage 1 through 6 regardless of which side each landed.

   Weight inside a stage is Yuri's order: what you do, then what it gets you, then the example
   message last and quiet. The example was a navy bubble as big as the heading until 2026-10-03,
   which made the section look like it was selling sample texts.
   ------------------------------------------------------------------------------------------ */
#texting .tx-journey-sec { background: var(--pale); padding: 62px 0 58px; }
#texting .tx-journey {
    position: relative;
    list-style: none;
    max-width: 1000px;
    margin: 50px auto 0;
    padding: 0;
}
/* The line. Faded at both ends rather than stopped dead, so it reads as the life of a loan
   running past the section instead of a six-item list with a top and a bottom. */
#texting .tx-journey::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    margin-left: -1px;
    z-index: 0;
    /* The background lives with the pulse below, which layers a lit segment over this one. */
    background: linear-gradient(180deg, #f5f7fa 0, #ccd5e0 60px, #ccd5e0 calc(100% - 60px), #f5f7fa 100%);
}
#texting .tx-stop {
    position: relative;
    /* The whole stage sits above the line and the pulse running down it. It has to be here rather
       than on the node: .tx-stop carries the arrival animation on opacity, which makes it a
       stacking context of its own, so a z-index on the node inside it is sealed in and loses to a
       pseudo-element that paints after the whole list. The node's own z-index is kept for the
       line, which it still has to cover. */
    z-index: 1;
    display: grid;
    grid-template-columns: 1fr 96px 1fr;
    align-items: start;
    padding-bottom: 46px;
}
#texting .tx-stop:last-child { padding-bottom: 0; }
/* Odd stages left, even stages right. grid-column is what moves them, not order, so the DOM
   stays in the order the loan happens.

   grid-row: 1 on BOTH, and it is not decoration. The node comes first in the DOM and sits in
   column 2; a body that then asks for column 1 is behind the auto-placement cursor, so the
   browser drops it to a second row - which is why every left-hand stage sat exactly one node
   height lower than its icon, and every right-hand one lined up. Pinning the row is what keeps
   the heading level with the node on both sides. */
#texting .tx-stop__body { grid-column: 1; grid-row: 1; text-align: right; padding-top: 2px; }
#texting .tx-stop:nth-child(even) .tx-stop__body { grid-column: 3; text-align: left; }

/* The node sits on the line, filled, with a ring of the section background so the line appears
   to pass behind it. */
#texting .tx-stop__node {
    grid-column: 2;
    grid-row: 1;
    /* Above the line and above the pulse travelling down it, so the head of the pulse passes
       behind each node instead of across its face. Both pseudo-elements sit at z-index 0 and
       .tx-stop is position: relative with z-index auto, so this 1 is measured against them. */
    z-index: 1;
    position: relative;
    justify-self: center;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 66px;
    height: 66px;
    border-radius: 50%;
    background: var(--navy);
    box-shadow: 0 0 0 7px var(--pale);
}
#texting .tx-stop__node svg {
    width: 31px;
    height: 31px;
    fill: none;
    stroke: var(--orange);
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* The job: the biggest thing in the stage, because it is the thing being sold. */
#texting .tx-stop h3 {
    /* No eyebrow above it any more, so the heading is what the node lines up with. */
    margin: 2px 0 14px;
    font-size: 21px;
    line-height: 1.28;
}
#texting .tx-stop__more {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 13.5px;
    line-height: 1.5;
    color: #5a6472;
}
#texting .tx-stop__more li { margin: 0 0 6px; }
/* The benefit as a pill, which is the one place in the stage with a fill: the eye lands on it
   after the heading and before anything grey. */
#texting .tx-stop__gain {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 14px 0 0;
    padding: 8px 14px;
    border-radius: 999px;
    background: #fff5e0;
    font-size: 13px;
    line-height: 1.35;
    font-weight: 700;
    color: #6b4c05;
    text-align: left;
}
#texting .tx-stop__gain svg {
    flex: none;
    width: 16px;
    height: 16px;
    fill: none;
    stroke: #d99600;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* The example, last and quiet: there for the visitor who wants to know what one of these says,
   invisible to the one who does not. */
#texting .tx-stop__eg {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 12px 0 0;
    font-size: 11.5px;
    line-height: 1.45;
    font-style: italic;
    color: #8b939d;
}
/* Ranged right on the left side of the line, so the mark stays on the outside edge of the stage
   rather than floating in the middle of the page. */
#texting .tx-stop__eg { flex-direction: row-reverse; }
#texting .tx-stop:nth-child(even) .tx-stop__eg { flex-direction: row; }
#texting .tx-stop__eg svg {
    flex: none;
    width: 14px;
    height: 14px;
    margin-top: 2px;
    fill: none;
    stroke: #b7bec8;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
#texting .tx-stop__gain,
#texting .tx-stop__eg { max-width: 420px; }
#texting .tx-stop__body > .tx-stop__gain { margin-left: auto; }
#texting .tx-stop:nth-child(even) .tx-stop__body > .tx-stop__gain { margin-left: 0; }
#texting .tx-stop__eg { margin-left: auto; }
#texting .tx-stop:nth-child(even) .tx-stop__eg { margin-left: 0; margin-right: auto; }

#texting .tx-journey__cta { margin-top: 48px; text-align: center; }

/* One side of the line below the breakpoint. Alternating halves need a full desktop width; at
   tablet the right-ranged copy was four words to a line, and at phone width it was two. The line
   moves to the left margin and every stage hangs off it the same way.

   These two blocks were lost in a rewrite on 2026-10-03 and the section shipped to staging with
   the three-column layout at every width - 130px columns on a phone. They are restored here
   DIRECTLY under the layout they correct, not at the foot of the file, so the next edit to the
   rail has them in view. */
@media (max-width: 900px) {
    #texting .tx-journey { max-width: 620px; }
    #texting .tx-journey::before,
    #texting .tx-journey::after { left: 33px; }
    #texting .tx-stop { grid-template-columns: 66px 1fr; column-gap: 22px; padding-bottom: 38px; }
    #texting .tx-stop__node { grid-column: 1; justify-self: start; }
    #texting .tx-stop__body,
    #texting .tx-stop:nth-child(even) .tx-stop__body { grid-column: 2; text-align: left; }
    #texting .tx-stop__eg,
    #texting .tx-stop:nth-child(even) .tx-stop__eg { flex-direction: row; margin-left: 0; margin-right: auto; }
    #texting .tx-stop__body > .tx-stop__gain,
    #texting .tx-stop:nth-child(even) .tx-stop__body > .tx-stop__gain { margin-left: 0; }
}
@media (max-width: 767px) {
    #texting .tx-stop { grid-template-columns: 52px 1fr; column-gap: 16px; }
    #texting .tx-stop h3 { font-size: 18px; }
    #texting .tx-journey::before,
    #texting .tx-journey::after { left: 26px; }
    #texting .tx-stop__node { width: 52px; height: 52px; box-shadow: 0 0 0 6px var(--pale); }
    #texting .tx-stop__node svg { width: 25px; height: 25px; }
}

/* ------------------------------------------------------------------------------------------
   The stages arriving.

   Armed by texting.js (.is-armed), never by this file - see the contract at the top of that
   script. A fade and 6px of rise over .45s: no horizontal travel, no stagger, no delay.

   Two failed settings are worth knowing about, both from 2026-10-03. Sliding in from the side of
   the line over .85s made the copy unreadable. A .25s fade armed a quarter-screen early was the
   overcorrection - it was always finished before the stage could be seen, which is the same as
   having no animation. This is the middle: the stage is armed as it crosses into view, so the
   movement happens where it can be noticed and is over before it can be waited on.
   ------------------------------------------------------------------------------------------ */
#texting .tx-journey.is-armed .tx-stop { opacity: 0; }
#texting .tx-journey.is-armed .tx-stop.is-in { animation: tx-stop-in .45s ease-out both; }
@keyframes tx-stop-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}
/* A belt to the script's braces: it does not arm under reduced motion, so this should never
   apply there. If a future change arms it anyway, this is what stops seven stages sitting at
   opacity 0. */
@media (prefers-reduced-motion: reduce) {
    #texting .tx-journey.is-armed .tx-stop {
        animation: none;
        opacity: 1;
        transform: none;
    }
}

/* ------------------------------------------------------------------------------------------
   The pulse.

   The one piece of motion on the rail, and the only one: seven stages used to fade in as they
   were scrolled to, and that came out on 2026-10-03 because arriving copy is copy you cannot
   read - you reach a stage and wait for it before you can start. Nothing here is armed by
   JavaScript any more, and texting.js went with it.

   What is left is a message running the length of the line, over and over, which is the section's
   claim drawn rather than said: texting is working at every stage of the file. Three parts moving
   on one clock - a lit segment of the line, a head on the front of it, and a ring breathing out
   of the head - so it reads as something travelling rather than a dot sliding.

   Eleven seconds: slow enough to be travel rather than a loading bar, with a rest at the bottom
   so it is not a metronome in the corner of the eye. The reduced-motion guard at the end of this
   block is the ONLY thing that switches it off, since no script is involved.
   ------------------------------------------------------------------------------------------ */
/* The line carries two layers: the grey rail, and a lit segment travelling down it. The segment
   is a background position rather than an element, so it is clipped by the line itself and needs
   nothing to mask its ends. */
#texting .tx-journey::before {
    background-image:
        linear-gradient(180deg, #f9b23300 0, var(--orange) 45%, #f9b23300 100%),
        linear-gradient(180deg, #f5f7fa 0, #ccd5e0 60px, #ccd5e0 calc(100% - 60px), #f5f7fa 100%);
    background-repeat: no-repeat;
    background-size: 100% 190px, 100% 100%;
    background-position: 0 -190px, 0 0;
    animation: tx-pulse-line 9s linear infinite;
}
/* Linear and with no rest at the end: an eased travel that then sat still for four seconds read
   as an animation that had run once and stopped, which is what Yuri saw on 2026-10-03. It now
   leaves the bottom and re-enters the top in the same second, so the loop is the point. */
@keyframes tx-pulse-line {
    0%   { background-position: 0 -190px, 0 0; }
    100% { background-position: 0 calc(100% + 190px), 0 0; }
}
/* The head of it. Bigger than the line by some margin, which is the point - at 9px it read as a
   bullet that had come loose. */
#texting .tx-journey::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 0;
    width: 13px;
    height: 13px;
    margin-left: -6.5px;
    z-index: 0;
    border-radius: 50%;
    background: var(--orange);
    box-shadow: 0 0 0 6px #f9b2333d, 0 0 22px 6px #f9b23366;
    animation: tx-pulse-travel 9s linear infinite;
}
/* The head keeps the same 9s clock as the lit segment, so the two stay one object. It fades in
   over the first stage and out over the last rather than popping at the ends. */
@keyframes tx-pulse-travel {
    0%   { top: 0;    opacity: 0; transform: scale(.6); }
    6%   {            opacity: 1; transform: scale(1); }
    50%  {                        transform: scale(1.2); }
    94%  {            opacity: 1; transform: scale(1); }
    100% { top: 100%; opacity: 0; transform: scale(.6); }
}

/* The head's left has to be restated per breakpoint HERE, after the rule that sets it to 50%.
   The breakpoints that move the line to the left margin are further up the file, and a media
   query does not beat a later rule of the same specificity - so the line went left on a phone
   and the head stayed in the middle of the page, travelling down nothing. */
@media (max-width: 900px) {
    #texting .tx-journey::after { left: 33px; }
}
@media (max-width: 767px) {
    #texting .tx-journey::after { left: 26px; }
}

@media (prefers-reduced-motion: reduce) {
    /* Both halves go, and the line goes back to the plain grey rail it draws without them. A
       parked head mid-rail would read as a bullet, and a parked lit segment as a highlight on a
       stage that is no more important than the others. */
    #texting .tx-journey::after { content: none; }
    #texting .tx-journey::before {
        animation: none;
        background-image: linear-gradient(180deg, #f5f7fa 0, #ccd5e0 60px, #ccd5e0 calc(100% - 60px), #f5f7fa 100%);
        background-size: 100% 100%;
        background-position: 0 0;
    }
}

/* ------------------------------------------------------------------------------------------
   Odds and ends on stock components.
   ------------------------------------------------------------------------------------------ */

/* ------------------------------------------------------------------------------------------
   Automated calls.

   The same .om-card-grid as the texting features, so the band reads as a section of this site
   rather than as a footnote under one. Three cards, not eight, which is the only difference
   worth a rule.

   White, by Yuri, 2026-10-03 - and the reason it was pale until then still applies: .om-card-grid
   article IS white, so on a white band three cards vanish into it and read as three bare columns.
   The hairline border below is what replaces the contrast the pale band used to give them. Take
   the border off and the cards go with it.
   ------------------------------------------------------------------------------------------ */
/* .om-engines is 770px tall by hand in the generated sheet, and 785px again at the desktop
   breakpoint - measured for Opportunity Mining's six engine blurbs plus a lead. Three short cards
   do not fill that, which is the dead strip under the band. The height rule left this file when
   the feature band stopped being an .om-engines section; the calling band still is one. */
#texting .tx-voice {
    background: #fff;
    height: auto;
    min-height: 0;
    padding: 62px 0 54px;
}
#texting .tx-voice__cards { grid-template-columns: repeat(3, 1fr); }
#texting .tx-voice__cards article,
#texting .tx-voice__cards article:nth-child(n+4) {
    border: 1px solid #e4e9f0;
    border-radius: 12px;
    box-shadow: 0 10px 26px -22px #15304c66;
}
/* height: auto is what stops the longest card overflowing. The generated sheet gives every
   .om-card-grid article a FIXED height - 225px, 210px from the fourth - sized for Opportunity
   Mining's six engine blurbs; a min-height does not undo a height, so the middle card here ran
   its last two lines straight through the "See it in action" link under it. The grid rows stay,
   so the icon, heading, text and link keep their places. */
#texting .tx-voice__cards article,
#texting .tx-voice__cards article:nth-child(n+4) { height: auto; min-height: 210px; }
/* The heading row is a literal 36px in the generated sheet - one line of it - and two of these
   titles wrap to two. The row goes auto so it fits the taller title, and the icon and the heading
   both centre inside it, so a one-line title and a two-line title are each level with the middle
   of their icon rather than hanging off its top. */
#texting .tx-voice__cards article { grid-template-rows: auto minmax(0, 1fr) 24px; }
#texting .tx-voice__cards article i,
#texting .tx-voice__cards article h3 { align-self: center; }

@media (max-width: 1024px) {
    #texting .tx-voice__cards { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 767px) {
    #texting .tx-voice__cards { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------------------------------
   The stats band.

   .om-stats is built for three short labels - "40+ tools in your core plan". These two carry a
   sentence and a source line each, so the cells need a width and the gap has to come down from
   the 150px that spaced three terse ones.
   ------------------------------------------------------------------------------------------ */
/* width, not max-width, and height auto: the desktop breakpoint pins .om-stats to a 223px box
   with 185px cells, which is right for "40+ / tools in your core plan" and wraps a sentence into
   a column five words wide. */
/* Four cells, so the gap and the cell width come down from what three could afford. */
#texting .tx-stats { gap: 36px; height: auto; min-height: 0; padding: 40px 20px; }
#texting .tx-stats div { width: 250px; }
#texting .tx-stats span { line-height: 1.5; }
/* The source on its own line, a shade back from the claim it backs: checkable without
   competing with it. */
#texting .tx-stats small {
    display: block;
    margin-top: 6px;
    color: #8a929c;
    font-size: 10px;
}

@media (max-width: 767px) {
    #texting .tx-stats { flex-direction: column; gap: 26px; padding: 28px 20px; }
    #texting .tx-stats div { max-width: none; }
}
