:root {
    color-scheme: light;

    /* Every solid blue surface is painted with a gradient instead of a flat
       fill. Both start from the blue the site already used and lift toward
       white at the top; the big shapes take the smaller lift of the two.
       Big shapes - the video frame, the Yearly card, the Annual card. */
    --xnp-blue: #30a5e6;
    --xnp-blue-gradient: linear-gradient(180deg, #53b4ea 0%, #30a5e6 100%);

    /* Buttons, which are short enough that the same lift would read as a hard
       edge rather than as light. */
    --xnp-blue-top: #5eb9eb;   /* the light stop, and where a button's hover lands */
    --xnp-blue-gradient-button: linear-gradient(180deg, #5eb9eb 0%, #30a5e6 100%);
}

:root[data-theme="dark"] {
    color-scheme: dark;
}

html {
    scroll-behavior: smooth;
}

/* The dot tile is 2017px wide with a dot sitting on its own left edge. Nudging
   it 4px right, as this did, pushed that edge dot to x=4 and let the PREVIOUS
   tile's trailing dot show in the 4px strip before it - two dots side by side
   down the whole left edge of every page. Starting the tile flush removes the
   seam; the pattern moves 4px, which nothing else depends on. */
body {
    min-width: 320px;
    overflow-x: hidden;
    background-position: 0 -6px;
}

.xnp-variation-b {
    color: #262c41;
    background-color: #f8f8fa;
    background-image: url("assets/dots-6b14a7af.svg");
    background-position: 0 -6px;
    background-size: auto;
}

:root[data-theme="dark"] .xnp-variation-b {
    color: #fff;
    background-color: #262c41;
    background-image: url("assets/dots-6b14a7af.svg");
}

:root[data-theme="light"] .xnp-variation-b {
    color: #262c41;
    background-color: #f8f8fa;
    background-image: url("assets/dots-6b14a7af.svg");
}

.xnp-variation-b .bg-purple-50 {
    color: #262c41;
}

a,
button {
    -webkit-tap-highlight-color: transparent;
}

.site-logo {
    display: block;
    width: 7.5rem;
    height: auto;
}

.hero-illustration-mobile {
    display: none;
}

@media (min-width: 640px) {
    .hero-cta {
        width: 200px;
    }
}

.xnp-variation-b .hero-cta-secondary {
    color: inherit;
}

:root[data-theme="dark"] .xnp-variation-b .hero-cta-secondary {
    border-color: rgba(255, 255, 255, 0.3);
    background: rgba(255, 255, 255, 0.06);
}

@media (min-width: 1024px) {
    .hero-copy {
        min-height: 674px;
    }
}

@media (max-width: 639px) {
    .site-header .site-logo {
        width: 6.5rem;
    }

    .hero-illustration {
        margin-inline: auto;
        transform: translateX(0);
    }

    .hero-illustration-desktop {
        display: none;
    }

    .hero-illustration-mobile {
        display: block;
        width: 138%;
        max-width: none;
        margin: 1.5rem -19%;
    }
}

.bg-cyan-600 {
    background-image: var(--xnp-blue-gradient);
}

a.bg-cyan-600 {
    background-image: var(--xnp-blue-gradient-button);
}

/* The hero button's 1px rim of solid blue was an accident: a transparent
   border with the gradient tiling into it. It looks deliberate, so it is now
   deliberate, and the pricing card's Book a Call gets the same. */
a.bg-cyan-600,
.price-card-monthly > a {
    border: 1px solid var(--xnp-blue);
}

/*
 * THE BUTTON HOVER FADES INTO ITS OWN LIGHT SHADE.
 *
 * A gradient is a background-IMAGE, and background-image does not animate - swap
 * one for another and it jumps. That is why the old hovers on this site
 * teleported to a darker blue that appeared nowhere in the button.
 *
 * The fix is to animate the STOPS instead of the image. A custom property
 * registered with @property has a type, so the browser can interpolate it, and
 * the gradient rebuilds itself every frame from the two that are moving. On
 * hover both stops travel to #5eb9eb - the light end the button already has -
 * so the button eases into a colour it was already wearing rather than a new one.
 *
 * The 1px rim stays #30a5e6 on purpose: it keeps the button's edge readable
 * against the page while the fill lightens.
 *
 * Without @property the two declarations still apply, so the hover still lands
 * on the right colour - it just arrives at once instead of easing. That is the
 * same behaviour as every other button on the site today, so nothing regresses.
 */
@property --xnp-grad-top {
    syntax: "<color>";
    inherits: false;
    initial-value: #5eb9eb;
}

@property --xnp-grad-bottom {
    syntax: "<color>";
    inherits: false;
    initial-value: #30a5e6;
}

/* Every blue button on the site, and only the buttons: the big blue shapes have
   nothing to hover. Blue buttons are all <a>, which is what separates them. */
a.bg-cyan-600,
.price-card-monthly > a {
    --xnp-grad-top: var(--xnp-blue-top);
    --xnp-grad-bottom: var(--xnp-blue);
    background-image: linear-gradient(180deg, var(--xnp-grad-top) 0%, var(--xnp-grad-bottom) 100%);
    /* ⚠ EVERY PROPERTY THIS CONTROL ANIMATES IS IN THIS ONE DECLARATION, AND
       SPLITTING IT AGAIN SILENTLY BREAKS TWO BUTTONS IN OPPOSITE DIRECTIONS.
       `transition` is not additive: a second declaration REPLACES the first,
       and which one survives is decided by specificity and source order, per
       element. That is what happened here — the lift rule listed transform and
       box-shadow, this rule listed the two gradient stops, and:

         - on the hero button `a.bg-cyan-600` (0,1,1) beat `.hero-cta` (0,1,0),
           so the colour eased and the LIFT SNAPPED;
         - on the pricing button both rules had the same selector, so the later
           one won, the lift eased and the COLOUR SNAPPED.

       One button flashed its colour, the other jumped its position, the CSS
       looked correct in both places, and nothing anywhere reported a fault.
       Keep them in one list. */
    transition:
        --xnp-grad-top 420ms cubic-bezier(0.45, 0, 0.25, 1),
        --xnp-grad-bottom 420ms cubic-bezier(0.45, 0, 0.25, 1),
        transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1),
        box-shadow 420ms cubic-bezier(0.45, 0, 0.25, 1),
        background-color 420ms cubic-bezier(0.45, 0, 0.25, 1),
        border-color 420ms cubic-bezier(0.45, 0, 0.25, 1),
        color 420ms cubic-bezier(0.45, 0, 0.25, 1);
}

/* ⚠ THE HOVER LANDS ON A GRADIENT, NOT ON A COLOUR, AND THAT IS THE FIX.
   Both stops used to travel to the same #5eb9eb, so the end of the hover was a
   FLAT button — the gradient the button wears at rest simply stopped existing
   under the pointer. Measured, the travel itself was fine: the stops
   interpolated smoothly across about 210ms. What read as a flash was not the
   speed, it was arriving at a solid fill. The eye sees "gradient, then paint".

   Now it brightens INTO another gradient: a lighter top and a lighter bottom
   that stay a step apart, so the button is never flat at any point in the
   hover, the leave, or the rest state.

   420ms with a gentle in-out curve, roughly double the old 240, because a
   colour ramp needs longer than a movement does before a person reads it as
   travelling rather than switching. */
a.bg-cyan-600:hover,
.price-card-monthly > a:hover {
    --xnp-grad-top: #8ed4f6;
    --xnp-grad-bottom: #4fb2e9;
}

a:focus-visible,
button:focus-visible {
    outline: 3px solid rgba(48, 165, 230, 0.45);
    outline-offset: 3px;
}

.product-mockup {
    position: relative;
    aspect-ratio: 1.78 / 1;
    display: block;
    width: 100%;
    overflow: hidden;
    isolation: isolate;
    container-type: inline-size;
    background: linear-gradient(180deg, #bbdff8 0%, #698bf8 100%);
}

.product-mockup img {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top center;
}

/* Wistia's own default play button, measured off a live Wistia player rather
   than eyeballed: square corners, 1.5625:1, and the player colour laid down
   twice - once blended with darken, once at 70% - with the icon in the shade
   Wistia derives from that colour (#30a5e6 -> #003172).
   Its width is piecewise, which is why the clamp looks odd: 19.5312% of the
   frame below 640px, a flat 125px from 640 to 960, 13.0208% above that, and
   never more than 162.5px. The plain percentage is the fallback for engines
   without container queries. */
.wistia-play-button {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 13.0208%;
    width: min(clamp(11.0677cqw, 106px, 16.6015cqw), 138px);
    aspect-ratio: 1.5625 / 1;
    transform: translate(-50%, -50%);
    overflow: hidden;
    cursor: pointer;
}

.wistia-play-button-tint {
    position: absolute;
    inset: 0;
    background-color: rgba(48, 165, 230, 0.7);
}

.wistia-play-button svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    fill: #ffffff;
}

.reference-product {
    margin-top: -1.875rem;
}

.reference-product > .max-w-7xl > .grid {
    margin-top: 3.75rem;
}

@media (min-width: 640px) {
    .reference-product {
        margin-top: -2.125rem;
    }
}

@media (min-width: 768px) {
    .reference-product {
        margin-top: -1.75rem;
    }

    .reference-product > .max-w-7xl > .grid {
        margin-top: 9rem;
    }
}

.code-demo {
    align-self: start;
    min-width: 0;
    position: relative;
    padding-bottom: 3.5rem;
}

.reference-deploy > * {
    min-width: 0;
}

.feedback-copy {
    align-self: center;
}

.feedback-copy .deploy-heading {
    font-size: 3rem;
    line-height: 1;
}

.feedback-copy > p {
    max-width: 31rem;
}

/* The same ink as every other body copy on the site. It was inheriting the
   near-black body colour, which made this one paragraph read heavier than the
   rest. Every width, not just a phone. The founder's name below keeps the dark
   colour, which matches the signature. */
.feedback-copy .deploy-heading + p {
    color: rgba(38, 44, 65, 0.78);
}

.founder-signoff {
    display: flex;
    align-items: flex-end;
    gap: 1rem;
    margin-top: 4rem;
}

.founder-signature {
    width: clamp(11.3333rem, 16vw, 14.6667rem);
    height: auto;
    flex: 0 0 auto;
}

.founder-identity {
    min-width: 0;
    padding-bottom: 0.3rem;
    font-size: 1rem;
    line-height: 1.45;
}

.founder-identity span {
    display: block;
}

:root[data-theme="dark"] .founder-signature {
    filter: invert(1);
}

.team-stack {
    position: relative;
    min-width: 0;
    min-height: 35rem;
    align-self: center;
    isolation: isolate;
    touch-action: pan-y;
    user-select: none;
    -webkit-user-select: none;
}

.team-card {
    --team-x: 0rem;
    --team-y: 0rem;
    --team-rotation: 0deg;
    position: absolute;
    top: 1.25rem;
    left: 50%;
    width: min(61%, 22.6667rem);
    aspect-ratio: 0.72;
    overflow: hidden;
    border: 1px solid #e7e3ff;
    border-radius: 1.75rem;
    background: #dbe1ed;
    box-shadow: 0 1.5rem 3.5rem rgba(38, 44, 65, 0.06);
    transform: translateX(-50%) translate(var(--team-x), var(--team-y)) rotate(var(--team-rotation));
    transform-origin: center 90%;
    transition: transform 700ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.team-card img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

.team-card-name {
    position: absolute;
    bottom: 1.25rem;
    left: 1.25rem;
    color: #262c41;
    font: inherit;
    font-size: 1.125rem;
    line-height: 1.5;
    font-weight: 400;
}

.team-card-dennis {
    --team-x: -3rem;
    --team-y: 0rem;
    --team-rotation: -6deg;
    z-index: 1;
}

.team-card-yelena {
    --team-x: 0rem;
    --team-y: 0.75rem;
    --team-rotation: -1.5deg;
    z-index: 2;
}

.team-card-dmitriy {
    --team-x: 3rem;
    --team-y: 1.5rem;
    --team-rotation: 5deg;
    z-index: 3;
}

.team-stack.is-expanded .team-card-dennis {
    --team-x: -10.5rem;
    --team-y: 0rem;
    --team-rotation: -8deg;
}

.team-stack.is-expanded .team-card-yelena {
    --team-x: 0rem;
    --team-y: -0.75rem;
    --team-rotation: 0deg;
}

.team-stack.is-expanded .team-card-dmitriy {
    --team-x: 10.5rem;
    --team-y: 0rem;
    --team-rotation: 8deg;
}

/* Drag state. Killing the transition is what makes the cards track the pointer 1:1 -
   .team-card carries a 700ms transform transition that would otherwise lag every frame.
   The mobile block declares its own copy of this rule for the mobile drag.

   Deliberately NO cursor rule anywhere on this stack, at any width: the pointer stays the
   ordinary arrow over these cards whether you hover them, press them or drag them. That
   costs the drag its only visible affordance, and it is a deliberate call - do not add a
   grab cursor back as an improvement. */
.team-stack.is-dragging .team-card {
    transition: none;
}

.team-card:focus-visible {
    outline: 3px solid #30a5e6;
    outline-offset: 4px;
}

:root[data-theme="dark"] .team-card {
    border-color: rgba(255, 255, 255, 0.3);
}

@media (min-width: 640px) {
    .deploy-heading {
        font-size: 2.5rem;
        line-height: 1;
    }
}

.code-demo pre {
    min-height: 15.4rem;
    margin: 0 0 0.8rem;
    overflow-x: auto;
    border-radius: 0.28rem;
    background: #262c41;
    color: #b9bdd0;
    padding: 1.25rem 1.5rem;
    font-family: "GT America", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.7rem;
    line-height: 1.45;
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12) inset;
}

.code-demo pre + pre {
    min-height: 15.4rem;
}

.code-comment {
    color: #4fe58a;
}

.code-keyword {
    color: #c884ff;
}

.code-string {
    color: #ff8eb8;
}

.code-run {
    position: absolute;
    right: 0;
    bottom: 0;
    min-width: 8.5rem;
    border-radius: 0.28rem;
    background: var(--xnp-blue-gradient);
    color: white;
    padding: 0.95rem 1.5rem;
    font-weight: 700;
    text-align: center;
}

.pricing-reference {
    display: grid;
    grid-template-areas:
        "copy"
        "cards";
    gap: 4rem;
    align-items: center;
}

.pricing-copy {
    grid-area: copy;
    max-width: 38rem;
    /* Right aligned at every width. `margin-left: auto` is what puts the box's
       own right edge on the column's right edge; `text-align` alone would ragged
       the text against a box still sitting on the left. */
    margin-left: auto;
    text-align: right;
}

.pricing-cards {
    grid-area: cards;
    position: relative;
    /* The white card is anchored to the bottom of this box, so this height is
       what sets the gap between it and the blue card's own content. The third
       bullet needs the extra 2.5rem at tablet width, where the two cards are
       nearly the same width and the overlap is vertical rather than diagonal. */
    min-height: 45.5rem;
}

.price-card {
    border-radius: 0.35rem;
    padding: 2.2rem;
}

.price-card h3 {
    font-size: 1.55rem;
    line-height: 1.2;
    font-weight: 700;
}

.price-card h3 + p {
    max-width: 18rem;
    margin-top: 0.55rem;
}

.price-card a {
    display: inline-block;
    min-width: 10rem;
    border-radius: 0.28rem;
    padding: 0.9rem 1.25rem;
    font-weight: 700;
    text-align: center;
}

.price-card ul {
    display: grid;
    gap: 1rem;
    margin-top: 2.2rem;
}

.price-card li {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}

.price-card li img {
    width: 1.45rem;
    height: 1.45rem;
    object-fit: contain;
}

.price-card li strong {
    margin-right: -0.25rem;
}

.price-card-cost {
    display: flex;
    align-items: flex-end;
    gap: 0.55rem;
    margin-top: 2.25rem;
    border-top: 1px solid currentColor;
    padding-top: 1.35rem;
    font-size: 3.8rem;
    line-height: 1;
    font-weight: 700;
}

.price-card-price-stack {
    margin-top: 2.25rem;
    border-top: 1px solid currentColor;
    padding-top: 0.85rem;
}

.price-card-price-stack .price-card-cost {
    margin-top: 0.3rem;
    border-top: 0;
    padding-top: 0;
}

.price-card-original-price {
    display: block;
    width: max-content;
    font-size: 1.15rem;
    line-height: 1.2;
    font-weight: 600;
    opacity: 0.82;
    text-decoration-thickness: 2px;
}

/* Same size and weight as the struck original price above, but this label is a
   qualifier on the price below it, not a superseded price - so no line through
   it. A `del` here would say "this number no longer applies". */
.price-card-starting-label {
    display: block;
    width: max-content;
    /* Two pixels under the 18.4px the struck original price uses. */
    font-size: 16.4px;
    line-height: 1.2;
    font-weight: 600;
    opacity: 0.82;
}

.price-card-period {
    padding-bottom: 0.35rem;
    font-size: 1rem;
    line-height: 1.5;
    font-weight: 400;
}

.price-card small {
    display: block;
    margin-top: 1.25rem;
    opacity: 0.52;
}

.price-card-yearly {
    position: absolute;
    inset: 0 auto auto 0;
    width: min(84%, 38rem);
    min-height: 36rem;
    background: var(--xnp-blue-gradient);
    color: white;
}

.price-card-yearly a {
    background: #262c41;
    color: white;
}

.price-card-topline {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

.pricing-cube {
    position: absolute;
    z-index: 2;
    top: -5.5rem;
    /* Sits past the end of the blue card's title. At 66.6667% it landed on top
       of the last word of "Google Ads Management" and hid it. */
    left: 80%;
    width: 9.8125rem;
    transform: translateX(-50%);
    pointer-events: none;
}

.price-card-monthly {
    position: absolute;
    z-index: 1;
    right: 0;
    bottom: 0;
    /* 66%, not 72%: this width only applies between 768 and 1023, where the two
       cards sit almost on top of each other. The six points buy back enough of
       the blue card for its price to stay readable beside this one. */
    width: min(66%, 32rem);
    min-height: 29rem;
    border: 1px solid rgba(231, 227, 255, 0.7);
    background:
        linear-gradient(80deg, rgba(241, 238, 255, 0.72) 8%, rgba(248, 247, 255, 0.64) 72%, rgba(255, 255, 255, 0.45) 100%);
    box-shadow: 0 1.5rem 4rem rgba(93, 86, 168, 0.08);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
}

.price-card-monthly > div:first-child {
    /* Wide enough for "Google Growth System" to stay on one line. The button no
       longer shares this row, so nothing is competing for the width. */
    max-width: 22rem;
}

.price-card-monthly > a {
    position: absolute;
    bottom: 2.2rem;
    right: 2.2rem;
    /* No `background` here: the gradient is set above from the two animated
       stops, and a shorthand at this point in the file would overwrite it and
       take the hover with it. */
    color: white;
}

.section-subheadline {
    max-width: 31rem;
}

.feature-spotlight {
    overflow: visible;
    background: transparent;
}

.feature-spotlight > img {
    display: block;
    max-width: 100%;
    height: auto;
    object-fit: contain;
}

.feature-spotlight > .ai-recommendation-illustration {
    transform: scale(0.92);
    transform-origin: center;
}

.reference-wide {
    padding-top: 1.3333rem;
    margin-bottom: calc(-0.8444rem - 2.2734vw);
}

.reference-wide > div {
    position: relative;
    top: calc(-0.4444rem - 2.2734vw);
}

.footer-legal-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 2rem;
    width: 100%;
    max-width: 80rem;
    margin: 0 auto;
    padding: 0 1.5rem 2.5rem;
    color: rgba(38, 44, 65, 0.52);
    font-size: 0.8rem;
    font-weight: 400;
    line-height: 1.5;
}

.reference-footer-wide > div {
    position: relative;
    top: -1.85rem;
}

.footer-legal-strip a {
    color: inherit;
    transition: color 180ms ease-in-out;
}

.footer-legal-strip a:hover,
.footer-legal-strip a:focus-visible {
    color: #262c41;
}

:root[data-theme="dark"] .footer-legal-strip {
    color: rgba(255, 255, 255, 0.72);
}

:root[data-theme="dark"] .footer-legal-strip a:hover,
:root[data-theme="dark"] .footer-legal-strip a:focus-visible {
    color: #ffffff;
}

@media (min-width: 640px) {
    .reference-wide {
        padding-top: 2.6667rem;
        margin-bottom: calc(-1.6889rem - 2.2734vw);
    }

    .reference-wide > div {
        top: calc(-0.8889rem - 2.2734vw);
    }
}

@media (min-width: 768px) {
    .reference-wide {
        padding-top: 4rem;
        margin-bottom: calc(-2.5333rem - 2.2734vw);
    }

    .reference-wide > div {
        top: calc(-1.3333rem - 2.2734vw);
    }
}

:root[data-theme="dark"] img[src*="cubes-hero"] { content: url("assets/cubes-hero-dark.png"); }
:root[data-theme="dark"] img[src*="databases-"] { content: url("assets/databases-dark.png"); }
:root[data-theme="dark"] img[src*="caches-"] { content: url("assets/caches-dark.png"); }
:root[data-theme="dark"] img[src*="cubes-wide"] { content: url("assets/cubes-wide-dark.png"); }
:root[data-theme="dark"] img[src*="cube-blue"] { content: url("assets/cube-blue-dark.png"); }
:root[data-theme="dark"] img[src*="cube-purple"] { content: url("assets/cube-purple-dark.png"); }

:root[data-theme="dark"] .opaque-purple-cube {
    background-image: url("assets/cube-purple-dark.png");
}

:root[data-theme="dark"] .price-card-monthly {
    background: linear-gradient(80deg, rgba(70, 78, 106, 0.82) 8%, rgba(79, 87, 116, 0.74) 72%, rgba(96, 102, 130, 0.58) 100%);
    border-color: rgba(185, 189, 208, 0.34);
    box-shadow: 0 1.5rem 4rem rgba(8, 12, 28, 0.2);
}

:root[data-theme="dark"] .price-card-monthly small {
    opacity: 0.72;
}

.opaque-purple-cube {
    background-image:
        url("assets/cube-purple-f6628dfa.png"),
        url("assets/cube-purple-f6628dfa.png"),
        url("assets/cube-purple-f6628dfa.png"),
        url("assets/cube-purple-f6628dfa.png"),
        url("assets/cube-purple-f6628dfa.png"),
        url("assets/cube-purple-f6628dfa.png"),
        url("assets/cube-purple-f6628dfa.png");
    background-position: center;
    background-repeat: no-repeat;
    background-size: 100% 100%;
}

/* Portrait phones get the vertical video at a true 9:16. The cut is at 639px
   and not at the 767px the rest of the mobile block uses, because 9:16 is a
   ratio and not a fixed height: at 767 it works out 695x1236, a block taller
   than the phone it is meant for. No phone is 640px wide in portrait. */
@media (max-width: 639px) {
    .product-mockup {
        aspect-ratio: 9 / 16;
    }

    /* Wistia sizes its button off the player's WIDTH, which is the narrow side
       of a vertical frame, so its own 19.5312% leaves a button that reads as an
       afterthought on a phone. 30% of the frame is the preview cue at arm's
       length, and it keeps the tap target over 44px tall down to a 320px
       phone (74x47). */
    .wistia-play-button {
        width: 26%;
    }
}

/* Desktop's blue frame around the video: 48px a side cut by a third, to 32px.
   Padding and the upward shift are one measurement - they set the frame on the
   sides and underneath - so both move together, the same as tablet's 24/24 and
   mobile's 18/18. */
@media (min-width: 1024px) {
    .reference-product > .max-w-7xl > .relative {
        padding-left: 2rem;
        padding-right: 2rem;
    }

    .product-mockup {
        transform: translateY(-2rem);
    }
}

@media (min-width: 768px) and (max-width: 1023px) {
    .reference-product > .max-w-7xl > .relative {
        padding-left: 1.5rem;
        padding-right: 1.5rem;
    }

    .product-mockup {
        transform: translateY(-1.5rem);
    }
}

@media (min-width: 1024px) {
    .reference-product {
        margin-top: -5.05rem;
    }

    body > div:first-of-type {
        padding-top: 3rem;
    }

    /* ⛔ THE HEADER SAT 24px HIGHER ON EVERY PAGE EXCEPT THE HOMEPAGE, AND THE
       CAUSE IS THE RULE DIRECTLY ABOVE. It is written as `body > div`, so it
       lands on the homepage — whose header is a plain `<div>` — and misses every
       other page, where the same header is a semantic `<header>`. Measured
       2026-09-04 at 1440px: `/` computed 48px, `/book/`, `/privacy/`, `/terms/`,
       `/accessibility/` and `/agreement-v1/` all computed 24px, straight off
       Tailwind's `pt-6`. Below 1024px every page already agreed at 24px, which is
       why it only ever showed on a desktop.

       ⚠ THE MARKUP IS NOT THE THING TO FIX. Turning the homepage's `<div>` into
       a `<header>` would take it OUT of the rule above and drop the homepage by
       24px instead — the same bug pointing the other way — and `<header>` is the
       right element for the inner pages regardless.

       So the intent is stated once, on the CLASS, and it no longer depends on
       which tag a page happens to use. `body > .site-header` scores (0,1,1),
       which beats `.pt-6` at (0,1,0) and loses to the rule above at (0,1,2) —
       and losing there is harmless, because both declare the same 3rem. A future
       page gets the right header whatever element it wraps it in. */
    body > .site-header {
        padding-top: 3rem;
    }

    /* ⛔ THIS IS THE 96px TRAP, AND IT HAS COST FOUR PAGES SO FAR. It is a
       HOMEPAGE rule, and it matches ANY plain div nested one deep inside the
       FIRST div of main -- on every page in the site, at every width above
       1024px. A new section put in that position silently gains six rems of top
       margin and renders with a hole in it, with nothing anywhere reporting a
       fault.

       ⚠ AN OVERRIDE MUST SCORE (0,1,3) OR IT DOES NOTHING AT ALL. This selector
       is three elements plus a pseudo-class. Measured, on this rule:

           .thing > div            (0,1,1)   computed 96px. No change on screen
           main > .thing > div     (0,1,2)   computed 96px. No change on screen
           main > div.thing > div  (0,1,3)   computed 0px. Ties, wins on order

       Both losing attempts look EXACTLY like the unfixed page, which is why two
       of them shipped before anybody noticed. Never judge this from a
       screenshot; read the computed value:

           getComputedStyle(document.querySelector('.thing > div')).marginTop

       ⚠ DO NOT NARROW OR DELETE THIS RULE TO FIX A NEW PAGE. It is load-bearing
       for the homepage's hero spacing at this breakpoint. Override it locally
       on the page that is affected. */
    main > div:first-of-type > div {
        margin-top: 6rem;
    }

    .max-w-7xl.px-6,
    body > div:first-of-type.max-w-7xl,
    footer .max-w-7xl {
        padding-left: 2.25rem;
        padding-right: 2.25rem;
    }

    /* The nudge belongs to whatever is POSITIONED, and since the glow layer
       arrived that is the wrapper rather than the image. Left on the image it
       is a `right` on a static box: silently inert, and the illustration sits
       half a rem out at this one breakpoint with nothing to show why. */
    main > div:first-of-type .cube-field.hero-illustration-desktop {
        right: -0.5rem;
    }

    .reference-features {
        padding-top: 6.9375rem;
        padding-bottom: 8.375rem;
    }

    .reference-features > h2,
    .reference-features > .section-subheadline {
        margin-left: auto;
        margin-right: 6rem;
        text-align: right;
        transform: translateX(3.75rem);
    }

    .reference-features > h2 {
        width: fit-content;
    }

    /* The break is forced after "see" rather than left to the container width.
       Matched left and right edges were tried here too and scrapped with the
       headline's. Below 1024 the spans stay inline and the sentence wraps. */
    .reference-features > .section-subheadline {
        max-width: 34rem;
    }

    .reference-features > .section-subheadline .sub-line {
        display: block;
    }

    .reference-features > .grid {
        margin-top: 5.4375rem;
    }

    .reference-features .feature-spotlight > * {
        transform: translateX(3.75rem);
    }

    .reference-features .feature-spotlight > .ai-recommendation-illustration {
        transform: translateX(3.75rem) scale(0.92);
    }

    .reference-product > .max-w-7xl > .grid {
        row-gap: 4rem;
    }

    .reference-wide {
        height: 39.618rem;
        margin-bottom: calc(-1.3333rem - 2.1423vw + clamp(-3.4744rem, calc(-7.4694rem + 6.2424vw), 0rem));
    }

    .reference-wide > div {
        width: 113.1%;
        top: calc(-1.3333rem - 2.1423vw);
        transform: translateX(-5.79%);
    }

    .reference-deploy {
        grid-template-columns: minmax(0, 1fr) 36.833rem;
        margin-top: 1.5rem;
        column-gap: 2.5rem;
    }

    .reference-deploy .code-demo {
        transform: translateX(-0.5rem);
    }

    .code-demo {
        padding-left: 0;
    }

    .pricing-reference {
        grid-template-areas: "cards copy";
        grid-template-columns: minmax(0, 55fr) minmax(0, 45fr);
        gap: 2rem;
        align-items: start;
    }

    .pricing-cards {
        width: min(60.5rem, calc(100vw - 6.3125rem));
        min-height: 37.25rem;
        margin-left: 4.0625rem;
    }

    .pricing-copy {
        padding-top: 2rem;
        /* No horizontal nudge any more. The 1.75rem pushed a left-aligned block
           to the right; with the copy right-aligned it pushes the text past the
           container's own right padding instead. */
        transform: translateY(-9.25rem);
    }

    .pricing-reference {
        padding-top: 16.75rem;
        padding-bottom: 0;
    }

    .price-card-yearly {
        width: 36.833rem;
        min-height: 28.667rem;
        padding-right: 2.5rem;
    }

    .price-card-yearly a {
        min-width: 12.667rem;
        padding-top: 1.2rem;
        padding-bottom: 1.2rem;
        transform: translateY(1rem);
    }

    .price-card-monthly {
        width: 34.333rem;
        min-height: 26.833rem;
    }

    .price-card-monthly > a {
        bottom: 2.5rem;
        right: calc(2.5rem - 1px);
        min-width: 12.667rem;
        padding-top: 1.2rem;
        padding-bottom: 1.2rem;
    }

    footer {
        margin-top: 7rem !important;
    }

    .reference-footer-wide {
        height: 30.856rem;
        padding-top: 3.125rem;
        padding-bottom: 0;
    }

    .reference-footer-wide > div {
        width: 100%;
        transform: translateX(-6.75%);
    }
}

@media (min-width: 1200px) {
    .reference-wide {
        margin-top: 48px;
        margin-bottom: calc(-5.7276rem + 28.338px);
    }

    .reference-wide > div {
        left: calc(100% - 100.7139rem);
        width: 105.625rem;
        margin-left: 0;
        margin-right: 0;
        top: -65.75px;
        transform: none;
    }

    .reference-footer-wide > div {
        left: -5.0625rem;
        width: 105.625rem;
        margin-left: 0;
        margin-right: 0;
        transform: none;
    }

    .pricing-reference {
        margin-top: 61px;
    }

    footer {
        margin-top: 168px !important;
    }

    .footer-legal-strip {
        padding-top: 33.512px;
        padding-bottom: 20.279px;
    }

}

@media (max-width: 767px) {
    .reference-features > h2,
    .feedback-copy .deploy-heading,
    .pricing-copy > h2 {
        font-size: 2.5rem;
        line-height: 1.1;
    }

    footer > nav {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        row-gap: 3rem;
        column-gap: 1rem !important;
    }

    footer > nav > div:first-child {
        grid-column: 1 / -1;
    }

    .footer-legal-strip {
        padding-bottom: 27px;
    }

    .reference-wide,
    .reference-footer-wide {
        height: auto;
        margin-bottom: 0;
        padding-top: 3rem;
        padding-bottom: 3rem;
    }

    .reference-wide > div,
    .reference-footer-wide > div {
        top: 0;
        width: 240%;
        transform: translateX(-29.1667%);
    }

    .reference-product > .max-w-7xl {
        padding-left: 1.125rem;
        padding-right: 1.125rem;
    }

    .reference-product > .max-w-7xl > .relative {
        padding-left: 1.125rem;
        padding-right: 1.125rem;
    }

    .reference-product > .max-w-7xl > .grid {
        margin-left: 0.375rem;
        margin-right: 0.375rem;
    }

    .team-stack {
        position: relative;
        display: block;
        min-height: clamp(26rem, 112vw, 35rem);
        margin-top: 1.5rem;
        padding: 0.25rem 0 1rem;
        overflow: visible;
        touch-action: pan-y;
        user-select: none;
        -webkit-user-select: none;
    }

    .team-card {
        position: absolute;
        top: 0.25rem;
        left: 50%;
        width: min(72vw, 22.6667rem);
        transform: translateX(-50%) translate(var(--team-x), var(--team-y)) rotate(var(--team-rotation));
        transition-duration: 620ms;
        transition-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
        will-change: transform;
        box-shadow: 0 1.5rem 3.5rem rgba(38, 44, 65, 0.04);
    }

    .team-stack.is-dragging .team-card {
        transition: none;
    }

    .team-card-dennis {
        --team-x: -1.75rem;
        --team-y: 0rem;
        --team-rotation: -4deg;
    }

    .team-card-yelena {
        --team-x: 0rem;
        --team-y: 0.5rem;
        --team-rotation: -1deg;
    }

    .team-card-dmitriy {
        --team-x: 1.75rem;
        --team-y: 1rem;
        --team-rotation: 4deg;
    }

    .team-stack.is-expanded .team-card-dennis {
        --team-x: -6.375rem;
        --team-y: 0rem;
        --team-rotation: -8deg;
    }

    .team-stack.is-expanded .team-card-yelena {
        --team-x: 0rem;
        --team-y: -0.375rem;
        --team-rotation: 0deg;
    }

    .team-stack.is-expanded .team-card-dmitriy {
        --team-x: 6.375rem;
        --team-y: 0rem;
        --team-rotation: 8deg;
    }

    .feature-spotlight {
        padding-left: 0;
        padding-right: 0;
    }

    .feature-spotlight > img {
        align-self: center;
        margin-top: 0;
        width: 151px;
    }

    .feature-spotlight > .ai-recommendation-illustration {
        width: 137px;
        transform: none;
    }

    .product-mockup {
        transform: translateY(-1.125rem);
    }

    .cube-field--frame-blue {
        top: 66.6667%;
        bottom: auto;
        transform: translate(-50%, -50%);
    }

    .code-demo pre {
        min-height: 13rem;
        padding: 1.2rem;
        font-size: 0.72rem;
    }

    .pricing-cards {
        display: grid;
        gap: 1.25rem;
        min-height: 0;
    }

    .price-card-yearly,
    .price-card-monthly {
        position: relative;
        inset: auto;
        width: 100%;
        min-height: 0;
    }

    .price-card-yearly {
        padding-top: 4rem;
    }

    .pricing-cube {
        top: -4.5rem;
        width: 7rem;
    }

    .price-card-topline {
        display: block;
    }

    .price-card-topline a {
        margin-top: 1.5rem;
    }

    .price-card-monthly > a {
        position: static;
        margin-top: 1.5rem;
        width: 100%;
    }
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    .team-card {
        transition-duration: 320ms;
        transition-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
    }
}


/* Hero headline: the three lines it was written as, and nothing more.

   Justifying them out to a shared left and right edge was built and then
   scrapped - it was a real effect but not one worth the word gaps it opened in
   the two shorter lines. The spans stay blocks purely to hold the break; the
   right edge is ragged, as normal type is.

   This lives here because the repo ships a PREBUILT marketing.css with no
   Tailwind build step, so a utility class that is not already compiled is inert. */
.hero-headline .hero-line {
    display: block;
}


@media (max-width: 639px) {
    /* THE HEADLINE STAYS ON THREE LINES AT EVERY PHONE WIDTH. That is the rule;
       the sizes below are just what it works out to. Each step is the largest
       whole pixel size at which all three lines still fit, measured in a real
       browser at the narrowest width in its range - the narrow end binds,
       because wider is always more room.

           320+   38px      375+   46px      414+   52px
           360+   44px      390+   48px

       These are one to two pixels smaller than the set they replace, because
       the headline changed: the longest line went from "Your website" to
       "Every website". A copy change to this headline INVALIDATES EVERY NUMBER
       HERE and they have to be measured again, not adjusted by eye - four
       lines on a 360px phone is what the old set produced the moment the words
       moved. */
    .hero-headline { font-size: 38px; }

    .hero-lede > p {
        font-size: 21px;
    }


    /* Two pixels up from 16, on both this button and the pricing card's, so
       the two calls to action are the same size. */
    .hero-cta,
    .price-card a {
        font-size: 18px;
    }
}

@media (min-width: 360px) and (max-width: 639px) { .hero-headline { font-size: 44px; } }
@media (min-width: 375px) and (max-width: 639px) { .hero-headline { font-size: 46px; } }
@media (min-width: 390px) and (max-width: 639px) { .hero-headline { font-size: 48px; } }
@media (min-width: 414px) and (max-width: 639px) { .hero-headline { font-size: 52px; } }

/* Phone type scale, second pass.

   Every size here is a whole pixel. Anything derived from rem landed on
   fractions - 20.8px, 21.8px, 12.8px - and those are not sizes anyone chose. */
@media (max-width: 639px) {
    .reference-features > .section-subheadline,
    .feedback-copy .deploy-heading + p,
    .pricing-copy > p {
        font-size: 22px;
    }
    /* 2.7rem on all three section headings. They were 2.5rem, so this raises
       the other two as well and keeps the trio level. Left in rem because
       that is the unit they were already written in; the whole-pixel rule was
       about the sizes actually set in pixels. */
    .reference-features > h2,
    .feedback-copy .deploy-heading,
    .pricing-copy > h2 {
        font-size: 2.7rem;
    }


    /* The plan sublines stay subordinate to the plan name: one pixel up from
       16, not up to the section-subheadline size. */
    .price-card p {
        font-size: 17px;
    }

    .price-card small {
        font-size: 13px;
    }

    .price-card li {
        font-size: 17px;
    }

    /* The six feature bullets go up two pixels, and the two spotlight cards
       above them match, so all eight bodies read at one size. */
    .reference-product .grid > div p,
    .feature-spotlight p {
        font-size: 18px;
    }
}


/* Phone hero: one screenful.

   The headline, subheadline and buttons sit centred in what is left of the
   screen under the header, and the illustration begins just below the fold so
   only the tip of the top cube shows - enough to say there is more below
   without eating the screen.

   The trio needs its own box (.hero-lede) to do this. Auto margins inside
   .hero-copy were tried and cannot work: the illustration lives in the same
   flex column and is 308px tall, so there is no free space left for an auto
   margin to distribute, and the image simply flows straight after the buttons.
   Giving the trio a min-height of one screen minus the peek puts the image's
   top edge exactly --hero-peek above the fold, and keeps it in flow so the
   rest of it appears on scroll.

   --hero-offset is the space above .hero-copy: header plus the section's top
   margin, measured at 82px on a 390x844 screen. */
@media (max-width: 639px) {
    .hero-copy {
        container-type: inline-size;
        --hero-offset: 82px;
        /* Half the top cube's face. The illustration renders 138% of the copy
           column and is 1618x1106, so its height is about 0.94 of that column's
           width; the top face is about a quarter of that height, measured off
           the rendered image. 0.28 of the column width shows the top face and
           the start of the two front faces below it, and holds that same slice
           on any phone. */
        --hero-peek: calc(100cqw * 0.28);
    }

    .hero-lede {
        display: flex;
        flex-direction: column;
        justify-content: center;
        min-height: calc(100svh - var(--hero-offset) - var(--hero-peek));
    }

    /* Twice the 20px it had, as asked. */
    .hero-copy .hero-actions {
        margin-top: 2.5rem;
    }

    .hero-copy .hero-illustration-mobile {
        margin-top: 0;
    }
}

/* No sideways scroll, at any width.

   Several elements bleed past the viewport on purpose - the hero cubes, the
   -translate-x decorations, the w-[120%] product mockup - and each one adds
   real scrollable width. clip removes that without making the body a scroll
   container, so anything sticky keeps working; hidden is the fallback for
   engines without clip. */
html,
body {
    overflow-x: hidden;
    overflow-x: clip;
}

/* max-width on the root as well: with clip alone the viewport could still
   scroll a few pixels when a revealed card landed past the edge. */
html {
    max-width: 100%;
}

/* The plan sublines stay on ONE line at every width. "30-day money back
   guarantee." needs 248px at 17px, and the card only offers 202px on a 320px
   phone, so the size steps down on the narrowest screens rather than wrapping.
   14.9 is that string's width per font pixel plus a safety margin. */
.price-card {
    container-type: inline-size;
}

.price-card > div > p,
.price-card > .price-card-topline p {
    white-space: nowrap;
}

@media (max-width: 639px) {
    .price-card > div > p,
    .price-card > .price-card-topline p {
        font-size: min(17px, calc(100cqw / 14.9));
    }

    /* +1px */
    .price-card small {
        font-size: 14px;
    }

    /* Footer: the two column headings up two, their links up one. */
    footer h4 {
        font-size: 18px;
    }

    footer li a {
        font-size: 17px;
    }

    /* "Our guarantee" and "One price" came down a pixel, from 2.7rem.
       "See results within 30 days" was put back to where it was and keeps
       2.7rem, so that one section now reads a pixel larger than its peers. */
    .feedback-copy .deploy-heading,
    .pricing-copy > h2 {
        font-size: 42px;
    }

    .reference-features > h2 {
        font-size: 2.7rem;
    }

    /* ...and their subheadlines down one, from 22px - except this section's,
       which was put back with its heading. */
    .feedback-copy .deploy-heading + p,
    .pricing-copy > p {
        font-size: 21px;
    }

    .reference-features > .section-subheadline {
        font-size: 22px;
    }

    /* The guarantee section only, by instruction: its heading comes down two
       pixels and its paragraph comes down two. The pricing section keeps the
       sizes above, which is why these are their own rules and not an edit to
       the shared ones. */
    /* The same size as every other section heading, by instruction. Same
       clamp as `.bd-h` in the homepage's own sheet. */
    .feedback-copy .deploy-heading {
        font-size: 35px;
    }

    .feedback-copy .deploy-heading + p {
        font-size: 19px;
    }

    /* The pricing subheadline had the cards almost touching it. */
    .pricing-copy > p {
        margin-bottom: 2.5rem;
    }

    /* The six bullets, titles and bodies, up one - and the two spotlight
       cards keep matching the bullet bodies. */
    .reference-product .grid > div h2 {
        font-size: 25px;
    }

    .reference-product .grid > div p,
    .feature-spotlight p {
        font-size: 19px;
    }
}

@media (max-width: 639px) {
    /* The three legal links: one pixel up from 12.8, and centred as a group
       rather than left-aligned across two ragged rows. */
    .footer-legal-strip {
        justify-content: center;
        text-align: center;
        font-size: 14px;
    }

    /* Header Login: one pixel up, and given the outline the "Book a call"
       button has on wider screens - on a phone that button is hidden, so
       Login was the only header action and read as plain text. */
    /* rgb(48 165 230) is the brand cyan - the exact value .border-cyan-600 and
       .text-cyan-600 carry in marketing.css, which is what the desktop
       "Book a call" outline uses. It was written as #0891b2 here, which is
       Tailwind's stock cyan-600: a dark teal that matches nothing on the site.
       Never hand-pick a colour for this site; take the one the design system
       already defines. */
    /* ⚠ MATCH THE END OF THE HREF, NOT THE WHOLE OF IT. This was
       `a[href="login/"]`, the RELATIVE form, which only the homepage uses — every
       other page links `/login/`. So on a phone the homepage's Login was an
       outline button and the same link on /book, /privacy, /terms,
       /accessibility and /agreement-v1 was plain text, on a width where "Book a
       call" is hidden and Login is the only header action there is. */
    .site-header a[href$="login/"] {
        font-size: 17px;
        border: 1px solid rgb(48 165 230);
        border-radius: 0.375rem;
        padding: 0.5rem 1.25rem;
        color: rgb(48 165 230);
    }
}

/* Six bullets and the two spotlight cards: even lines, no stub last line.

   The ask was right padding to tighten the rag. Measured at 12/20/28/36/44px
   it does the opposite - a narrower column turns four-line bodies into
   five-line ones with SHORTER last lines. text-wrap: balance is the lever
   that was actually wanted: the browser distributes the words across the
   lines it needs, so the last line comes out close to full instead of a stub.

   Measured on the six bullets, last line as a share of the column:

       before   63  90  77  73  19  93
       after    81  90  86  81  75  93

   "Ongoing SEO Optimization" still needs five lines - it is six characters
   too long for four - but its last line is no longer a fragment. */
@media (max-width: 639px) {
    .reference-product .grid > div p,
    .feature-spotlight p {
        text-wrap: balance;
    }
}

/* ===========================================================================
   INTERACTION LAYER
   ===========================================================================

   Everything below is hover, press, focus and ambient motion. None of it moves
   a box: every animated property is `opacity` or `transform`, or a paint on a
   pseudo-element that did not exist before. That is deliberate and it is
   checkable — the greenscreen twin of this site composites a recorded pointer
   over a client's real page, so a control that shifts two pixels here is a
   cursor that misses it in the finished video.

   The motion vocabulary is the one already shipping on XNP's other live build,
   recycling-solution.com: a 240ms control curve, a 140ms release, and an
   expressive out-curve for anything larger than a button. Two XNP sites that
   move differently read as two different companies.

   ⚠ EVERY RULE HERE IS SWITCHED OFF BY `prefers-reduced-motion`, at the bottom
   of this file. The reduced path is not "the same page with the animation
   removed" — the ambient glows and sheens are removed outright, and hover
   still changes colour so a pointer user is never left without feedback.
   =========================================================================== */

:root {
    /* Control motion: buttons, links, small state changes. */
    --xnp-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
    --xnp-dur: 240ms;
    --xnp-dur-press: 140ms;

    /* Expressive motion: media, anything the size of a photograph. */
    --xnp-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    --xnp-dur-media: 600ms;
}

/* ------------------------------------------------------------------ buttons
   The lift is 1px and the shadow is tinted with the brand blue rather than
   neutral black. A grey shadow under a blue button reads as dirt on the page;
   the same shadow carrying the button's own hue reads as light.

   `transform` is what lifts, so `translateY` must never be combined here with
   a layout offset — none of these controls has one, which is why the lift is
   safe on the element itself rather than on a wrapper. */
/* ⚠ THE CONTROLS THAT ARE NOT BLUE-GRADIENT BUTTONS NEED THEIR OWN LIST, AND
   "Show me how" WAS LEFT OUT OF IT. It carries the class `hero-cta` — which the
   blue hero button also carries — so when the blue buttons' transition moved
   into their own declaration, the secondary button silently lost every
   transition it had, and its hover started switching on instead of easing. The
   selector to trust for that button is `hero-cta-secondary`, never `hero-cta`,
   which names both of them. */
.site-header a[href="/book"],
.hero-cta-secondary {
    transition:
        transform 200ms var(--xnp-ease),
        box-shadow 420ms cubic-bezier(0.45, 0, 0.25, 1),
        background-color 420ms cubic-bezier(0.45, 0, 0.25, 1),
        border-color 420ms cubic-bezier(0.45, 0, 0.25, 1),
        color 420ms cubic-bezier(0.45, 0, 0.25, 1);
}

.hero-cta:hover,
.site-header a[href="/book"]:hover,
.price-card-monthly > a:hover {
    transform: translateY(-1px);
}

/* The press has to travel further down than the hover travelled up, or the
   control feels sticky rather than clicked. */
.hero-cta:active,
.site-header a[href="/book"]:active,
.price-card-monthly > a:active {
    transform: translateY(1px);
}

/* ⛔ THE LIFT MOVES THE CONTROL OUT FROM UNDER THE POINTER, AND THAT IS THE
   "DISAPPEARING HAND CURSOR" BUG. Reported across the whole site and measured
   on the header's own button, 2026-09-04:

       bottom edge at rest      97.3px    point 1px above it -> A, cursor pointer
       bottom edge on hover     96.3px    the SAME point     -> DIV, cursor auto

   Hover anywhere in the bottom pixel and the sequence is: the hand appears, the
   control lifts 1px, the pointer is now BELOW it, and the cursor snaps back to
   an arrow a moment later. It reads as the hand randomly vanishing, which is
   exactly how it was reported. `:active` has the mirror version at the top edge,
   because the press travels 1px the other way.

   ⚠ IT IS NOT A CURSOR RULE AND CANNOT BE FIXED WITH ONE. Every one of these
   controls is an `<a href>` and already computes `cursor: pointer`; the pointer
   is simply not over it any more. Setting `cursor` harder changes nothing.

   The fix is to stop the hit area moving. This skirt extends it 2px above and
   below — further than either travel — so the pointer stays inside the control
   through the whole transition. It is invisible, it adds no layout, and it
   belongs to the anchor, so a click on it still activates the link.

   ⚠ The 2px is vertical only. These controls sit in rows with real horizontal
   gaps, and widening the skirt sideways would let one steal a neighbour's edge. */
.hero-cta,
.hero-cta-secondary,
.site-header a[href="/book"],
.price-card-monthly > a {
    position: relative;
}

.hero-cta::after,
.hero-cta-secondary::after,
.site-header a[href="/book"]::after,
.price-card-monthly > a::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -2px;
    bottom: -2px;
}

