/* The Customer Portal's own layer, on top of nexus.css.

   Two jobs, and they are separate.

   One: make this portal look like Shelli. A shop owner meets Shelli every day at the counter
   and Nexus perhaps twice a year, and the two are the same product family. Shelli's own interface
   is built on a purple primary (#845ADF), soft neutral greys, half-rem corners and quiet borders;
   Nexus was blue on square-ish cards. The tokens below retune this portal to that palette, which
   is a handful of variables because nexus.css was written against variables. The Admin Portal is
   deliberately untouched — staff are not the audience being made to feel at home, and changing
   the shared file would have moved both.

   Two: the pieces the Admin Portal has no use for. A shop's own page, a tile of numbers, a
   meter with a bar under it. Those belong here rather than in the shared file, exactly as the note
   at the top of nexus.css says.

   Nothing here restyles a shared component into something different — a button is still a button.
   A portal that redefined the design system would be a second design system. */

/* The drawer is dark next door because staff work in it all day and the contrast helps them find
   it. A customer opens this twice a year, and a black column is heavier than the thing it holds. */
.shell-customer .drawer {
    background: #fff;
    border-right: 1px solid var(--line);
    color: var(--ink-soft);
}

.shell-customer .drawer .brand {
    color: var(--ink);
}

.shell-customer .nav-link {
    color: var(--ink-soft);
    font-weight: 500;
    margin-bottom: 0.15rem;
    padding: 0.5rem 0.6rem;
}

.shell-customer .nav-link:hover {
    background: var(--paper);
    color: var(--ink);
}

/* The active item is a tint rather than the brand gradient. Five links with one of them carrying a
   full-strength gradient makes the menu the loudest thing on a page whose job is numbers. */
.shell-customer .nav-link.active {
    background: rgb(132 90 223 / 10%);
    color: var(--accent-dark);
}

.nav-icon {
    flex-shrink: 0;
    opacity: 0.8;
}

/* Whose company this is, sitting above the links. */
.nav-scope {
    border-bottom: 1px solid var(--line);
    border-top: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    margin-bottom: 0.9rem;
    padding: 0.6rem 0.6rem 0.7rem;
}