/* Blue fills: the gradient stops already ease on hover (see the @property
   block above). This adds the light the lift implies. */
a.bg-cyan-600:hover,
.price-card-monthly > a:hover {
    box-shadow: 0 10px 24px -10px rgba(48, 165, 230, 0.65);
}

/* The two outline controls — "Show me how" and the header's "Book a call" —
   had no hover at all. They fill toward the brand instead of merely darkening,
   so the secondary action still reads as secondary while it is under the
   pointer. */
.xnp-variation-b .hero-cta-secondary:hover {
    background-color: #ecebfb;
    border-color: #c7c3ee;
}

/* ⛔ THE HEADER BUTTON LIFTS. IT DOES NOT FILL, AND IT DOES NOT GLOW.
   Dmitriy's call, 2026-09-04: "it does not color the inside of the container.
   It should simply lift up and that's it."

   The glow went first, for the reason that still holds: this is an OUTLINE
   button on the page's own ground rather than on a card, and a blue-tinted
   shadow reads as light coming off a solid blue shape. Under a hollow one there
   is no shape for the light to come from, so it reads as a halo around the
   outline. The fill went second, and the same logic finishes the thought — a
   hollow control that fills on hover stops being hollow, which is the one thing
   distinguishing it from the filled buttons beside it.

   ⚠ So there is deliberately NO `:hover` declaration for this button here. The
   1px lift is declared with the other lifted controls further up and is the
   whole hover. Do not add a background, a border colour or a shadow back. */

/* ------------------------------------------------------- standalone links
   An underline that GROWS FROM THE LEFT rather than switching on. Same cost as
   `text-decoration: underline` on hover, and it is the difference between a
   state and a design.

   It is a background-image, not `text-decoration`, because only a background
   has a size to animate. `background-position: left 100%` pins it to the
   bottom of the line box so it does not drift with the font.

   ⚠ The three links in the very bottom strip — Agreement, Accessibility, Your
   Privacy Choices — are deliberately NOT in this set. They are the small print
   under the page, and giving them the same emphasis as Login would flatten the
   difference. */
.xnp-underline {
    background-image: linear-gradient(currentColor, currentColor);
    background-size: 0% 1.5px;
    background-repeat: no-repeat;
    background-position: left 100%;
    transition: background-size 220ms var(--xnp-ease-out);
}

.xnp-underline:hover,
.xnp-underline:focus-visible {
    background-size: 100% 1.5px;
}

/* ------------------------------------------------------------------- video
   THE PLAY BUTTON IS A CIRCLE.

   ⚠ It was a 1.5625:1 rectangle measured off a real Wistia player, and the
   comment above still explains that width formula because the WIDTH is what
   was measured and is still what is used. Only the aspect ratio and the corner
   changed, on Dmitriy's call. A square button inside a rounded frame was the
   one shape on the page that belonged to somebody else's product.

   The whole frame is the hover target, not just the button: a visitor aiming
   at a video aims at the picture. */