.nav-scope-label {
    color: var(--ink-faint);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.nav-scope-name {
    color: var(--ink);
    font-weight: 600;
    overflow-wrap: anywhere;
}

/* What they are in this company, under its name. Quiet on purpose — it is only consulted when a
   screen offers less than the last one did, which is exactly what happens to somebody who owns
   one company and was invited into another. Without it the thinner menu reads as a fault. */
.nav-scope-role {
    color: var(--muted);
    display: block;
    font-size: 0.78rem;
    margin-top: 0.15rem;
}

/* "Yours" and "you were invited to", inside the switcher. Drawn only where both kinds exist —
   an owner with two shops gets the plain list they always had. */
.nav-switch-heading {
    color: var(--muted);
    display: block;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    padding: 0.5rem 0.6rem 0.2rem;
    text-transform: uppercase;
}

/* The role, on the entry rather than only after arriving. This is the list somebody scans to
   decide where to go, and "why does that one show fewer pages" is answered here. */
.nav-switch-item .nav-switch-role {
    color: var(--muted);
    display: block;
    font-size: 0.75rem;
}

.nav-switch {
    margin: -0.6rem 0 0.9rem;
    padding: 0 0.6rem;
}

.nav-switch summary {
    color: var(--accent);
    cursor: pointer;
    font-size: 0.85rem;
    padding: 0.25rem 0;
}

.nav-switch-list {
    display: flex;
    flex-direction: column;
    padding: 0.25rem 0 0.5rem;
}

.nav-switch-item {
    border-radius: 6px;
    color: var(--ink-soft);
    font-size: 0.9rem;
    padding: 0.35rem 0.5rem;
    text-decoration: none;
}

.nav-switch-item:hover {
    background: var(--paper);
}

.nav-switch-item.is-current {
    color: var(--ink);
    font-weight: 600;
}

/* --- The page above the cards ------------------------------------------------ */

/* A heading, what it is about, and whatever acts on it — one row that becomes two on a phone. */
.page-head {
    align-items: flex-start;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: space-between;
    margin-bottom: 1.1rem;
}

.page-head h1 {
    margin-bottom: 0.15rem;
}

.page-head .lede {
    margin: 0;
}

/* Where a page came from, above its title. A shop's page is reached from three places and the
   browser's back button is not an answer on a phone that arrived by a link. */
.crumb {
    color: var(--ink-faint);
    display: inline-flex;
    font-size: 0.85rem;
    gap: 0.35rem;
    margin-bottom: 0.35rem;
    text-decoration: none;
}

.crumb:hover {
    color: var(--accent);
}

/* --- Tiles ------------------------------------------------------------------- */

/* The numbers a dashboard leads with. auto-fit rather than a column count, so four tiles become
   two rows on a tablet and one column on a phone without a breakpoint per layout. */
.tile-grid {
    display: grid;
    gap: 0.85rem;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    margin-bottom: 1.1rem;
}

.tile {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.9rem 1rem;
}

.tile-label {
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.tile-value {
    font-size: 1.6rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.tile-good .tile-value {
    color: var(--good);
}

.tile-warn .tile-value {
    color: var(--warn);
}

.tile-bad .tile-value {
    color: var(--bad);
}

/* A tile that is also a link. The whole card, not a word inside it — a 40px target on a phone
   beats an underlined phrase. */
.tile-link {
    color: inherit;
    text-decoration: none;
    transition: border-color 0.12s ease;
}

.tile-link:hover {
    border-color: var(--accent-bright);
}

/* --- A shop, as a card ------------------------------------------------------- */

.store-grid {
    display: grid;
    gap: 0.85rem;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

/* The shops list is cards rather than a table on purpose. A table of shops answers "which of these
   is which"; an owner with four branches is asking "is anything wrong with one of them", and the
   answer is a state per shop rather than a row of columns to read across. */
.store-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    color: inherit;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    padding: 1rem;
    text-decoration: none;
    transition: border-color 0.12s ease;
}

.store-card:hover {
    border-color: var(--accent-bright);
}

.store-card-head {
    align-items: baseline;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

.store-card-name {
    font-size: 1.02rem;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.store-card-where {
    color: var(--ink-faint);
    font-size: 0.85rem;
}

.store-card-facts {
    border-top: 1px solid var(--line);
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1.1rem;
    padding-top: 0.55rem;
}

.store-card-fact {
    display: flex;
    flex-direction: column;
}

.store-card-fact-label {
    color: var(--ink-faint);
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.store-card-fact-value {
    font-weight: 600;
}

/* A shop that is closed, or one nobody has quoted for. Dimmed rather than hidden — it is still
   theirs, and hiding it is how a branch goes six months without a licence. */
.store-card-quiet {
    opacity: 0.72;
}

/* --- Connected, or not ------------------------------------------------------- */

/* A dot beside a word. Colour alone would say nothing to a colour-blind owner, so the word is
   always there and the dot is the thing you find at a glance. */
.live {
    align-items: center;
    color: var(--ink-soft);
    display: inline-flex;
    font-size: 0.85rem;
    gap: 0.35rem;
    white-space: nowrap;
}

.live::before {
    background: var(--ink-faint);
    border-radius: 50%;
    content: "";
    height: 8px;
    width: 8px;
}

.live-on::before {
    background: #26bf94;
}

.live-off::before {
    background: #c9ced6;
}

/* --- Label and value, down a page -------------------------------------------- */

/* The company's details, a shop's details, an installation's. Two columns on a desktop, stacked on
   a phone, and the label never wraps away from its value. */
.kv {
    display: grid;
    gap: 0.15rem 1rem;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.kv-row {
    border-bottom: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    padding: 0.5rem 0;
}

.kv-label {
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.kv-value {
    overflow-wrap: anywhere;
}

/* --- A meter, with what is left under it ------------------------------------- */

.meter-grid {
    display: grid;
    gap: 0.85rem;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* Charts side by side rather than stacked. A chart per meter is right — documents and email are
   counted in the same unit and mean nothing alike — but two full-width ones pushed the table that
   explains them off the bottom of the screen. Wider than the meter cards, because a trend needs
   horizontal room before it says anything; below that it falls to one column and stays readable. */
.chart-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    margin-bottom: 1.1rem;
}

.meter {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.9rem 1rem;
}

.meter-head {
    align-items: baseline;
    display: flex;
    gap: 0.5rem;
    justify-content: space-between;
}

.meter-name {
    font-weight: 600;
}

.meter-left {
    font-size: 1.3rem;
    font-weight: 600;
    letter-spacing: -0.02em;
}

/* The track is always full width; the fill is what is left, not what is spent. An owner reads a
   fuel gauge, not a progress bar — a bar that filled up as credits ran out would be read backwards
   by everybody who glanced at it. */
.meter-bar {
    background: #eceef2;
    border-radius: 999px;
    height: 6px;
    overflow: hidden;
}

.meter-fill {
    background: var(--accent-bright);
    border-radius: 999px;
    display: block;
    height: 100%;
}

.meter-fill-warn {
    background: #f5b849;
}

.meter-fill-bad {
    background: #e6533c;
}

.meter-foot {
    color: var(--ink-faint);
    display: flex;
    font-size: 0.82rem;
    gap: 0.5rem;
    justify-content: space-between;
}

/* --- What is on sale --------------------------------------------------------- */

/* The credits page is laid out as a shop: one section per kind of credit, holding that kind's gauge
   and that kind's packages.

   A section a company cannot use is never rendered, so there is no rule here for hiding one. */

/* The line above the sections that says the whole shop is a request rather than a checkout, with
   the market it is priced for on the other end. */
.shelf-head {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    justify-content: space-between;
    margin-bottom: 1rem;
}

.shelf-head p {
    margin: 0;
}

.shelf {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

/* The card's own children carry the rhythm now, so the paragraph margins that used to do it
   would double every gap. */
.shelf > p {
    margin: 0;
}

/* What is left, beside the heading rather than under it: the question "have I got any" is asked
   at the same moment as "what does more cost", and they belong on one line. */
.shelf-left {
    font-size: 0.9rem;
    font-weight: 600;
}

/* --- Nothing here yet -------------------------------------------------------- */

/* A real state, drawn deliberately rather than left as an empty region. Every one of these says
   what would put something here. */
.empty {
    border: 1px dashed var(--line);
    border-radius: var(--radius);
    color: var(--ink-faint);
    padding: 1.6rem 1rem;
    text-align: center;
}

.empty p {
    margin: 0 0 0.6rem;
}

.empty p:last-child {
    margin-bottom: 0;
}

/* --- Narrow screens ---------------------------------------------------------- */

@media (max-width: 640px) {
    .tile-value {
        font-size: 1.4rem;
    }

    /* One column, because two 190px tiles plus a gap is wider than a phone and the grid would
       otherwise let them shrink until the numbers wrapped mid-figure. */
    .tile-grid,
    .store-grid,
    .meter-grid {
        grid-template-columns: 1fr;
    }
}

/* --- Waiting for a shop to answer --------------------------------------------

   A card rather than an overlay. The reader has just pressed a button and is
   waiting on a machine in a shop, which can take twenty seconds and can fail —
   covering the page would hide the window picker and the sentence explaining
   what is happening. */
.report-waiting {
    align-items: center;
    display: flex;
    gap: 1.1rem;
}

.report-waiting h2 {
    margin-top: 0;
}

/* Its own element rather than a border on the card: a spinner drawn on the
   panel would rotate the text with it. Sized in rem so it grows with the
   reader's own type size. */
.report-spinner {
    animation: report-spin 900ms linear infinite;
    block-size: 2rem;
    border: 3px solid var(--line);
    border-radius: 50%;
    border-top-color: var(--accent);
    flex-shrink: 0;
    inline-size: 2rem;
}

@keyframes report-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Somebody who has asked not to be shown motion still needs to know the page is
   doing something, so the ring stays and stops turning. A spinner removed
   entirely leaves a card that looks finished and is not. */
@media (prefers-reduced-motion: reduce) {
    .report-spinner {
        animation: none;
    }
}

/* When it was built, when it goes, and how many asks are left — one line above
   the numbers rather than three sentences under them. */
.report-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 1.1rem;
    margin: 0 0 1rem;
}

/* The button and what it will cost, side by side. The cost has to be readable
   before the button is pressed, not after it is refused. */
.report-ask {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
}

.report-ask p {
    margin: 0;
}

/* --- the shop window --------------------------------------------------------

   The one page somebody reads before they have an account.

   Every colour is a token from nexus.css, so the page follows the theme rather
   than carrying a palette of its own - a landing page with its own colours is
   the page that stops matching the product the first time the brand moves.

   Motion is entrance only, and every rule of it is inside the reduced-motion
   guard at the bottom. A shop owner on a slow phone gets the page, not the
   performance. */

/* The shell caps .content at 64rem, which is the reading column a dashboard wants and the
   wrong measure for the one page in this portal that is not one. On a wide monitor the hero
   became a small card adrift in the middle of the screen.

   Widened here rather than in the shared layer, and by the same means the work layout already
   uses - .content:has(.work-layout) sets 90rem. The landing goes further and takes the whole
   width, because its sections carry background of their own that has to reach both edges.

   The pricing page needs it every bit as much and was missing from this list, which is the kind
   of thing that only shows up on a monitor. Every section on it already asks for the full-bleed
   padding below - .pricing-head, .pricing-plans, .compare, .closing - and every one of them was
   resolving it against 64rem instead of the viewport, so the gutter computed to its 1.5rem floor
   and the band was 1024px wide on a 1440px screen. Three plan cards could not fit in it, Pro
   wrapped onto a row of its own, and it read as a two-plan product with an orphan underneath. */
.content:has(.hero),
.content:has(.pricing-head) {
    max-width: none;
    padding-inline: 0;
    padding-top: 0;
}

/* Full-bleed background, centred content, and no wrapper element to do it.

   The padding resolves to whatever is left over once 78rem is taken out of the viewport, or
   the gutter on a narrow one - so every section paints edge to edge while its words stay in a
   column somebody can read. A wrapper div per section would do the same and put six of them in
   the markup. */
.hero,
.industries,
.steps,
.capabilities,
.screens,
.planes,
.assurances,
.faq,
.closing {
    --landing-gap: clamp(3rem, 7vw, 5.5rem);

    padding-inline: max(1.5rem, calc((100% - 78rem) / 2));
}

/* A tinted field behind the fold rather than a photograph. It reads as designed
   at every width and costs no request, which a hero image does. */
.hero {
    align-items: center;
    background:
        radial-gradient(60% 80% at 15% 0%,
            color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 70%),
        radial-gradient(50% 70% at 95% 20%,
            color-mix(in srgb, var(--maui) 18%, transparent) 0%, transparent 70%);
    display: grid;
    gap: clamp(1.5rem, 5vw, 4rem);
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    margin-bottom: var(--landing-gap);

    /* Deeper than a card. A hero that is only as tall as its words reads as a banner; this is
       the first thing anybody sees and it has to hold the screen. */
    padding-block: clamp(3rem, 9vw, 7rem);
}

@media (max-width: 60rem) {
    .hero {
        grid-template-columns: minmax(0, 1fr);
        text-align: center;
    }

    .hero-actions {
        justify-content: center;
    }
}

.hero-eyebrow {
    color: var(--accent);
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    margin: 0 0 0.9rem;
    text-transform: uppercase;
}

/* clamp rather than breakpoints: the headline is the one thing that has to look
   deliberate at every width, and two breakpoints never quite manage it. */
.hero-title {
    font-size: clamp(2.1rem, 4.6vw, 4rem);
    letter-spacing: -0.02em;
    line-height: 1.08;
    margin: 0 0 1rem;
    max-width: 15ch;
}

@media (max-width: 60rem) {
    .hero-title {
        margin-inline: auto;
    }
}

.hero-lede {
    color: var(--ink-soft);
    font-size: clamp(1rem, 1.6vw, 1.15rem);
    line-height: 1.65;
    margin: 0 0 1.75rem;
    max-width: 48ch;
}

@media (max-width: 60rem) {
    .hero-lede {
        margin-inline: auto;
    }
}

.hero-actions {
    /* Centred, so a taller button does not drag a shorter one to the top of the row. */
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

/* Never narrower than their own words.

   Flex items shrink by default, and these were being squeezed until the label wrapped: "Ver los
   planes" came out 153px wide and 70px tall beside a 50px primary, which read as a card that had
   landed in the button row rather than as a second button. */
.hero-actions .button {
    flex: none;

    /* .button-primary carries a 1.2rem top margin globally, so a primary hangs below the form
       above it. In a row of buttons it drops that one 19px below its neighbour and stretches the
       row to 70px around two 50px buttons. Zeroed here the way .action-row already does it -
       scoped, because zeroing it on the button itself once lost the gap for every form in the
       portal at once. */
    margin-top: 0;
    white-space: nowrap;
}

.button-large {
    font-size: 1.02rem;
    padding: 0.72rem 1.65rem;
}

.hero-note {
    color: var(--ink-faint);
    font-size: 0.88rem;
    margin: 1.15rem 0 0;
}

/* --- the drawn devices ------------------------------------------------------

   A till and a phone, in the page's own colours. Schematic on purpose: a
   fabricated screenshot would be a picture of a product that does not look
   like that, and it would go stale the first time the real one changed. */

.hero-art {
    display: flex;
    justify-content: center;
}

/* --- Screens, and the things they stand in -------------------------------------

   The chrome is drawn here rather than shipped as artwork: three PNG bezels would be three more
   files to keep in step with the palette, and a device wanted at another size would be a new
   export instead of a number. */

.device {
    display: flex;
    flex-direction: column;
    margin: 0;
    max-width: 100%;
}

/* The bezel. The screen inside it is whatever it is handed - a capture keeps its own
   proportions, and a drawn one takes an aspect ratio from the device below. */
.device-screen {
    background: var(--ink);
    overflow: hidden;
    position: relative;
}

.device-shot {
    display: block;
    height: auto;
    width: 100%;
}

/* A monitor: a thin dark bezel, a neck and a foot. The neck and the foot are pseudo-elements
   on the figure rather than boxes in the markup, because they are decoration and a screen
   reader meeting two empty divs has to wonder what it missed. */
.device-desktop .device-screen {
    border: 10px solid var(--ink);
    border-radius: 12px;
    box-shadow: 0 18px 48px rgb(20 23 28 / 18%);
    order: 1;
}

/* Every part numbered, because a ::before is drawn before the content by default and put the
   monitor's neck above its screen - a stand growing out of the top of a monitor, which is
   exactly the kind of thing that only shows up when somebody looks. */
.device-desktop::before {
    order: 2;
}

.device-desktop::after {
    order: 3;
}

.device-desktop .device-caption {
    order: 4;
}

.device-desktop::before {
    background: linear-gradient(
        to bottom, color-mix(in srgb, var(--ink) 70%, transparent), var(--ink));
    border-radius: 0 0 3px 3px;
    content: "";
    height: 14px;
    margin: 0 auto;
    width: 22%;
}

.device-desktop::after {
    background: var(--ink);
    border-radius: 4px;
    content: "";
    height: 7px;
    margin: 0 auto;
    width: 38%;
}

/* A self-service kiosk: a portrait screen on a floor column. Built like the monitor and
   numbered like it, because a ::before is drawn before the content and a kiosk growing out of
   the top of its own pedestal is the same bug wearing a different shape. */
.device-kiosk .device-screen {
    border: 12px solid var(--ink);
    border-radius: 14px;
    box-shadow: 0 18px 48px rgb(20 23 28 / 18%);
    order: 1;
}

.device-kiosk::before {
    background: linear-gradient(
        to bottom, color-mix(in srgb, var(--ink) 70%, transparent), var(--ink));
    content: "";
    height: 26px;
    margin: 0 auto;
    order: 2;
    width: 30%;
}

.device-kiosk::after {
    background: var(--ink);
    border-radius: 5px;
    content: "";
    height: 9px;
    margin: 0 auto;
    order: 3;
    width: 55%;
}

.device-kiosk .device-caption {
    order: 4;
}

/* A tablet: rounder, an even bezel all the way round, landscape. */
.device-tablet .device-screen {
    border: 14px solid var(--ink);
    border-radius: 20px;
    box-shadow: 0 14px 36px rgb(20 23 28 / 16%);
}

/* A phone: rounder still, a thinner bezel so the screen is most of it, and portrait. */
.device-phone .device-screen {
    border: 9px solid var(--ink);
    border-radius: 26px;
    box-shadow: 0 14px 36px rgb(20 23 28 / 16%);
}

/* The bar across the top of a phone. Not a notch - a shape borrowed off one manufacturer's
   handset is that manufacturer's handset. */
.device-phone .device-screen::after {
    background: var(--ink);
    border-radius: 0 0 6px 6px;
    content: "";
    height: 10px;
    left: 50%;
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    width: 34%;
    z-index: 1;
}

/* The drawn screen: blocks in the shape of a till, obviously a drawing rather than a capture.
   A page waiting on a real screenshot must not carry something a reader could take for one. */
.device-placeholder {
    background: var(--paper);
    display: flex;
    flex-direction: column;
    gap: 6%;
    height: 100%;
    padding: 7%;
}

.device-tablet .device-placeholder {
    aspect-ratio: 4 / 3;
}

.device-phone .device-placeholder {
    aspect-ratio: 9 / 16;
}

.device-kiosk .device-placeholder {
    aspect-ratio: 3 / 4;
}

.ph-bar {
    background: color-mix(in srgb, var(--accent) 24%, transparent);
    border-radius: 4px;
    display: block;
    flex: none;
    height: 7%;
}

.ph-body {
    display: flex;
    flex: 1;
    gap: 6%;
    min-height: 0;
}

.ph-tiles {
    display: grid;
    flex: 2;
    gap: 6%;
    grid-template-columns: repeat(3, 1fr);
}

.device-phone .ph-tiles,
.device-kiosk .ph-tiles {
    grid-template-columns: repeat(2, 1fr);
}

.ph-tile {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-radius: 5px;
}

.ph-tile:nth-child(3n + 2) {
    background: color-mix(in srgb, var(--ink) 8%, transparent);
}

.ph-panel {
    background: color-mix(in srgb, var(--ink) 5%, transparent);
    border-radius: 5px;
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 8%;
    padding: 7%;
}

/* The panel is the ticket, so its lines are a list and the last block is the total. On a
   phone there is no room for it beside the products, so it goes under them. */
.device-phone .ph-body,
.device-kiosk .ph-body {
    flex-direction: column;
}

.ph-line {
    background: color-mix(in srgb, var(--ink) 16%, transparent);
    border-radius: 3px;
    flex: none;
    height: 6px;
}

.ph-total {
    background: color-mix(in srgb, var(--accent) 38%, transparent);
    border-radius: 4px;
    flex: none;
    height: 14px;
    margin-top: auto;
}

.device-caption {
    margin-top: 1rem;
    text-align: center;
}

.device-title {
    display: block;
    font-weight: 700;
}

.device-note {
    color: var(--ink-soft);
    display: block;
    font-size: 0.92rem;
    line-height: 1.5;
    margin-top: 0.35rem;
}

/* --- the hero stage ---------------------------------------------------------

   Four devices, one composition. The capture takes the top row whole because it is the only
   thing on this page a reader can check against what they will get; the three still waiting on
   screenshots stand under it at the sizes they really are next to a monitor.

   Capped rather than stretched: past this the capture is being enlarged beyond its own
   resolution and starts to look soft. */
.hero-art .hero-stage {
    max-width: 40rem;
    width: 100%;
}

.hero-stage {
    display: grid;

    /* Not three equal columns. A kiosk, a tablet and a phone given a third each come out the
       same width, which is the one thing a picture of three different devices must not say. */
    grid-template-columns: 0.85fr 1.35fr 0.6fr;

    align-items: end;
    gap: clamp(0.6rem, 2vw, 1.4rem);
}

.hero-stage .device-desktop {
    grid-column: 1 / -1;
    margin-bottom: clamp(0.5rem, 2vw, 1.25rem);
}

/* Their bezels shrink with them. A 14px border on a tablet drawn nine rem wide is a picture of
   a frame with a screen somewhere inside it. */
.hero-stage .device-tablet .device-screen {
    border-width: 6px;
    border-radius: 10px;
}

.hero-stage .device-phone .device-screen {
    border-width: 5px;
    border-radius: 13px;
}

.hero-stage .device-kiosk .device-screen {
    border-width: 6px;
    border-radius: 8px;
}

.hero-stage .device-kiosk::before {
    height: 16px;
}

/* Two lines' worth whether the caption fills them or not.

   The row bottom-aligns the figures, and a figure is its device plus its caption - so in Spanish,
   where "Tomando el pedido" wraps and the other two do not, the handheld floated a line above its
   neighbours. Reserving the space levels the devices instead of the captions.

   The subgrid the Shelli Mini row uses would be the better answer and cannot be used here: the
   kiosk's column and base are pseudo-elements ordered inside a flex column, and turning that
   figure into a grid puts them in cells of their own. */
.hero-stage .device-caption {
    margin-top: 0.6rem;
    min-height: 2.6em;
}

.hero-stage .device-title {
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

/* Under the copy rather than beside it, the stage has the whole width and the small devices
   stop being thumbnails. */
@media (max-width: 60rem) {
    .hero-art .hero-stage {
        max-width: 34rem;
    }
}

.screens {
    margin-bottom: var(--landing-gap);
}

/* Devices of two different shapes on one row, sized by what they are rather than by an equal
   share of the width. A tablet and a phone given a column each come out the same height, which
   is the one thing a picture of a tablet and a phone must not say.

   Two rows rather than one, and each device borrows them: the screens line up along the first,
   the captions start together on the second. On a flex row the figures bottom-aligned instead,
   so the device with the shorter caption floated a caption's worth above the other and the two
   read as a pair somebody had failed to line up. */
.screen-row {
    align-items: end;
    display: grid;
    gap: clamp(2rem, 5vw, 4rem);
    grid-template-columns: minmax(0, 26rem) minmax(0, 13rem);
    grid-template-rows: auto auto;
    justify-content: center;
    margin-top: 2.5rem;
}

.screen-row .device {
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid;
}

/* Back to a column once they stack, where there is no row left to share. */
@media (max-width: 44rem) {
    .screen-row {
        grid-template-columns: minmax(0, 22rem);
        justify-items: center;
    }

    .screen-row .device {
        display: flex;
        grid-row: auto;
    }

    .screen-row .device-phone {
        max-width: 13rem;
    }
}

.screen-row .device-caption {
    max-width: 26rem;
}

.screens-more {
    margin: 2.25rem 0 0;
    text-align: center;
}

.screens-more a {
    color: var(--accent);
    font-weight: 600;
}

/* --- the sections -----------------------------------------------------------*/

.grow,
.industries,
.steps,
.capabilities,
.ladder,
.ecosystem,
.nexus-band,
.modules,
.planes,
.assurances,
.faq {
    margin-bottom: var(--landing-gap);
}

/* The sections added with the growth story sit on the same full-bleed field as the rest. Set
   here rather than on each one: a section that forgets it is a column of text in the middle of
   a page whose every other band runs edge to edge. */
.grow,
.ladder,
.ecosystem,
.nexus-band,
.modules {
    padding-inline: max(1.5rem, calc((100% - 78rem) / 2));
}

/* Rhythm. Every section on a flat field reads as one long page; a tinted band every other one
   gives the eye somewhere to rest and makes the page feel composed rather than stacked. The
   tint is faint on purpose - it is punctuation, not decoration. */
.steps,
.planes,
.ladder,
.nexus-band {
    background: color-mix(in srgb, var(--ink) 3%, transparent);
    padding-block: var(--landing-gap);
}

/* --- how somebody starts ---------------------------------------------------*/

.step-list {
    counter-reset: none;
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    list-style: none;
    margin: 0;
    padding: 0;
}

.step {
    border-top: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
    padding-top: 1.1rem;
}

.step-number {
    color: var(--accent);
    display: block;
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1;
    margin-bottom: 0.5rem;
}

.step h3 {
    font-size: 1.05rem;
    margin: 0 0 0.4rem;
}

.step p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0;
}

/* --- the questions ---------------------------------------------------------*/

.faq-list {
    margin-inline: auto;
    max-width: 52rem;
}

.faq-item {
    border-bottom: 1px solid var(--line);
    padding: 1.1rem 0;
}

.faq-item summary {
    cursor: pointer;
    font-size: 1.02rem;
    font-weight: 600;
    list-style: none;
    padding-right: 2rem;
    position: relative;
}

.faq-item summary::-webkit-details-marker {
    display: none;
}

/* Drawn rather than a character, so it turns rather than swapping glyph. */
.faq-item summary::after {
    border-bottom: 2px solid var(--ink-faint);
    border-right: 2px solid var(--ink-faint);
    content: "";
    height: 0.5rem;
    position: absolute;
    right: 0.35rem;
    top: 0.35rem;
    transform: rotate(45deg);
    transition: transform 0.15s ease;
    width: 0.5rem;
}

.faq-item[open] summary::after {
    transform: rotate(-135deg);
}

.faq-item p {
    color: var(--ink-soft);
    line-height: 1.7;
    margin: 0.75rem 0 0;
    max-width: 60ch;
}

.section-title {
    font-size: clamp(1.5rem, 3vw, 2.1rem);
    letter-spacing: -0.015em;
    margin: 0 0 0.6rem;
    text-align: center;
}

.section-lede {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0 auto 2.25rem;
    max-width: 56ch;
    text-align: center;
}

.industry-grid,
.capability-grid {
    display: grid;
    gap: 1rem;
}

.industry-grid {
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.capability-grid {
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.industry,
.capability {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.5rem;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

/* Lifts under the pointer. Small enough to read as responsive rather than as
   something having gone wrong. */
.industry:hover,
.capability:hover {
    border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
    transform: translateY(-2px);
}

.industry-glyph {
    display: block;
    font-size: 1.9rem;
    line-height: 1;
    margin-bottom: 0.75rem;
}

.industry h3,
.capability h3 {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    font-size: 1.08rem;
    gap: 0.5rem;
    margin: 0 0 0.45rem;
}

.industry p,
.capability p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0;
}

/* Not built yet, said on the card. Muted rather than bright: it is a caveat,
   and a badge that competes with the heading reads as a feature. */
.badge-soon {
    background: color-mix(in srgb, var(--warn) 20%, transparent);
    border-radius: 999px;
    color: var(--ink-soft);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    padding: 0.15rem 0.5rem;
    text-transform: uppercase;
}

/* --- plans ------------------------------------------------------------------*/

.product {
    margin-bottom: 2.5rem;
}

.product-head {
    margin-bottom: 1.15rem;
}

.product-head h3 {
    font-size: 1.35rem;
    margin: 0 0 0.35rem;
}

.market-note {
    color: var(--ink-faint);
    font-size: 0.88rem;
    margin: 0;
    text-align: center;
}

/* --- the closing ------------------------------------------------------------*/

.assurances {
    display: grid;
    gap: 1.5rem;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.assurance h3 {
    font-size: 1.02rem;
    margin: 0 0 0.4rem;
}

.assurance p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0;
}

.closing {
    background:
        radial-gradient(70% 120% at 50% 0%,
            color-mix(in srgb, var(--accent) 15%, transparent) 0%, transparent 70%);
    padding-block: clamp(3rem, 8vw, 5.5rem);
    text-align: center;
}

.closing h2 {
    font-size: clamp(1.5rem, 3vw, 2.1rem);
    margin: 0 0 0.7rem;
}

.closing p {
    color: var(--ink-soft);
    margin: 0 auto 1.75rem;
    max-width: 46ch;
}

.closing .hero-actions {
    justify-content: center;
}

/* --- motion -----------------------------------------------------------------

   Entrance only, and staggered so the page assembles rather than flashing. The
   default is the finished state, so anything that stops the animation running -
   an old browser, a blocked stylesheet - leaves the page readable rather than
   invisible, which is the failure mode of animating from opacity nought. */

@media (prefers-reduced-motion: no-preference) {
    .hero-copy,
    .hero-art,
    .industry,
    .capability,
    .assurance,
    .grow-stage,
    .rung,
    .eco-node,
    .nexus-point,
    .pricing-plans .offer {
        animation: landing-rise 0.5s ease-out both;
    }

    .hero-art { animation-delay: 0.08s; }

    .industry:nth-child(2),
    .capability:nth-child(2),
    .grow-stage:nth-child(2),
    .rung:nth-child(2),
    .nexus-point:nth-child(2) { animation-delay: 0.05s; }

    .industry:nth-child(3),
    .capability:nth-child(3),
    .grow-stage:nth-child(3),
    .rung:nth-child(3),
    .nexus-point:nth-child(3) { animation-delay: 0.1s; }

    .industry:nth-child(n+4),
    .rung:nth-child(4) { animation-delay: 0.15s; }

    .capability:nth-child(4) { animation-delay: 0.15s; }
    .capability:nth-child(n+5) { animation-delay: 0.2s; }

    .pricing-plans .offer:nth-child(2) { animation-delay: 0.05s; }
    .pricing-plans .offer:nth-child(3) { animation-delay: 0.1s; }

    /* The comparison arrives as one card rather than forty rows. Staggering the lines would
       animate the thing somebody is trying to read, and a table that assembles itself under the
       eye is slower to read than one that is simply there. */
    .compare-card {
        animation: landing-rise 0.5s ease-out both;
        animation-delay: 0.08s;
    }

    /* The two switches settle after the cards they change, so the eye lands on the prices first
       and then notices there is something to press. */
    .market-switch { animation: landing-rise 0.45s ease-out both; }

    .term-switch {
        animation: landing-rise 0.45s ease-out both;
        animation-delay: 0.05s;
    }

    /* A tick that draws itself as its row arrives. Scale rather than opacity, because a tick
       fading in reads as a page still loading and a tick popping in reads as an answer. */
    .compare-yes {
        animation: tick-in 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) both;
        display: inline-block;
    }

    /* Six down the column, then the rest together. Enough for the eye to read it as a sweep
       rather than a flash, and short enough that nobody is waiting on row forty. */
    .compare-table tr:nth-child(2) .compare-yes { animation-delay: 0.10s; }
    .compare-table tr:nth-child(3) .compare-yes { animation-delay: 0.14s; }
    .compare-table tr:nth-child(4) .compare-yes { animation-delay: 0.18s; }
    .compare-table tr:nth-child(5) .compare-yes { animation-delay: 0.22s; }
    .compare-table tr:nth-child(n+6) .compare-yes { animation-delay: 0.26s; }

    /* The recommended plan lifts a little as it settles, so the card the business is pointing at
       is the one that moves last and finishes highest. */
    .pricing-plans .offer-featured {
        animation: featured-rise 0.6s ease-out both;
        animation-delay: 0.12s;
    }
}

@keyframes tick-in {
    from {
        opacity: 0;
        transform: scale(0.4);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

@keyframes featured-rise {
    from {
        opacity: 0;
        transform: translateY(1.1rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes landing-rise {
    from {
        opacity: 0;
        transform: translateY(0.75rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* --- signing in and opening an account --------------------------------------

   Two panels: the argument on the left, the form on the right. The shared .account-card is a
   card alone in the middle of the screen, which is right for the Admin Portal - a staff sign-in
   has nothing to sell and the person opening it has already decided - and was the one place on
   this site where a visitor stopped being sold to.

   All of it scoped under .account-split, so the Admin Portal keeps its card untouched. */

.account-split {
    display: grid;
    gap: clamp(2rem, 5vw, 4.5rem);

    /* Both tracks capped, and that is what centres the pair. The panel column used to be 1fr:
       a free-space track eats everything the window has spare, so justify-content was handed
       nothing to centre and the two panels sat hard against the right of a wide monitor with
       half a screen of nothing beside them. minmax keeps them free to shrink. */
    grid-template-columns: minmax(0, 30rem) minmax(0, 26rem);
    justify-content: center;
    padding: clamp(2rem, 6vw, 4rem) clamp(1rem, 5vw, 3rem);
}

/* The panel is not read on a phone, and a column of reassurance above the fields is a column
   somebody scrolls past to reach them. Below the breakpoint the form is the whole job. */
@media (max-width: 60rem) {
    .account-split {
        grid-template-columns: minmax(0, 26rem);
    }

    .account-pitch {
        display: none;
    }
}

.account-pitch {
    align-self: center;
    max-width: 30rem;

    /* Right-aligned against the gutter, so the two panels read as one composition rather than
       as a column of text that happens to sit beside a box. */
    justify-self: end;
}

.account-logo {
    display: inline-block;
    margin-bottom: 2rem;
}

.account-logo img {
    display: block;
    height: 46px;
    width: auto;
}

/* Only on the narrow layout, where the panel that carries the other one is gone. */
.account-logo-small {
    display: none;
}

@media (max-width: 60rem) {
    .account-logo-small {
        align-self: center;
        display: inline-block;
        margin-bottom: 1.5rem;
    }

    .account-logo-small img {
        height: 38px;
    }
}

.account-headline {
    font-size: clamp(1.6rem, 3vw, 2.3rem);
    font-weight: 700;
    line-height: 1.2;
    margin: 0 0 1.75rem;
}

.account-points {
    display: grid;
    gap: 0.85rem;
    list-style: none;
    margin: 0 0 2rem;
    padding: 0;
}

/* A tick before each, drawn rather than typed, so the list reads as things you get rather than
   as three sentences that happen to be near each other. */
.account-points li {
    color: var(--ink-soft);
    display: grid;
    gap: 0.7rem;
    grid-template-columns: 1.35rem 1fr;
    line-height: 1.5;
}

.account-points li::before {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-radius: 999px;
    color: var(--accent-dark);
    content: "\2713";
    font-size: 0.75rem;
    font-weight: 700;
    height: 1.35rem;
    line-height: 1.35rem;
    text-align: center;
    width: 1.35rem;
}

.account-quote {
    border-left: 3px solid var(--accent);
    color: var(--ink-faint);
    font-size: 0.9rem;
    margin: 0;
    padding-left: 0.9rem;
}

.account-column {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-width: 0;
}

/* Roomier than the shared card and without the gradient band across the top. The band is Nexus
   introducing itself, which is what a staff portal does; here the reader already knows whose
   site this is - the lockup is right above it. */
.account-split .account-card {
    border-radius: calc(var(--radius) * 1.75);
    box-shadow: 0 24px 60px -40px rgba(15, 23, 42, 0.4);
    max-width: none;
    padding: clamp(1.75rem, 3vw, 2.4rem);
}

.account-split .account-card::before {
    content: none;
}

.account-split .account-card h1 {
    font-size: 1.55rem;
    margin: 0 0 0.4rem;
}

.account-split .account-lede {
    margin-bottom: 1.6rem;
}

/* Centred under the card rather than tucked against its left edge, which is where a footer and
   a language picker belong when the thing above them is the whole page. */
.account-split .account-tools,
.account-split .account-footer {
    align-self: center;
    text-align: center;
}

.account-split .account-aside {
    text-align: center;
}

/* Full width, which is what a sign-in button is everywhere. A 92px button under a 337px field
   reads as one more control on the form rather than as the thing the form is for. */
.account-split .account-form > button[type="submit"],
.account-split .account-form > .button-primary {
    padding-block: 0.62rem;
    width: 100%;
}

/* Room to breathe between the last field and the button. The global 1.2rem on .button-primary
   does this already; saying it here as well keeps it true if the button is ever a plain one. */
.account-split .account-form > button[type="submit"] {
    margin-top: 1.35rem;
}

/* The shape of a way in that is not a password. Same size and same place whichever provider it
   is, so a page offering two does not read as one real button and one afterthought. */
.provider-button {
    align-items: center;
    display: flex;
    font-weight: 600;
    gap: 0.65rem;
    justify-content: center;
    padding-block: 0.62rem;
    width: 100%;
}

/* Drawn to the provider's brand rather than ours. These are the controls on the site that are
   not allowed to look like Shelli: people recognise them by their shape, and a purple one
   carrying our own wordmark reads as a phishing page. */
.google-button {
    background: var(--surface);
    color: var(--ink);
}

/* ⚠ Black, white text, and no mark yet - Apple's guidelines cover the button down to their
   artwork, and it goes in with the browser lane that would draw it. Nothing lists Apple as a
   browser provider until then, so this styles a button the page cannot yet render. */
.apple-button {
    background: #000;
    border-color: #000;
    color: #fff;
}

.apple-button:hover {
    background: #1a1a1a;
    border-color: #1a1a1a;
}

/* --- the dot field ----------------------------------------------------------

   A grid of faint dots, the way Shelli's own sign-in stands on one. What is here is the grid at
   rest: a mask rather than elements, so the whole layer is one box and costs no DOM at all.

   The dots move, and that is dot-field.js on a canvas - CSS can tint a dot and cannot shift one,
   and a wave is dots leaving their places. The stylesheet did once animate a colour across this
   as a fallback, and it had to go: what it drew was four gradient lobes over a fixed grid, which
   looks nothing like the wave and turned up whenever the script had not taken over yet.

   Two places, and no more. The sign-in page, where it is the background and is also what the
   empty half of a wide monitor is made of; and the foot of the public site, as a band under the
   last line. A texture that turns up on every screen stops being one.

   Decoration, and said so in both directions: aria-hidden in the markup, nothing hit-testable
   here. */

.dot-field {
    inset: 0;
    pointer-events: none;
    position: absolute;

    /* Read by dot-field.js, which draws the same two colours onto a canvas. In channels because
       that is the one form canvas can take: it cannot resolve a var() or a color-mix(), and a
       palette written into the script would be a palette in two places. --dot-live is the
       accent this portal is retuned to, and a test holds the two spellings together. */
    --dot-rest: 140 144 151;
    --dot-live: 107 63 212;
}

/* What the script draws on, once it has. Sized by the host rather than by its own attributes,
   which are in device pixels and would otherwise be read as CSS ones. */
.dot-canvas {
    display: block;
    height: 100%;
    width: 100%;
}

/* And with the canvas there, this grid steps aside: the two draw the same dots in the same
   places, and both at once is one grid drawn twice. */
:root.dots-drawn .dot-field::before {
    display: none;
}

/* The grid at rest: grey, and barely there. Anything stronger competes with the words in front
   of it. This is the whole of what somebody without the script sees, and it is meant to look
   deliberate rather than unfinished. */
.dot-field::before {
    background: color-mix(in srgb, var(--ink-faint) 30%, transparent);
    content: "";
    inset: 0;
    position: absolute;

    -webkit-mask-image: radial-gradient(circle at center, #000 1.4px, transparent 1.6px);
    -webkit-mask-size: 26px 26px;
    mask-image: radial-gradient(circle at center, #000 1.4px, transparent 1.6px);
    mask-size: 26px 26px;
}

/* Fixed rather than absolute: on this page the grid belongs to the window, not to the form, and
   on a wide monitor it is the thing either side of the two panels. */
.sign-in-dots {
    position: fixed;
    z-index: 0;
}

/* Both panels above it. Neither is positioned otherwise, and an unpositioned card paints its
   background underneath anything that is - which would leave the card behind the dots. */
.account-split > .account-pitch,
.account-split > .account-column {
    position: relative;
    z-index: 1;
}

.provider-button svg {
    flex: none;
}

/* --- the public frame -------------------------------------------------------

   The header a visitor gets, and the footer under every public page. Both are
   drawn only when nobody is signed in - somebody with an account has all of it
   in the drawer already. */

.public-nav {
    align-items: center;
    display: flex;

    /* Takes the rest of the bar rather than being pushed to its end, so the links start beside
       the lockup and the one control on the header can hold the far edge on its own. */
    flex: 1;
    gap: 0.35rem;
    min-width: 0;
}

.nav-link,
.nav-summary {
    align-items: center;
    border-radius: var(--radius);
    color: var(--ink-soft);
    cursor: pointer;
    display: flex;
    font-size: 0.94rem;
    font-weight: 600;
    gap: 0.3rem;
    list-style: none;
    padding: 0.45rem 0.7rem;
    text-decoration: none;
    transition: background-color 0.12s ease, color 0.12s ease;
}

.nav-link:hover,
.nav-summary:hover {
    background: color-mix(in srgb, var(--ink) 5%, transparent);
    color: var(--ink);
}

.nav-summary::-webkit-details-marker {
    display: none;
}

.nav-menu {
    position: relative;
}

.nav-menu[open] .chevron {
    transform: rotate(180deg);
}

.nav-menu .chevron {
    transition: transform 0.15s ease;
}

.nav-panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    display: flex;
    flex-direction: column;
    left: 0;
    min-width: 13rem;
    padding: 0.4rem;
    position: absolute;
    top: calc(100% + 0.4rem);
    z-index: 20;
}

/* Tighter than a button on the page. The bar is 3.5rem tall and a full-sized
   button in it pushes the brand mark off centre. */
/* Shelli's own lockup, left of everything. The artwork is a wordmark with its own padding,
   so it is sized on height and left to keep its ratio. */
.site-brand {
    align-items: center;
    display: flex;
    flex: none;
    text-decoration: none;
}

.site-brand img {
    display: block;
    height: 30px;
    width: auto;
}

/* Centred on its own text, not just as a box.

   .button is inline-block, so the nav row centred the button while its label sat on a
   baseline the flex-centred links beside it do not share - which reads as the one control on
   the header being a pixel low. A flex box with a matched height puts all four on one line. */
.nav-cta {
    align-items: center;
    display: inline-flex;
    justify-content: center;
    min-height: 2.25rem;
    padding: 0.42rem 1rem;
    white-space: nowrap;
}

/* The one control on the bar sat 11px below the words beside it. .page-header .button adds a
   top margin - right for a heading row where the button hangs off a block of text, wrong inside
   a nav that has already centred everything. Cleared here, and the row is one band again. */
.public-nav .nav-cta {
    align-self: center;

    /* The far edge. Everything before it is the site map and sits where reading starts. */
    margin-left: auto;
    margin-top: 0;
}

/* The links get the same height, so the row is one band rather than four boxes of three
   different heights that happen to share a centre. */
.public-nav .nav-link,
.public-nav .nav-summary {
    min-height: 2.25rem;
}

/* The narrow bar keeps the two buttons and drops the words. A visitor on a
   phone needs the way in, not the site map - the footer carries that. */
@media (max-width: 48rem) {
    .nav-link-quiet,
    .nav-menu {
        display: none;
    }
}

/* The band of dots sits in the space the bottom padding opens up. It is padding rather than a
   margin on the mark above it because the band is positioned against this box, and a margin
   would leave it outside. */
.public-footer {
    border-top: 1px solid var(--line);
    margin-top: 2rem;
    padding: clamp(2.5rem, 5vw, 3.5rem) max(1.5rem, calc((100% - 78rem) / 2)) 8.5rem;
    position: relative;
}

/* Under the last line, never behind it. The dots are the end of the page rather than a texture
   the links have to be read through. */
.footer-dots {
    height: 7rem;
    inset: auto 0 0;
}

.footer-columns {
    display: grid;
    gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}

.footer-column h2 {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin: 0 0 0.75rem;
    text-transform: uppercase;
}

.footer-column a {
    color: var(--ink-soft);
    display: block;
    margin-bottom: 0.45rem;
    text-decoration: none;
}

.footer-column a:hover {
    color: var(--accent);
}

.footer-mark {
    border-top: 1px solid var(--line);
    color: var(--ink-faint);
    font-size: 0.85rem;
    margin: 2rem 0 0;
    padding-top: 1.25rem;
}

/* --- the products, and the pricing page -------------------------------------*/

.products {
    margin-bottom: var(--landing-gap);
    padding-inline: max(1.5rem, calc((100% - 78rem) / 2));
}

.product-grid {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
}

.product-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    padding: 1.75rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.product-card:hover {
    border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
    box-shadow: var(--shadow);
    transform: translateY(-2px);
}

.product-card h3 {
    font-size: 1.3rem;
    margin: 0 0 0.25rem;
}

.product-for {
    color: var(--accent);
    font-size: 0.88rem;
    font-weight: 600;
    margin: 0 0 0.9rem;
}

.product-card p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0 0 1rem;
}

.product-from {
    color: var(--ink);
    font-weight: 700;
    margin-top: auto;
}

.product-card .button {
    margin-top: 0.75rem;
    text-align: center;
}

.pricing-head {
    padding: clamp(2rem, 5vw, 3.25rem) max(1.5rem, calc((100% - 78rem) / 2)) 0;
    text-align: center;
}

.pricing-head h1 {
    font-size: clamp(1.9rem, 4vw, 2.75rem);
    margin: 0 0 0.5rem;
}

/* Two products, one of which is the page you are on. A pair of tabs rather
   than a dropdown: with two choices a menu is a click that hides one word. */
.pricing-switch {
    display: flex;
    gap: 0.4rem;
    justify-content: center;
    margin: 1.5rem 0 0;
}

.switch-link {
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--ink-soft);
    font-weight: 600;
    padding: 0.4rem 1.1rem;
    text-decoration: none;
}

.switch-current {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.compare {
    margin-bottom: var(--landing-gap);
    padding-inline: max(1.5rem, calc((100% - 78rem) / 2));
}

/* A line of the comparison is a sentence, not a column heading, and it has to say so loudly
   because `.data-table th` does not know the difference. That rule is written for the row across
   the top - 0.72rem, faint, uppercase, wide-tracked - and every name here is marked up as `th`
   for the screen reader rather than for the look of it.

   It survived while the table held nine two-word capabilities: small caps read as a style. It
   stopped surviving the moment the lines grew a sentence underneath, because the half that
   actually sells was rendering as forty lines of tiny faint shouting. */
.compare-table th[scope="row"] {
    color: var(--ink);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: normal;
    text-align: left;
    text-transform: none;
}

.compare-about {
    color: var(--ink-soft);
    display: block;
    font-size: 0.85rem;
    font-weight: 400;
    letter-spacing: normal;
    margin-top: 0.2rem;
    max-width: 46ch;
    text-transform: none;
}

.compare-table td {
    text-align: center;
}

.compare-yes {
    color: var(--good);
    font-size: 1.1rem;
    font-weight: 700;
}

.compare-no {
    color: var(--ink-faint);
}

/* --- the price list ---------------------------------------------------------

   The cards themselves are the portal's, from nexus.css, so what follows is only what a shop
   window needs and a shop's own page does not: room for three of them, a term switch, and the
   trial. */

.pricing-plans {
    padding-inline: max(1.5rem, calc((100% - 78rem) / 2));

    /* Room under the band. On a market with no published prices the cards are short, and without
       this the tinted panel stopped a few pixels under the last line of them - which reads as the
       section having been cut off rather than as it having finished. */
    padding-bottom: clamp(2rem, 4vw, 3.25rem);
}

/* Wider than the portal's minimum. Inside a shop the cards sit in a column beside the
   navigation; here they have the page, and three 230px cards on a 78rem field read as a row of
   receipts. */
.pricing-plans .offer-grid {
    gap: 1.25rem;

    /* Three across before the cards get narrow enough to read as receipts, and two below that
       rather than three cramped ones. 20rem is the width at which a plan card still holds its
       price on one line with the feature list underneath it legible. */

    /* One row of equal rows. Grid stretches items to the tallest by default, but only within
       a row it has decided on - and a card that wrapped onto a second row was sized against
       whatever landed beside it rather than against the plan everybody is comparing to. */
    grid-auto-rows: 1fr;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    margin-bottom: 1.5rem;
}

/* Roomier than the portal's. Inside an account these sit beside a navigation drawer and are
   read by somebody who has already bought; here they are the page, and the whole decision is
   made off them. */
.pricing-plans .offer {
    padding: 2rem 1.6rem;
}

.pricing-plans .offer-name {
    font-size: 1.35rem;
}

.pricing-plans .offer-price {
    font-size: 2.1rem;
}

/* margin-top auto, so every button in a row sits on one line however much wording the card
   above it carries. */
.pricing-plans .offer .button {
    margin-top: auto;
    text-align: center;
}

/* The trial is a different offer, not a cheaper plan. Marked rather than coloured: a filled card
   beside two plain ones reads as the recommended one, which is the opposite of what it is.

   It keeps the surface. Transparent was fine on a white page and wrong the moment the plans got
   a tinted band behind them - the card stopped having a face and read as an empty slot between
   two real ones. The dashed edge is the whole of the mark. */
.offer-trial {
    background: var(--surface);
    border-style: dashed;
    border-width: 2px;
}

/* The plan's own headline facts - how many tills, how many more. Darker than the feature lists
   under them, because this is what the price buys rather than what the shop can do with it. */
.offer-plain li {
    color: var(--ink);
    justify-content: flex-start;
}

/* Month or year. The same pair of tabs the products wear above, so a reader meets one control
   twice rather than two controls once. */
/* Which price list. Above the term switch, because it decides the currency the term is then
   counted in, and a reader who meets the months before the money has to go back up. Quieter than
   the product tabs: it answers a question most readers never ask, and only the one who is in the
   wrong currency is looking for it. */
/* Space under it as well as over it.

   The gap below these switches was on .term-switch alone, and that one is only drawn where a
   market publishes a yearly price. On the worldwide list, which publishes none, the price-list
   tabs sat directly on top of the Basic card with nothing between them. Adjacent margins
   collapse, so putting it here costs nothing on a market that draws both. */
.market-switch {
    flex-wrap: wrap;
    margin-bottom: 2.25rem;
    margin-top: 1.75rem;
}

.market-switch .switch-link {
    font-size: 0.9rem;
    font-weight: 500;
    padding: 0.3rem 0.9rem;
}

/* The currency beside the country, because "Colombia" and "COP" answer different halves of the
   same question: a reader recognises the country and checks the currency. */
.switch-currency {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    margin-left: 0.35rem;
    opacity: 0.65;
}

.term-switch {
    margin-bottom: 2.25rem;
}

/* The reason to press it, on the tab itself. A discount announced only after the tab is chosen
   is a discount most readers never find. */
.switch-flag {
    background: var(--good);
    border-radius: 999px;
    color: #ffffff;
    font-size: 0.7rem;
    font-weight: 700;
    margin-left: 0.4rem;
    padding: 0.1rem 0.45rem;
    white-space: nowrap;
}

.switch-current .switch-flag {
    background: #ffffff;
    color: var(--accent-dark);
}

/* --- what each plan includes ------------------------------------------------

   The table in a card, bordered in the same purple the recommended plan wears. It is the thing
   this page exists for and it was the flattest block on it: a grid of ticks with no edge, under
   two rows of coloured cards. */

.compare-card {
    background: var(--surface);
    border: 2px solid var(--accent);
    border-radius: calc(var(--radius) * 1.75);

    /* Tinted with the border's own colour rather than black, so the card reads as lit from the
       accent instead of merely raised. */
    box-shadow: 0 20px 45px -30px color-mix(in srgb, var(--accent) 75%, transparent);
    padding: 1.5rem;
}

/* The recommended plan's column, tinted down its whole length. Without it the header cell was
   the only thing marking which column a reader had come to look at, and it scrolls away. */
.compare-featured {
    background: color-mix(in srgb, var(--accent) 7%, transparent);
}

.compare-table th[scope="col"] {
    font-size: 0.95rem;
    text-align: center;
}

/* Except the first, which heads the column of capability names rather than a column of ticks.
   Centred, it floated over left-aligned rows with no relationship to any of them. */
.compare-table th[scope="col"]:first-child {
    text-align: left;
}

.compare-table th[scope="col"].compare-featured {
    color: var(--accent-dark);
}

/* Bought on top rather than included, and it must not look like either a tick or a dash. Worded
   and boxed, because "Extra" set as plain text in a column of symbols reads as a missing icon. */
.compare-extra {
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    border-radius: 999px;
    color: var(--accent-dark);
    display: inline-block;
    font-size: 0.75rem;
    font-weight: 700;
    padding: 0.1rem 0.55rem;
    white-space: nowrap;
}

/* A heading across the table, opening each group.

   The comparison is forty rows now that it can say what both plans do, and read as one
   undifferentiated list a shopkeeper looking for the counter had to scan past the reports to
   find it. Full width and sticky-looking rather than boxed: it is a divider, not a row with an
   answer. */
.compare-group th {
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    border-radius: var(--radius);
    color: var(--accent-dark);
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    padding: 0.6rem 0.75rem;
    text-align: left;
    text-transform: uppercase;
}

/* The first group heading sits directly under the column titles, where the border above it
   would read as a rule the header already draws. */
.compare-table tbody:first-of-type .compare-group th {
    padding-top: 0.35rem;
}

/* A count or a phrase instead of a tick - "one per licence", "up to 3". Set as words rather
   than as a symbol, because it is the half of the table that stops the cheaper plan reading as
   poor: a column of ticks and dashes says Basic lacks things, and a number says Basic is a
   size. */
.compare-count {
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Inside the card, so it is left with the table rather than centred under the page. */
.compare-note {
    margin-top: 1.25rem;
    max-width: 60ch;
    text-align: left;
}

/* --- what the page is arguing ------------------------------------------------

   Everything below carries the same claim in a different form: a business should not have to
   leave when it grows. Three stages, four rungs, one diagram. */

.hero-trust {
    color: var(--ink-faint);
    font-size: 0.82rem;
    letter-spacing: 0.04em;
    margin: 1.25rem 0 0;
    text-transform: uppercase;
}

.grow-list {
    counter-reset: none;
    display: grid;
    gap: 1.5rem;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    list-style: none;
    margin: 0;
    padding: 0;
}

.grow-stage h3 {
    font-size: 1.1rem;
    margin: 0 0 0.4rem;
}

.grow-stage p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0;
}

/* The bar is the argument, and it is why these are three cards rather than three paragraphs:
   the mark gets longer before anybody has read a word. */
.grow-bar {
    background: linear-gradient(90deg, var(--accent), var(--accent-bright));
    border-radius: 999px;
    display: block;
    height: 5px;
    margin-bottom: 1rem;
}

.grow-bar-1 { width: 30%; }
.grow-bar-2 { width: 65%; }
.grow-bar-3 { width: 100%; }

/* --- one shop to a chain ---------------------------------------------------- */

.rungs {
    counter-reset: none;
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    list-style: none;
    margin: 0;
    padding: 0;
}

.rung {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: calc(var(--radius) * 1.5);
    padding: 1.4rem 1.25rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.rung:hover {
    border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
    box-shadow: var(--shadow);
    transform: translateY(-2px);
}

.rung h3 {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    font-size: 1.05rem;
    gap: 0.5rem;
    margin: 0 0 0.4rem;
}

.rung p {
    color: var(--ink-soft);
    font-size: 0.92rem;
    line-height: 1.6;
    margin: 0;
}

/* A gauge rather than a number. The rungs are not steps somebody takes in order - most
   businesses stop at the second - so a numbered list would read as a course to complete. */
.rung-mark {
    background: color-mix(in srgb, var(--ink) 10%, transparent);
    border-radius: 999px;
    display: block;
    height: 5px;
    margin-bottom: 1rem;
    overflow: hidden;
}

.rung-fill {
    background: linear-gradient(90deg, var(--accent), var(--accent-bright));
    border-radius: 999px;
    display: block;
    height: 100%;
}

.rung-fill-1 { width: 25%; }
.rung-fill-2 { width: 50%; }
.rung-fill-3 { width: 75%; }
.rung-fill-4 { width: 100%; }

/* --- the ecosystem -----------------------------------------------------------

   A three by three grid with the hub in the middle cell. Built from the page's own elements
   rather than drawn in SVG, because the labels are translated and SVG text does not wrap. */

.eco {
    display: grid;
    gap: 0.85rem;
    grid-template-areas:
        "nw n  ne"
        "w  hub e"
        "sw s  se";
    grid-template-columns: repeat(3, 1fr);
}

.eco-hub {
    align-items: center;
    background: linear-gradient(160deg, var(--accent) 0%, var(--accent-dark) 100%);
    border-radius: calc(var(--radius) * 1.5);
    color: #ffffff;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    grid-area: hub;
    justify-content: center;
    padding: 1.75rem 1.25rem;
    text-align: center;
}

.eco-hub-name {
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.eco-hub-note {
    color: color-mix(in srgb, #ffffff 78%, transparent);
    font-size: 0.85rem;
}

.eco-node {
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: calc(var(--radius) * 1.5);
    display: flex;
    flex-direction: column;
    font-size: 0.92rem;
    font-weight: 600;
    gap: 0.35rem;
    justify-content: center;
    min-height: 4.5rem;
    padding: 1rem;
    text-align: center;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

.eco-node:hover {
    border-color: var(--accent-bright);
    transform: translateY(-2px);
}

.eco-nw { grid-area: nw; }
.eco-n  { grid-area: n; }
.eco-ne { grid-area: ne; }
.eco-w  { grid-area: w; }
.eco-e  { grid-area: e; }
.eco-sw { grid-area: sw; }
.eco-s  { grid-area: s; }
.eco-se { grid-area: se; }

/* The two products either side of the middle, marked as the two things a customer actually
   chooses between. Everything else on this diagram is something they add to one of them. */
.eco-n,
.eco-ne {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

/* Below the grid it is a list, and the hub reads first - which is the reading order the
   markup already has, so nothing needs reordering. */
@media (max-width: 47rem) {
    .eco {
        grid-template-areas: none;
        grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    }

    .eco-hub {
        grid-column: 1 / -1;
    }

    .eco-nw, .eco-n, .eco-ne, .eco-w, .eco-e, .eco-sw, .eco-s, .eco-se {
        grid-area: auto;
    }
}

/* --- the portal, and the one claim worth being careful with ------------------ */

.nexus-grid {
    display: grid;
    gap: 1.5rem;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    margin-bottom: 1.75rem;
}

.nexus-point h3 {
    font-size: 1.05rem;
    margin: 0 0 0.4rem;
}

.nexus-point p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0;
}

/* Set apart from the three points above it, because it is the answer to the question those
   three provoke: if the reports are here, where is my business? */
.privacy-note {
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 3px solid var(--good);
    border-radius: var(--radius);
    padding: 1.25rem 1.5rem;
}

.privacy-note h3 {
    font-size: 1rem;
    margin: 0 0 0.4rem;
}

.privacy-note p {
    color: var(--ink-soft);
    line-height: 1.6;
    margin: 0;
    max-width: 68ch;
}

/* --- bought on top ---------------------------------------------------------- */

.module-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    margin-bottom: 1.25rem;
}

.module {
    background: var(--surface);
    border: 1px dashed var(--line);
    border-radius: calc(var(--radius) * 1.5);
    padding: 1.25rem;
}

.module h3 {
    font-size: 1.02rem;
    margin: 0 0 0.35rem;
}

.module p {
    color: var(--ink-soft);
    font-size: 0.92rem;
    line-height: 1.55;
    margin: 0;
}

/* --- the chat panel ---------------------------------------------------------

   Fixed to the bottom right, over everything, on the public pages only. The number and the
   opening message come from Admin, so the panel exists on the page exactly when somebody has
   set one - there is no dead state to style.

   Green rather than the brand purple: this is the one control on the page that leaves for
   another application, and every reader already knows what that green means. It is the only
   place on the site that borrows a colour it did not choose.

   It opens on a checkbox and shuts on the same one, which is why there is no script behind any
   of this. The order in the markup is the order the rules need - the input first, then what it
   switches - because a sibling combinator only ever looks forward. */

.chat {
    bottom: 1.5rem;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.85rem;
    position: fixed;
    right: 1.5rem;

    /* Above the page and below a dialog. A chat button floating over a confirmation is a
       button covering the thing somebody is being asked to confirm. */
    z-index: 40;
}

/* The switch itself never draws. `.visually-hidden` in the markup takes it out of the layout
   without taking it out of the tab order, so this stays operable from a keyboard. */
.chat-toggle {
    position: absolute;
}

/* --- the button ----------------------------------------------------------- */

.chat-fab {
    align-items: center;
    background: var(--wa-green);
    border-radius: 50%;
    box-shadow: 0 10px 30px -8px rgb(20 23 28 / 45%);
    color: #ffffff;
    cursor: pointer;
    display: inline-flex;
    height: 3.6rem;
    justify-content: center;
    order: 2;

    /* So the ring below has something to sit behind. */
    position: relative;

    transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
    width: 3.6rem;
}

.chat-fab:hover {
    box-shadow: 0 14px 34px -8px rgb(20 23 28 / 55%);
    transform: translateY(-2px) scale(1.04);
}

/* The focus ring belongs to the checkbox and has to be drawn on what stands in for it. */
.chat-toggle:focus-visible + .chat-panel + .chat-fab {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* Shut, the mark. Open, the cross. One or the other, never both. */
.chat-fab-close,
.chat-toggle:checked + .chat-panel + .chat-fab .chat-fab-open {
    display: none;
}

.chat-toggle:checked + .chat-panel + .chat-fab {
    background: var(--ink);
}

.chat-toggle:checked + .chat-panel + .chat-fab .chat-fab-close {
    display: block;
}

/* A ring that leaves the button and fades, once every few seconds, so a reader who has scrolled
   past four sections still notices there is somebody to ask. Drawn on a pseudo-element rather
   than the button, so the button's own hover is not fighting it. */
.chat-fab::after {
    animation: chat-pulse 3.4s ease-out infinite;
    background: var(--wa-green);
    border-radius: 50%;
    content: "";
    inset: 0;
    position: absolute;
    z-index: -1;
}

@keyframes chat-pulse {
    0% { opacity: 0.55; transform: scale(1); }
    70% { opacity: 0; transform: scale(1.75); }
    100% { opacity: 0; transform: scale(1.75); }
}

/* Nothing pulses once the panel is open: the reader has already answered it. */
.chat-toggle:checked + .chat-panel + .chat-fab::after {
    animation: none;
    opacity: 0;
}

/* --- the panel ------------------------------------------------------------ */

.chat-panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 1.1rem;
    box-shadow: 0 24px 60px -20px rgb(20 23 28 / 45%);
    display: none;
    order: 1;
    overflow: hidden;
    width: min(21rem, calc(100vw - 3rem));
}

.chat-toggle:checked + .chat-panel {
    animation: chat-open 0.18s ease-out;
    display: block;
}

@keyframes chat-open {
    from { opacity: 0; transform: translateY(0.75rem); }
    to { opacity: 1; transform: translateY(0); }
}

.chat-panel-head {
    align-items: center;
    background: var(--wa-green);
    color: #ffffff;
    display: flex;
    gap: 0.7rem;
    padding: 0.95rem 1.1rem;
}

.chat-panel-mark {
    align-items: center;
    background: rgb(255 255 255 / 22%);
    border-radius: 50%;
    display: inline-flex;
    flex: none;
    height: 2.4rem;
    justify-content: center;
    width: 2.4rem;
}

.chat-panel-words {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

.chat-panel-words strong {
    font-size: 1rem;
}

.chat-panel-words span {
    font-size: 0.82rem;
    opacity: 0.92;
}

.chat-form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 1rem;
}

.chat-form input,
.chat-form textarea {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 0.6rem;
    color: var(--ink);
    font: inherit;
    font-size: 0.9rem;
    padding: 0.6rem 0.7rem;
    width: 100%;
}

.chat-form textarea {
    min-height: 5rem;
    resize: vertical;
}

.chat-form input:focus-visible,
.chat-form textarea:focus-visible {
    border-color: var(--wa-green);
    outline: 2px solid var(--wa-green);
    outline-offset: 1px;
}

/* Green, not the brand purple, for the same reason the button is: it is the control that
   leaves. `.button` gives it the shape and this gives it the colour.

   `display: flex` is the load-bearing line. `.button` is inline-block, so `gap` and
   `justify-content` are inert on it and a `display: block` mark inside becomes a box of its
   own - which is what put WhatsApp's glyph on a line above its own label, hard against the
   left edge, while the words sat centred underneath. The Google button beside the sign-in
   form has said this for months; this is the same three lines. */
.chat-send {
    align-items: center;
    background: var(--wa-green);
    border-color: var(--wa-green);
    color: #ffffff;
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    margin-top: 0.2rem;
    padding-block: 0.62rem;
    width: 100%;
}

.chat-send:hover {
    background: var(--wa-green-dark);
    border-color: var(--wa-green-dark);
    color: #ffffff;
}

/* The panel fills the width it is given on a phone and the button sits under it, which is the
   only arrangement where both fit over a page somebody is reading. */
@media (max-width: 40rem) {
    .chat {
        bottom: 1rem;
        left: 1rem;
        right: 1rem;
    }

    .chat-panel {
        width: 100%;
    }
}

/* Somebody who asked for less movement gets a button that does not lift, and no ring at all. */
@media (prefers-reduced-motion: reduce) {
    .chat-fab,
    .chat-panel {
        transition: none;
    }

    .chat-fab::after,
    .chat-toggle:checked + .chat-panel {
        animation: none;
    }

    .chat-fab:hover {
        transform: none;
    }
}

/* --- The tools ---------------------------------------------------------------- */

/* A colour for each tool, in one block, spelled with the key that names everything else
   about it. Anything wearing `.tool-tint-<key>` - a card, a page head, the drawing itself -
   takes that colour, and a tool added without a line here draws in the ink grey rather than
   drawing nothing, which is the failure that would ship unnoticed.

   Borrowed from nowhere: five hues far enough apart to be told apart at the size of a card,
   and each dark enough to carry a line drawing on white. WhatsApp's is WhatsApp's, because
   that tool builds a link into WhatsApp and a purple one would be a drawing of the wrong
   application. */
.tool-tint-CheckDigit {
    --tool-tint: #6d4aff;
}

.tool-tint-Tax {
    --tool-tint: #0f9d6b;
}

.tool-tint-Margin {
    --tool-tint: #d97008;
}

.tool-tint-Discount {
    --tool-tint: #d8385a;
}

.tool-tint-WhatsApp {
    --tool-tint: var(--wa-green);
}

/* The drawing, on a disc of its own colour. The fallback is what a tool with no tint gets:
   grey, visible, and obviously not finished. */
.tool-glyph {
    align-items: center;
    background: color-mix(in srgb, var(--tool-tint, var(--ink-faint)) 12%, transparent);
    border-radius: 30%;
    color: var(--tool-tint, var(--ink-faint));
    display: inline-flex;
    flex: none;
    height: 3.4rem;
    justify-content: center;
    transition: transform 0.2s ease, background 0.2s ease;
    width: 3.4rem;
}

/* --- the list ------------------------------------------------------------- */

.tool-cards {
    margin-top: 2.5rem;
}

/* The drawing above the name rather than beside it: the names are two and three words long
   in Spanish and a row that wraps under its own icon reads as a card that broke. */
.tool-card {
    align-items: flex-start;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.tool-card h3,
.tool-card p {
    margin: 0;
}

.tool-card:hover,
.tool-card:focus-visible {
    border-color: color-mix(in srgb, var(--tool-tint, var(--accent)) 45%, var(--line));
    box-shadow: 0 16px 34px -18px color-mix(
        in srgb, var(--tool-tint, var(--accent)) 70%, transparent);
    transform: translateY(-3px);
}

/* The drawing answers the hover as well as the card does. Small: a glyph that jumps is a
   glyph somebody notices instead of reading the name under it. */
.tool-card:hover .tool-glyph,
.tool-card:focus-visible .tool-glyph {
    background: color-mix(in srgb, var(--tool-tint, var(--ink-faint)) 20%, transparent);
    transform: scale(1.08) rotate(-4deg);
}

/* --- one tool ------------------------------------------------------------- */

/* Carries the tint and nothing else. No box, so the sections it wraps stay where the page
   column put them - and the property still inherits, which is the whole reason it is here. */
.tool-page {
    display: contents;
}

/* Centred like the pricing head. A glow rather than a band: the head sits inside the ordinary
   page column, and a rectangle of colour that stops at the column edge reads as a block
   somebody forgot to make full width. This fades out before it gets there. */
.tool-head {
    background: radial-gradient(
        70% 130% at 50% 0%,
        color-mix(in srgb, var(--tool-tint, var(--accent)) 15%, transparent) 0%,
        transparent 72%);
    padding-bottom: 1.25rem;
    padding-top: clamp(1.5rem, 4vw, 2.75rem);
    text-align: center;
}

/* The drawing sits between the way back and the title, so the page opens the same colour as
   the card it was reached from. */
.tool-head .tool-glyph {
    height: 4rem;
    margin-bottom: 0.9rem;
    width: 4rem;
}

/* A tool is one short form and its answer, and `.card-wide` is the full width of the page
   column - so every calculator drew a card with its fields down the left and half a metre of
   nothing to the right of them. Narrowed to what a form is, and centred under a heading that
   was already centred.

   On the tool pages only. `.card-wide` elsewhere holds tables and grids that want the room. */
.tool-page .card-wide {
    margin-inline: auto;
    max-width: 40rem;
}

.tool-head h1 {
    font-size: clamp(1.6rem, 3.5vw, 2.25rem);
    margin: 0 0 0.5rem;
}

.tool-back {
    margin: 0 0 0.75rem;
}

.tool-back a {
    color: var(--ink-faint);
    font-size: 0.9rem;
    text-decoration: none;
}

.tool-back a:hover,
.tool-back a:focus-visible {
    color: var(--accent);
    text-decoration: underline;
}

/* The answer, marked off from the form above it. A rule rather than a second card:
   the numbers belong to the form that produced them, and a card of their own would
   read as a separate thing that happened to appear. */
.tool-answer {
    border-top: 1px solid var(--line);
    margin-top: 1.5rem;
    padding-top: 1.25rem;
}

/* dl overrides sit here because .card dl is in nexus.css, which loads first. Stated
   above it they would lose to it and the grid would stay two narrow columns. */
dl.tool-answer {
    display: grid;
    gap: 0.6rem 1.5rem;
    grid-template-columns: 1fr auto;
    margin: 1.5rem 0 0;
}

dl.tool-answer dt {
    align-self: baseline;
}

dl.tool-answer dd {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    text-align: right;
}

/* The one figure somebody came for, in the colour of the tool that worked it out. */
dl.tool-answer .tool-figure {
    color: var(--tool-tint, var(--accent));
    font-size: 1.35rem;
}

/* The answer arrives rather than appearing, because on a page that reloads to compute there
   is otherwise nothing to say the numbers below are new ones. */
.tool-answer {
    animation: tool-answer-in 0.25s ease-out;
}

@keyframes tool-answer-in {
    from { opacity: 0; transform: translateY(0.4rem); }
    to { opacity: 1; transform: translateY(0); }
}

/* Nothing moves for a reader who asked for less of it. The card still marks itself out on
   hover - that is the border and the shadow, and both stay. */
@media (prefers-reduced-motion: reduce) {
    .tool-card,
    .tool-glyph {
        transition: none;
    }

    .tool-answer {
        animation: none;
    }

    .tool-card:hover,
    .tool-card:focus-visible {
        transform: none;
    }

    .tool-card:hover .tool-glyph,
    .tool-card:focus-visible .tool-glyph {
        transform: none;
    }
}

/* Quieter than the card above it: this is the explanation, and it is under the tool
   because somebody arrives wanting the answer rather than the method. */
.tool-about {
    color: var(--ink-soft);
    font-size: 0.92rem;
    line-height: 1.65;
}

.tool-about h2 {
    font-size: 0.95rem;
    margin: 0 0 0.75rem;
}

.tool-about p {
    margin: 0 0 0.75rem;
}

.tool-about p:last-child {
    margin-bottom: 0;
}

/* The link that gives way when the header runs out of room.

   Measured, not guessed, and measured in Spanish - which is the measurement that decides
   it. In English the header fits: "Tools" is 54px and the row comes to 375px exactly. In
   Spanish "Herramientas" is 111px and "Iniciar sesion" is 122px, and the same row is 404px
   on a 375px phone, so the whole page scrolls sideways. Checking this in English alone
   would have shipped it broken for almost every reader the site has.

   The footer lists every tool by name, so the link is what gives way rather than the
   button somebody came to press. */
@media (max-width: 40rem) {
    .public-nav .nav-link-extra {
        display: none;
    }
}

/* One name in the header, not two.

   .topbar .brand is off by default and switched on under 860px, because down there the
   drawer has slid off-canvas and taken the lockup with it. A visitor has no drawer at all
   - it is only rendered for somebody signed in - and already carries Shelli's own lockup
   in .site-brand, so under 860px they were getting both: the Nexus "N" mark and the Shelli
   wordmark beside it, on the one screen that is meant to introduce one product.

   Not scoped to a width. Whenever the header carries Shelli's lockup the Nexus mark is
   redundant, and this rule outranks the one inside the media query on specificity rather
   than on order, so it cannot be undone by somebody moving the breakpoint. */
.topbar:has(.site-brand) .brand {
    display: none;
}

/* The outcome of a payment attempt.

   Centred and large because this is the one page a payer reaches from somebody else's site,
   usually on a phone, wanting one thing: did it work. A heading and a paragraph make that
   something to read; a mark makes it something to see from arm's length.

   The glyph is a stroke over currentColor, so one tone class colours the ring, the drawing
   and the pill together. */
.checkout-outcome {
    text-align: center;
}

.outcome-mark {
    width: 76px;
    height: 76px;
    margin: 0.25rem auto 1.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.outcome-good {
    background: var(--good-soft);
    color: var(--good);
}

.outcome-warn {
    background: var(--warn-soft);
    color: var(--warn);
}

.outcome-bad {
    background: var(--bad-soft);
    color: var(--bad);
}

.outcome-muted {
    background: var(--paper);
    color: var(--ink-faint);
}

/* Which rail took the money. Quieter than the sentence above it and louder than a footnote:
   it is the line somebody comes back for when they are matching a statement. */
.outcome-rail {
    color: var(--ink-soft);
    margin: 0.35rem 0 0.9rem;
}

/* The buttons follow the text rather than sitting hard left under a centred column. */
.checkout-outcome .offer-foot {
    justify-content: center;
}

/* The moment between choosing a rail and the gateway's own page.

   A spinner rather than a button. The page leaves on its own within a second, and a button
   sitting there in the meantime is a thing to press that then gets pressed by somebody else -
   which is how a payer ends up unsure whether they did that or we did.

   One element, drawn in CSS. No script, because this page must still hand somebody over with
   scripting switched off, and no image, because a spinner that has to be fetched is blank for
   exactly as long as the page exists. */
.leaving {
    text-align: center;
}

.leaving-spinner {
    width: 34px;
    height: 34px;
    margin: 0.5rem auto 1rem;
    border-radius: 50%;
    border: 3px solid var(--line);
    border-top-color: var(--accent);
    animation: leaving-turn 0.7s linear infinite;
}

.leaving-said {
    margin: 0 0 0.35rem;
    font-weight: 600;
}

@keyframes leaving-turn {
    to {
        transform: rotate(360deg);
    }
}

/* Somebody who asked not to be moved is not moved. The wait is the same and the page still
   leaves; it just stops turning, which is what the setting is for. */
@media (prefers-reduced-motion: reduce) {
    .leaving-spinner {
        animation: none;
    }
}

/* The figure somebody is about to pay, at the foot of the cart.

   It was the same size as the quantity column above it. This is the one number on the page
   that decides whether they press the button, and it should be findable without reading. */
.cart-total th {
    font-size: 1.15rem;
    padding-top: 1rem;
}

.cart-total .cart-total-figure {
    font-size: 1.35rem;
    white-space: nowrap;
}

/* --- connecting a device: only the fields the chosen option uses ---------------
   Every field on the pairing form is rendered whichever option is selected, because an
   unrendered one posts nothing and binds to its default - a shop opened with no name. Rendered
   is not the same as shown, though, and the page used to conflate them: somebody connecting a
   device to a shop they already had was asked to name a new one and give it an address.

   So the sets are hidden rather than left out, and a hidden field still posts. The route reads
   only the fields the chosen option names, so what the others carry is never looked at.

   :has on the form, not a class on a parent, because nothing here runs script - the radio is the
   only thing that knows which option is live. Without :has every set is visible, which is the
   behaviour this replaced: confusing, and complete. */
.account-form:not(:has(input[name="Approval.Choice"][value="new-store"]:checked,
                       input[name="Approval.Choice"][value="new-business"]:checked))
    .proposal-store,

/* Which business it goes under. Only when opening a store on a business that already exists -
   opening a business creates the one it belongs to. */
.account-form:not(:has(input[name="Approval.Choice"][value="new-store"]:checked))
    .proposal-owner,

.account-form:not(:has(input[name="Approval.Choice"][value="new-business"]:checked))
    .proposal-business {
    display: none;
}

/* What has to be filled in before this can connect. Only ever beside a field the route refuses
   without, so it is a fact rather than decoration. */
.required-mark {
    color: var(--danger, #b3261e);
    margin-left: 0.15rem;
}