.wistia-play-button {
    aspect-ratio: 1 / 1;
    border-radius: 50%;
    box-shadow: 0 18px 40px -18px rgba(20, 26, 46, 0.55);
    transition:
        transform var(--xnp-dur) var(--xnp-ease),
        box-shadow var(--xnp-dur) var(--xnp-ease);
}

.wistia-play-button-tint {
    transition: background-color var(--xnp-dur) var(--xnp-ease);
}

/* The image scales, never the frame. `.product-mockup` carries a `translateY`
   at three breakpoints, so a transform on the frame itself would fight a
   layout offset and move the video. The frame already clips, so scaling the
   picture inside it is both safer and the effect that was wanted. */
.product-mockup img {
    transition: transform var(--xnp-dur-media) var(--xnp-ease-out);
    will-change: transform;
}

/* Half of what this was. A 4.5% push moved the frame's content enough to read
   as the picture jumping rather than as it leaning toward the pointer. */
.product-mockup:hover img,
.product-mockup:focus-within img {
    transform: scale(1.022);
}

.product-mockup:hover .wistia-play-button,
.product-mockup:focus-within .wistia-play-button {
    transform: translate(-50%, -50%) scale(1.07);
    box-shadow: 0 22px 52px -18px rgba(20, 26, 46, 0.62);
}

.product-mockup:hover .wistia-play-button-tint,
.product-mockup:focus-within .wistia-play-button-tint {
    background-color: rgba(48, 165, 230, 0.86);
}

.product-mockup:active .wistia-play-button {
    transform: translate(-50%, -50%) scale(1.02);
}

/* ------------------------------------------------------ the living blue
   THE BLUE ITSELF TRAVELS. It is not a highlight laid over the fill; it is the
   fill, built as a long multi-stop gradient three times the size of the shape
   and slid back and forth across it. Light and dark move through the surface,
   which is what a big painted plane does under a moving light.

   ⚠ THE FIRST ATTEMPT WAS A WHITE SHEEN AT 12% AND IT WAS INVISIBLE. Two
   reasons, and both are worth carrying because both are invisible from the
   code:

     1. THE VIDEO FRAME IS NOT A BLUE RECTANGLE, IT IS A 32px BORDER. The
        mockup covers 1144 of its 1208 pixels, so any effect painted on the
        frame shows only in a strip narrower than a thumb. An amplitude chosen
        for a large plane disappears there.
     2. A 12-of-255 change spread over 14 seconds is below what a person
        notices at all. Measured on four screenshots 2.2s apart: the largest
        channel delta anywhere on the shape was 12.

   So the amplitude is now the fill's own range, from #2b93cf to #7cc9f2, and
   the cycle is 12 seconds. That is visible on a 32px strip and still slow
   enough that it never pulls the eye off the words.

   ⚠ THIS OVERRIDES `.bg-cyan-600` FOR THESE TWO SHAPES ONLY. Every blue BUTTON
   keeps its own gradient and its own hover — a button that breathed would be a
   control that looks like it is loading. */
/* ⚠ THE LIGHT IS AN EXPLICIT BLOB, NOT A SLID GRADIENT, AND THAT IS WHAT LETS
   THE GLARE FIND IT. Sliding an oversized linear gradient does move light
   across a shape, but nothing in the CSS then knows WHERE the bright part
   currently is — so an outer glow built from the same gradient can only halo
   the entire container, which is a rectangle of light around the whole shape
   and not what was asked for.

   Two layers instead. A fixed multi-stop linear gradient underneath, which is
   what guarantees the shape is never flat paint at any moment. A soft radial
   blob on top at `var(--lx) var(--ly)`, which is the moving light. Because its
   position is two numbers rather than an emergent property of a slide, the
   glare can be the SAME blob at the SAME coordinates — so the glow appears
   only where the light actually is, and only when the light is near enough to
   an edge to spill past it.

   ⚠ TWO CLOCKS, ONE ON EACH AXIS. A single animation along one line is a
   pendulum: watch it twice and you know where it goes. The two axes run on
   periods that do not divide into each other, so the blob traces a path that
   only returns to its start after 221 seconds on the video frame and 209 on
   the card. */
@property --lx {
    syntax: "<percentage>";
    inherits: true;
    initial-value: 50%;
}

@property --ly {
    syntax: "<percentage>";
    inherits: true;
    initial-value: 50%;
}

#video {
    scroll-margin-top: 8rem;
}

.reference-product > .max-w-7xl > .relative,
.price-card-yearly {
    animation:
        xnp-lx var(--flow-x-dur) linear var(--flow-x-delay, 0s) infinite,
        xnp-ly var(--flow-y-dur) linear var(--flow-y-delay, 0s) infinite;
}

.reference-product > .max-w-7xl > .relative {
    --flow-x-dur: 13s;
    --flow-y-dur: 17s;
}

.price-card-yearly {
    --flow-x-dur: 11s;
    --flow-y-dur: 19s;
    --flow-x-delay: -4s;
    --flow-y-delay: -7s;
}

@keyframes xnp-lx {
    0% { --lx: 6.000%; }
    10% { --lx: 14.403%; }
    20% { --lx: 36.403%; }
    30% { --lx: 63.597%; }
    40% { --lx: 85.597%; }
    50% { --lx: 94.000%; }
    60% { --lx: 85.597%; }
    70% { --lx: 63.597%; }
    80% { --lx: 36.403%; }
    90% { --lx: 14.403%; }
    100% { --lx: 6.000%; }
}

@keyframes xnp-ly {
    0% { --ly: 10.000%; }
    10% { --ly: 17.639%; }
    20% { --ly: 37.639%; }
    30% { --ly: 62.361%; }
    40% { --ly: 82.361%; }
    50% { --ly: 90.000%; }
    60% { --ly: 82.361%; }
    70% { --ly: 62.361%; }
    80% { --ly: 37.639%; }
    90% { --ly: 17.639%; }
    100% { --ly: 10.000%; }
}

}

/* ⚠ EVERY STOP IS A DIFFERENT COLOUR AND NO TWO ADJACENT STOPS REPEAT A VALUE.
   That is what keeps the base ramp from ever being a flat plane. A gradient
   written with a repeated value — `#53b4ea 34%, #53b4ea 46%` — looks identical
   in the code and produces a stretch of solid paint.

   The video frame carries no text, so its light blob can go as bright as the
   effect wants. */
.reference-product > .max-w-7xl > .relative {
    background-image:
        radial-gradient(42% 52% at var(--lx) var(--ly),
            rgba(255, 255, 255, 0.36) 0%,
            rgba(255, 255, 255, 0.16) 42%,
            rgba(255, 255, 255, 0) 72%),
        linear-gradient(
            118deg,
            #52afe2 0%,
            #4eafe8 15%,
            #58b5e8 29%,
            #4eb0e6 41%,
            #5ab8ea 52%,
            #4eafe8 64%,
            #51b2e8 77%,
            #53b0e3 89%,
            #52afe2 100%
        );
}


/* ⚠ THE YEARLY CARD'S LIGHT BLOB IS WEAKER THAN THE FRAME'S AND THAT IS NOT A
   TASTE DECISION. White text sits on this card. White on #53b4ea is already
   only 2.3:1 — the colour the card wears today, and already under the 4.5:1
   the site's own accessibility statement claims. A blob as bright as the
   frame's would push the lit area past that and make the worst case worse. At
   0.24 the lit peak stays at the colour the card already had, and the base
   ramp is darker than today's, so most of the cycle reads better than the
   static card did.

   The pre-existing 2.3:1 is a separate problem and it belongs to whoever
   changes the brand blue, not to this file. */
.price-card-yearly {
    background-image:
        radial-gradient(46% 56% at var(--lx) var(--ly),
            rgba(255, 255, 255, 0.17) 0%,
            rgba(255, 255, 255, 0.07) 44%,
            rgba(255, 255, 255, 0) 74%),
        linear-gradient(
            118deg,
            #53aede 0%,
            #4cace5 14%,
            #4eb0e7 27%,
            #4eaee7 39%,
            #4fb1e8 51%,
            #4dabe2 63%,
            #51aee5 76%,
            #51addf 88%,
            #53aede 100%
        );
}


/* ------------------------------------------------------------ cube glow
   A soft light under each cube, breathing on its own clock.

   ⚠ IT SITS IN FRONT OF THE ARTWORK AND BLENDS AS LIGHT, WHICH IS THE WHOLE
   REASON IT IS VISIBLE. Behind the image was tried first and it fails for a
   reason the code cannot show you: the cube is opaque, so a light source
   underneath is hidden by the very shape it is meant to light, and all that
   escapes is a faint ring outside the silhouette. Measured, that ring moved
   the pixels by 12 of 255 at its brightest.

   `mix-blend-mode: screen` is what puts the light ON the square. Screen only
   ever brightens, so on the cyan face it lifts toward white and reads as a
   glow, and on the near-white page around it there is nothing left to
   brighten, so it stays invisible instead of smearing a blue smudge across the
   background. That is one property doing what a second masked layer would
   otherwise be needed for.

   ⚠ `ISOMETRIC-SYSTEM.md` FORBIDS GLOW, BLUR AND DROP SHADOW, AND THAT RULE
   STILL HOLDS. It governs the ARTWORK — what is baked into the PNG — and
   nothing here is baked into anything. The illustration is untouched; this is
   a light in the room it sits in.

   ⚠ THE COORDINATES ARE MEASURED, NOT EYEBALLED. Each `--x`/`--y` is the
   centroid of one shape in the PNG, found by connected-component analysis of
   the flat top-face colour, expressed as a percentage of the image box. They
   therefore survive every viewport for free — the wrapper is the image's own
   box, so the glows scale with it.

   ⚠ NO TWO DURATIONS SHARE A FACTOR. Six glows on 6.8/8.3/9.7/7.4/11.2/9.1
   seconds with unequal negative delays never line up again inside a session,
   which is what makes it read as random rather than as a loop. Give two of
   them the same period and the eye finds the pattern within seconds. */
/* ⚠ ZERO SPECIFICITY, ON PURPOSE, AND THE PAGE BREAKS WITHOUT IT.
   The hero wrapper carries Tailwind's `md:absolute`. A plain `.cube-field`
   selector ties that on specificity and wins on source order, because
   custom.css loads after marketing.css — so the hero illustration would fall
   out of its absolute slot on desktop and push the whole hero down. `:where()`
   scores zero, so any real position class beats it and the relative only
   applies where nothing else has spoken.

   `display: block` is here because the wrapper is a SPAN. An inline box
   ignores `w-[508px]` outright, which is a hero-sized image collapsing to
   nothing with no error anywhere. */
:where(.cube-field) {
    position: relative;
    display: block;
}

.cube-field > img {
    position: relative;
    z-index: 1;
}

.cube-glow {
    position: absolute;
    left: var(--x);
    top: var(--y);
    width: var(--size, 26%);
    aspect-ratio: 1 / 1;
    transform: translate(-50%, -50%) scale(var(--glow-k));
    border-radius: 50%;
    background: radial-gradient(
        circle,
        rgba(180, 232, 255, 0.95) 0%,
        rgba(120, 205, 255, 0.55) 38%,
        rgba(80, 190, 255, 0) 68%
    );
    filter: blur(10px);
    mix-blend-mode: screen;
    pointer-events: none;
    z-index: 2;
    /* ⚠ TWO CLOCKS PER GLOW. One animation is one loop, and six loops started
       at six offsets still line up whenever two of the offsets happen to
       coincide — which is exactly what a fresh page load did: two of the three
       cyan cubes sat at the same point in their cycle at t=0 and climbed
       together, so the first thing anybody saw was a synchronised flash.
       Strength and size now run on SEPARATE periods that do not divide into
       each other, so no glow has a single loop to fall into and no two of them
       can hold step. */
    opacity: var(--glow-a);
    animation:
        xnp-glow-a var(--a-dur, 3.4s) linear var(--a-delay, 0s) infinite,
        xnp-glow-k var(--k-dur, 4.7s) linear var(--k-delay, 0s) infinite;
}

/* ⚠ NO `will-change` HERE, DELIBERATELY. There are 23 of these on the page and
   each one carries a blur, so promoting every one to its own compositor layer
   costs more memory than the animation saves. A transform-and-opacity
   animation is composited by the browser on its own; `will-change` is for the
   ONE element that needs a hint, which on this page is the video image. */

/* The floor drops and the translucent core are lavender, not cyan. A cyan glow
   under a lavender shape reads as a colour error rather than as light. */
/* The floor drops and the translucent core are lavender. Screen blend needs a
   colour the shape can actually be lifted toward, and a pale lavender lifts a
   lavender face where a cyan one would just turn it blue. */
.cube-glow--lavender {
    background: radial-gradient(
        circle,
        rgba(226, 218, 255, 0.95) 0%,
        rgba(196, 184, 250, 0.55) 38%,
        rgba(176, 162, 245, 0) 68%
    );
}

/* ⚠ BACK TO BACK: A SINE, WRITTEN OUT AS STOPS, DRIVEN LINEAR. The obvious
   spelling of this animation is three keyframes — dim, bright, dim — on an
   eased curve, and it is wrong in a way no screenshot shows. An ease is slow
   at both ends, so the glow DWELLS dark, swells, DWELLS bright, and recedes.
   What you see is a pulse, then a wait, then a pulse. Two of the three
   keyframes were rest.

   Eleven stops tracing a cosine, played at a constant rate, have no ends to be
   slow at. The light is always travelling, so each beat starts the instant the
   last one finishes. The turnarounds are still smooth because the curve is
   smooth, not because the timing function bends them.

   ⚠ THE TROUGH IS 0.18, NOT ZERO. At zero the glow leaves the cube completely
   between beats, and a light that goes out and comes back reads as blinking
   rather than as breathing. It stays lit and swells.

   ⚠ THE PERIODS ARE SECONDS, NOT TENS OF SECONDS. An earlier pass ran these at
   7 to 12 seconds: four seconds of fade-in, which is longer than anybody looks
   at one part of a page, so the set read as painted on. Two to four seconds is
   a beat. No two share a period and none is a simple multiple of another, so
   the group never lines up into one clock — and the phases are spread across
   each field in `index.html`, so a beat is always landing somewhere. */
@property --glow-a {
    syntax: "<number>";
    inherits: false;
    initial-value: 0.4;
}

@property --glow-k {
    syntax: "<number>";
    inherits: false;
    initial-value: 1;
}

@keyframes xnp-glow-a {
    0% { --glow-a: 0.140; }
    10% { --glow-a: 0.217; }
    20% { --glow-a: 0.420; }
    30% { --glow-a: 0.670; }
    40% { --glow-a: 0.873; }
    50% { --glow-a: 0.950; }
    60% { --glow-a: 0.873; }
    70% { --glow-a: 0.670; }
    80% { --glow-a: 0.420; }
    90% { --glow-a: 0.217; }
    100% { --glow-a: 0.140; }
}

@keyframes xnp-glow-k {
    0% { --glow-k: 0.760; }
    10% { --glow-k: 0.800; }
    20% { --glow-k: 0.905; }
    30% { --glow-k: 1.035; }
    40% { --glow-k: 1.140; }
    50% { --glow-k: 1.180; }
    60% { --glow-k: 1.140; }
    70% { --glow-k: 1.035; }
    80% { --glow-k: 0.905; }
    90% { --glow-k: 0.800; }
    100% { --glow-k: 0.760; }
}

/* ⚠ A BEAT WITH A REST, AND NO TWO EVER AT ONCE. This is the hero's alone:
   the two wide bands stay continuous, because they are wallpaper and a gap
   there reads as the effect having stopped.

   ⚠ THE PULSE FILLS ONLY THE FIRST 15% OF THE PERIOD. That is what leaves
   room for six of them to take turns inside one shared 14s clock without ever
   touching. Widen this window and beats start overlapping; the guarantee is
   arithmetic, not taste. The pulse's own speed is unchanged — 15% of 14s is
   the same 2.1s it always was.

   ⚠ THE REST VALUE IS 0.02, NOT ZERO AND NOT 0.05. It has to be low enough
   that a resting glow is genuinely invisible, or five faint cubes plus one
   bright one still reads as several lit at once. */
@keyframes xnp-beat-a {
    0% { --glow-a: 0.020; }
    1.5% { --glow-a: 0.109; }
    3% { --glow-a: 0.341; }
    4.5% { --glow-a: 0.629; }
    6% { --glow-a: 0.861; }
    7.5% { --glow-a: 0.950; }
    9% { --glow-a: 0.861; }
    10.5% { --glow-a: 0.629; }
    12% { --glow-a: 0.341; }
    13.5% { --glow-a: 0.109; }
    15%, 100% { --glow-a: 0.020; }
}

/* ⚠ THE HERO'S DELAYS ARE POSITIVE, WHICH IS WHAT KEEPS THE PAGE STILL FOR THE
   FIRST THREE SECONDS. A negative delay starts an animation part-way through,
   so the old set had a cube already mid-pulse on the very first painted frame —
   the page arrived with the effect half-run, which reads as having missed the
   start of something. Positive delays mean nothing has begun yet: the first
   beat lands at 3s, and the six of them then take their turns 4.667s apart.

   ⚠ AND THE RESTING VALUE HAS TO BE DECLARED HERE, NOT LEFT TO THE PROPERTY'S
   INITIAL VALUE. Before a delayed animation starts, the element shows its own
   computed value, and `--glow-a` is registered with an initial-value of 0.4 —
   so without this line all six glows would sit at 40% for three seconds and
   then start pulsing, which is the opposite of a still page.

   ⚠ THE PERIOD IS 28s AND THE BEAT WINDOW IS STILL 15%, WHICH IS THE ONLY WAY
   TO HALVE THE SPEED WITHOUT BREAKING THE SEQUENCE. The pulse is 15% of the
   period, so doubling the period doubles the pulse to 4.2s; the six slots stay
   six, one beat each, still never overlapping. Widening the window instead
   would have made a pulse longer than its own slot and put two on screen at
   once. */
.cube-field--beat .cube-glow {
    --glow-a: 0.02;
    animation-name: xnp-beat-a, xnp-glow-k;
}
/* The two wide bands are 5120x1413 — three and a half times wider than they
   are tall. A glow sized off the WIDTH would be a pinhead, so those size off
   the band's height instead and stay circular. */
.cube-field--wide .cube-glow {
    width: auto;
    height: var(--size, 62%);
    filter: blur(18px);
}

/* ------------------------------------------------- the standalone cubes
   The two cubes overhanging the video frame and the one on the Yearly card now
   use the SAME wrapper-and-screen-blend glow as the hero, centred in their own
   box.

   ⚠ `drop-shadow` WAS TRIED HERE AND IT IS THE WRONG TOOL ON A LIGHT PAGE.
   It follows the PNG's own alpha, which is genuinely the nicer shape, but a
   shadow paints BEHIND the element — and behind these cubes is a near-white
   page. A coloured shadow on near-white is a grey cloud: it reads as a smudge
   or a printing fault rather than as light, and it is worst exactly where a
   cube overhangs onto the page instead of onto the blue. Screen blend cannot
   do that. It only ever brightens, so it lifts the cube and has nothing to add
   against the page.

   ⚠ EACH ONE STILL RUNS TWO CLOCKS at periods that do not divide into each
   other, so no cube has a single loop to fall into. */
.cube-field--solo .cube-glow {
    filter: blur(14px);
}

/* ------------------------------------------------------------- the glare
   ⚠ ONLY WHERE THE LIGHT IS. This is the SAME radial blob as the fill, at the
   SAME two coordinates, and nothing else — no linear ramp, no full-bleed
   rectangle. So when the light is somewhere in the middle of the shape the
   card covers all of it and there is no outer glow at all; when it drifts
   against an edge, that edge alone lights up and the rest of the outline stays
   dark.

   ⚠ AN EARLIER VERSION HALOED THE WHOLE CONTAINER and it was wrong twice over.
   It ringed the entire shape rather than following the light, and because the
   halo was a rectangle 22px larger than the card with an opaque gradient right
   up to its own edge, the blur left a visible straight cut-off above the
   corner. A blurred box with a full-bleed fill always does; a blurred blob
   that fades to transparent inside its own box never can.

   ⚠ `inset: 0`, NOT A NEGATIVE INSET. The box matches the shape exactly, so a
   percentage coordinate means the same point in both — the glare cannot drift
   out of register with the light it belongs to. The spill comes from the blur
   itself, which paints outside the border box on purpose.

   ⚠ NEVER PUT `isolation` ON THESE TWO SHAPES. A `z-index: -1` pseudo-element
   only sits behind its own parent's background while that parent is NOT a
   stacking context. Isolate it and the same pseudo-element is promoted to
   painting INSIDE the shape, on top of the fill, where it washes the whole
   thing out. It looks like a blur bug and the cause is one property on a
   different rule. */
.reference-product > .max-w-7xl > .relative::after,
.price-card-yearly::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    /* ⚠ THE GLARE IS SCALED BY THE SAME FACTOR AS THE LIGHT, ALWAYS. It is the
       spill from that light, so a dimmer light with the old glare reads as a
       shape glowing harder on the outside than the inside — which looks like a
       fault rather than a choice. Front light went 0.24 to 0.17, so these went
       down by the same 0.71. */
    background-image: radial-gradient(34% 42% at var(--lx) var(--ly),
        rgba(120, 205, 250, 0.64) 0%,
        rgba(120, 205, 250, 0.25) 45%,
        rgba(120, 205, 250, 0) 72%);
    filter: blur(26px);
}

/* ---------------------------------------------------------- reduced motion
   The ambient layers are REMOVED rather than paused: a glow frozen at half
   opacity is a smudge under the artwork that nobody chose. Hover still
   changes colour, so a pointer user keeps every affordance. */
@media (prefers-reduced-motion: reduce) {
    /* ⚠ HOLD THE LIGHT, DROP THE MOVEMENT. An earlier version removed the glow
       outright here, which quietly deletes a deliberate part of the design for
       anyone whose system asks for less motion — and on Windows that setting
       is one toggle in Accessibility that plenty of people have off without
       knowing. The glow is design; only its travel is motion. */
    .cube-glow {
        animation: none;
        --glow-a: 0.6;
        --glow-k: 1;
    }

    .reference-product > .max-w-7xl > .relative,
    .price-card-yearly {
        animation: none;
        --lx: 32%;
        --ly: 26%;
    }

    .hero-cta,
    .site-header a[href="/book"],
    .price-card-monthly > a,
    .wistia-play-button,
    .product-mockup img,
    .xnp-underline {
        transition-duration: 1ms;
    }

    .hero-cta:hover,
    .hero-cta:active,
    .site-header a[href="/book"]:hover,
    .site-header a[href="/book"]:active,
    .price-card-monthly > a:hover,
    .price-card-monthly > a:active,
    .product-mockup:hover img,
    .product-mockup:focus-within img {
        transform: none;
    }

    .product-mockup:hover .wistia-play-button,
    .product-mockup:focus-within .wistia-play-button,
    .product-mockup:active .wistia-play-button {
        transform: translate(-50%, -50%);
    }
}

/* Phone: the two hand-placed line breaks in the pricing copy are set for the
   wide layout. At 390 the headline's first line no longer fits, which left the
   word "to" alone on a line of its own, and the subheadline's break landed
   mid-thought on top of the natural wrap. One size down fixes the first; the
   second just wraps on its own. */
@media (max-width: 639px) {
    .pricing-copy > h2 {
        /* Scales with the screen so both hand-set lines stay whole from 320px
           up. A fixed size fits one phone width and wraps on the next one down. */
        font-size: clamp(1.75rem, 8.6vw, 2.7rem);
    }

    .pricing-copy > p br {
        display: none;
    }
}

/* The pricing copy carries two hand-set line breaks, so its two blocks are only
   right when each of those lines fits the column whole. Between 1024 and 1199
   the copy column is at its narrowest -- 414px at 1024 -- and both the headline
   and the subheadline spilled onto a third line there. Sizing them off the
   viewport in that band keeps each line whole without touching the wider
   layout, where the fixed sizes already fit. */
@media (min-width: 1024px) and (max-width: 1199px) {
    .pricing-copy > h2 {
        font-size: clamp(2.25rem, 4.35vw, 3rem);
    }

    .pricing-copy > p {
        font-size: clamp(1rem, 1.68vw, 1.25rem);
    }
}

/* The purple cube sits above the blue card and reaches up out of it. With the
   copy right-aligned, the copy now ends where the cube begins, so it has to be
   given room in the two bands where they meet. Both numbers are measured at the
   tightest width in each band, not chosen by eye. */
@media (min-width: 640px) and (max-width: 1023px) {
    /* Stacked, with the copy still right-aligned: the cube pokes up into its
       last line and needs the room. Below 640 it does not — the cube is 7rem
       there instead of 9.8125rem and the copy reads left — so this stops at
       639, where the extra 2rem was only making the gap to the card wider than
       the same gap in every other section. */
    .pricing-copy {
        margin-bottom: 2rem;
    }
}

@media (min-width: 1024px) and (max-width: 1199px) {
    /* Side by side, and this is the narrowest the copy column ever gets: at 1024
       the copy's own left edge lands 10px inside the cube. There is no width the
       cube can take that clears the card title on its left and the copy on its
       right at once, so the copy is lifted clear instead and the cube is left at
       the size it is everywhere else. 6.75rem on top of the -9.25rem below. */
    .pricing-copy {
        transform: translateY(-16rem);
    }

    /* And the cube comes back 3.5 points. Its right edge was landing 3px inside
       the start of the subheadline's last line. It still clears the card title
       on its other side by 37px. */
    .pricing-cube {
        left: 76.5%;
    }
}

/* ------------------------------------------------ the two wide cube bands
   The band under the features and the one under the footer. On a phone they
   fill the screen edge to edge, so the same glow that reads as a soft lift on
   a desktop monitor reads as a flare. The light itself is unchanged: only how
   much of it lands. Desktop is untouched. */
@media (max-width: 639px) {
    .cube-field--wide .cube-glow {
        opacity: calc(var(--glow-a) * 0.42);
    }
}

@media (min-width: 640px) and (max-width: 1023px) {
    .cube-field--wide .cube-glow {
        opacity: calc(var(--glow-a) * 0.7);
    }
}

/* Phone only: the pricing copy reads left. Right alignment is what puts the
   headline against the cards it belongs to on a wide screen; stacked on a
   phone there is nothing to its right to align to, and a right-ragged
   paragraph is slower to read. */
@media (max-width: 639px) {
    .pricing-copy {
        margin-left: 0;
        text-align: left;
    }
}
