/*
 * Global Web Concepts-authored glue CSS for the Nebulous template — NOT
 * part of the purchased vendor package (that lives in vendor/). Two
 * jobs: (1) style the Global Web Concepts-specific chrome Nebulous's own
 * CSS doesn't know about (utility bar, sidebar socials, nav-cta,
 * hero slide content, footer extras), and (2) re-skin the EXISTING
 * generic storefront partials (pricing-cards.php, marketing-
 * sections.php, category-tiles, cart/checkout/account forms) using
 * Nebulous's own real CSS variables instead of app.css's --hca-*
 * tokens — app.css is never loaded on a Nebulous-active page (see
 * NebulousTemplate::headHtml()), so every one of those generic
 * classes needs a real definition here rather than silently
 * inheriting nothing.
 */

* {
    box-sizing: border-box;
}

body.template-nebulous {
    margin: 0;
    background: var(--background-body);
    color: var(--color-body);
    font-family: var(--font-body);
    /* 16px, literal, and NOT var(--text-size).
       Tried that to make the Layout tab's "Text size" field work, and
       it blew the whole storefront up to 25.6px on the spot. The
       vendor's --text-size is 1.6em, sized against layout.css's
       `body { font-size: 62.5% }` — the old 10px-root trick — so 1.6em
       means 16px THERE and 25.6px here, where the parent is the 16px
       html element. The token is not a length this rule can borrow.
       Site Whiz drives body text through its own declaration instead;
       see the layout block in header.php. */
    font-size: 16px;
    line-height: var(--text-lineheight);
    /* 100vw (used by the full-bleed .banner/.category-hero-banner
       breakout below) includes the vertical scrollbar's own gutter
       width, which is narrower than the visible viewport whenever a
       scrollbar is actually showing — without this, that few-pixel
       gap becomes real, scrollable horizontal overflow on the whole
       page (found live 2026-08-16 via document.documentElement.scrollWidth
       > clientWidth after the full-bleed fix shipped). */
    overflow-x: hidden;
}

.headermain-logo-image img { max-height: 60px; width: auto; display: block; }

body.template-nebulous h1, body.template-nebulous h2, body.template-nebulous h3, body.template-nebulous h4 {
    font-family: var(--font-heading);
    color: var(--color-heading);
    text-wrap: balance;
}

body.template-nebulous a {
    color: inherit;
}

body.template-nebulous label {
    display: block;
    margin-bottom: 1rem;
    color: var(--color-body);
}

/* CHECKBOXES AND RADIOS ARE EXCLUDED, and that exclusion is the fix
   for a bug found in three separate places.

   This rule is right for a text field and wrong for a tick box. A
   block-level, 100%-wide checkbox fills its container, renders its
   glyph centred in all that width, and pushes its own label onto the
   next line. It had already been patched locally at .auth-trust-check
   and .configure-addon before anyone noticed it was one rule rather
   than several bugs; the cart's ID Protect box was measured at 729px
   wide, and every product page's billing-cycle radio at 542px —
   absolutely positioned and stretched across the whole tile, so the
   dot sat in the middle of the card instead of its corner.

   A sitewide sweep found eight of these controls on the storefront.
   Three were already protected by local overrides, one by being
   deliberately hidden at 1px; the remaining three had nothing. Fixing
   the source rather than adding a fourth, fifth and sixth patch also
   means the next checkbox anybody writes is correct by default.

   The existing overrides are left in place: they are now redundant but
   harmless, and removing them is a separate change with its own risk.

   Padding, border and background go with it. A native tick box wants
   none of them.

   :where() AND NOT :not(), and the difference broke the search box.
   :not() carries the specificity of its own argument, so
   input:not([type=checkbox]):not([type=radio]) scores (0,3,2) where a
   bare `input` scores (0,1,2). That raised this rule above vendor rules
   that had overridden it for as long as they have existed — including
   .domainchecker .domainsearch-form input[type="text"] { width: 74% }
   at (0,3,1). Both fields snapped to 100% and the Search button
   dropped onto its own line on the home page and domain-search.php.
   :where() contributes ZERO specificity, so this scores (0,1,2)
   exactly as the original did, and every existing override keeps
   winning. */
body.template-nebulous input:where(:not([type="checkbox"]):not([type="radio"])),
body.template-nebulous textarea,
body.template-nebulous select {
    display: block;
    width: 100%;
    margin-top: 0.25rem;
    padding: 0.6rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-border);
    border-radius: var(--border-radius-default);
    color: var(--color-body);
    font-family: inherit;
}

body.template-nebulous button,
body.template-nebulous a.button,
body.template-nebulous .checkout-button {
    display: inline-block;
    padding: 0.6rem 1.25rem;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    border: none;
    border-radius: var(--border-radius-default);
    text-decoration: none;
    cursor: pointer;
    font-weight: 600;
}

body.template-nebulous button:hover,
body.template-nebulous a.button:hover,
body.template-nebulous .checkout-button:hover {
    background: var(--color-primary-active-background);
}

body.template-nebulous .price {
    font-weight: 700;
    color: var(--color-primary-background);
}

/* ---- Header/nav/toolbar/sidebar chrome now uses Nebulous's OWN real
   class names (.toolbarmain-container, .headermain-logo-text,
   .navmain-link-heading/-text, .social-sidewidget, etc. — found by
   reading the genuine WizardPanel package's plain includes/*.tpl
   files, not guessed) so vendor/css/layout.css styles almost all of
   it automatically — this file only needs to fill the few real gaps
   below, not reimplement chrome layout.css already owns. ---- */

.nav-logout-form { margin: 0; display: inline; }
.nav-logout-form button { background: none; border: none; padding: 0; font: inherit; cursor: pointer; }

/* .navmain > ul is display:flex with the browser default
   align-items:stretch — every <li> stretches to the tallest sibling's
   cross-axis height. Plain text nav links have no background so this
   is invisible; .navmain-cta's solid background made it visible as an
   oversized block (found live 2026-08-18). Opt this one <li> out of
   the stretch so it sizes to its own content instead. The vendor's
   own .navmain-cta background is painted on the <li> itself (not the
   <a>), so border-radius has to live there too, with overflow:hidden
   to actually clip the corners. */
/* HIDDEN 2026-08-25 (Wayne): the orange "Order Now" button in the top
   right of the header is gone on desktop AND mobile. It is the tenth
   nav item, so on a tablet it was also consuming the width that pushed
   real navigation links out of the bar.

   Hidden rather than deleted from header.php: the item is admin-
   configurable (its text and URL come from store settings, see
   header.php's $ctaText/$ctaUrl), so removing the markup would mean
   removing that setting too. Delete both together if it is never
   coming back. */
.navmain ul li.navmain-cta { display: none !important; }

.navmain ul li.navmain-cta {
    align-self: center;
    border-radius: 8px;
    overflow: hidden;
}
.navmain ul li.navmain-cta a {
    padding: 1em 1.6em;
    line-height: 1.2;
    font-size: 1.05em;
}

/* "Login as Owner" impersonation banner — Nebulous loads no other
   stylesheet that touches .impersonation-banner, so without this it
   renders as a raw unstyled full-width div shoving the entire header
   down (found live 2026-08-18). Fixed + vertically centered on the
   right edge instead, out of normal document flow. */
.impersonation-banner {
    position: fixed;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    z-index: 2000;
    width: 220px;
    box-sizing: border-box;
    background: #fbbf24;
    color: #1f2937;
    text-align: right;
    padding: 0.85rem 1rem;
    font-size: 0.82rem;
    line-height: 1.35;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.6rem;
    border-radius: 8px 0 0 8px;
    box-shadow: -2px 2px 12px rgba(0, 0, 0, 0.18);
    /* Without these, the impersonated customer's email (a single
       unbroken string, no spaces) ignored the box's own width and
       spilled out past the right edge of the card instead of
       wrapping — caught live from Wayne's screenshot, 2026-08-19. */
    overflow-wrap: break-word;
    word-break: break-word;
}

.impersonation-banner-form { margin: 0; display: inline; width: 100%; }

.impersonation-banner-form button {
    background: #1f2937 !important;
    color: #fff !important;
    border: none !important;
    border-radius: 3px !important;
    padding: 0.45rem 1.1rem !important;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    cursor: pointer;
    white-space: nowrap;
    width: 100%;
}

.impersonation-banner-form button:hover { background: #374151; }

/* .toolbarmain-menu-logout's <button> needs the same treatment
   layout.css already gives its sibling <a> tags — the vendor markup
   assumes a plain link here, this app uses a POST form for CSRF. */
.toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-logout .nav-logout-form button {
    display: block;
    padding: 5px 10px;
    color: var(--color-grey);
}
.toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-logout .nav-logout-form button:hover {
    color: var(--color-primary-active-foreground);
    background: var(--color-primary-active-background);
}

/* WHMCS 9.0 parity Phase 3 — Multi-Currency display selector, a
   plain <li> in the toolbar's own <ul> (see currency-selector.php). */
.toolbarmain-menu li.currency-selector-li { display: flex; align-items: center; padding: 0 8px; }
/* Three classes plus the element, because the padding declared here
   was losing to a broader vendor form rule — computed padding came out
   9.6px rather than the 2px asked for, which made the control 40px tall
   against its 36px toolbar neighbours and pushed the whole row to 44px.
   Measured 2026-08-25: the select's centre sat 6px below the cart and
   client-area links beside it, which is the lean Wayne spotted.
   An explicit height plus a real line-height pins it rather than
   letting inherited padding decide. */
.toolbarmain-menu li.currency-selector-li .currency-selector-select {
    background: transparent;
    color: var(--color-grey);
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: 4px;
    font-size: 0.78rem;
    /* Right padding leaves room for the native dropdown arrow. */
    padding: 0 1.4rem 0 0.5rem;
    height: 1.65rem;
    line-height: 1.6;
    cursor: pointer;
}
.currency-selector-select option { color: #111; }

/* ---- THE ENTRANCE ANIMATIONS WIDEN THE PAGE (2026-09-03) ----------

   Wayne: "It turns into a complete mess", with a horizontal scrollbar
   and the whole page shunted sideways.

   MEASURED, in a 435px-wide frame: the document scrolled to 771px —
   nearly double the viewport. The widest offenders were ordinary text
   blocks 364px wide carrying transform: translateX(364px). That is
   animate.css's fadeInRight sitting at its START keyframe. WOW.js only
   removes it when the element scrolls into view, so everything below
   the fold is parked one full width to the right of where it belongs,
   and each one extends the document.

   It is worse on a narrow window for a reason that matters: the
   translate is a percentage of the element, and a narrow viewport makes
   these blocks a larger share of the page, so the overhang grows
   relative to the screen exactly as the screen shrinks.

   overflow-x: clip, NOT hidden. `hidden` on body makes it a scroll
   container, which silently kills position: sticky — and this site has
   a sticky main menu (nebulous.js, [data-sticky-menu]). `clip` cuts the
   overflow without creating a scroll port, so the sticky header still
   works. The paired overflow-y: visible is required: clip in one axis
   with the other left at auto would re-introduce a scroll container. */
html {
    overflow-x: clip;
}

body {
    overflow-x: clip;
    overflow-y: visible;
}

/* ---- ONE CONTAINER FOR THE WHOLE PAGE (2026-09-03) ----------------

   Wayne: "We need everything to line up to the left and right side of
   the website evenly", then "They all need to align evenly to the left
   ... and on the right."

   THE THEME ALREADY HAD A SHARED CONTAINER AND THIS ELEMENT WAS THE
   ONE THING NOT USING IT. .contentcontainer (vendor layout.css:172) is
   `width: 90%; max-width: 1250px; margin: 0 auto` with NO padding, and
   the header, the logo row, the toolbar and the hero all sit in one.
   This element instead took a FIXED 1250px box plus 4rem/1.5rem of
   asymmetric padding — a different width AND a different inset. At a
   1357px viewport that put main's content at x=118 while the header's
   sat at x=68: the 50px gap Wayne photographed. It was never a
   responsive bug, it was two different containers that only looked
   aligned at the width they were each tuned at.

   THE EARLIER REVERT IS NOT BEING IGNORED. I made this symmetric once
   before, unprompted, on the reasoning that the social rail it cleared
   no longer exists — and Wayne said the content had moved left, which
   it had, so it went back. What is different now is that this is the
   request, and that the target is not "symmetric" but "the same box as
   everything else", which is a stronger and more durable rule: nothing
   here is tuned to a width, so nothing can drift out of agreement
   again.

   Vertical padding is unchanged. Only the horizontal box moves. */
.site-main.nebulous-main {
    /* Width, max-width and centring come from .contentcontainer, which
       this element now carries (header.php). Repeating them here would
       out-specify the vendor's own responsive rules for that class —
       two classes beats one — and put main back out of step with the
       header on small screens, which is the bug this replaced.

       LONGHAND, not `padding: 2.5rem 0`. The shorthand also writes the
       horizontal values, and this selector out-specifies
       .contentcontainer — so on mobile, where the container switches to
       full width plus a 16px inset, the shorthand zeroed that inset and
       main ran to the screen edge while the header stayed at 16. Setting
       only the two axes this rule actually cares about leaves the
       container's own horizontal padding alone at every breakpoint. */
    padding-top: 2.5rem;
    padding-bottom: 2.5rem;
}

@media (max-width: 780px) {
    /* The old rule dropped the left padding here to stop 4rem eating a
       phone screen. A percentage container needs no such special case —
       90% is 90% at every width. */
    .social-sidewidget { display: none; }
}

/* CURRENT ORDER, corrected 2026-09-01 — the list below had described
   Web Design and AI for over a week after both were removed, which is
   exactly the drift these numbers cannot survive:
   Home(1)/Domains(2)/Web Hosting(3)/Get a Quote(4)/
   Web & App Development(5)/Software(6)/Addons(7)/Company(8)/Order(10).
   Support(9) was merged into Company; link9 is now unused.

   Development took the link5 slot the AI item left empty, deliberately
   rather than appending — inserting a position would renumber
   everything after it and invalidate the two overrides below. Vendor layout.css's own narrow-viewport
   collapse is hardcoded to specific numbers tuned for none of these
   orders. Cancel the vendor rule and reapply the same "hide least
   essential item first" priority at the current correct numbers:
   Support (link9) hides first at <1030px (Company/link8 stays
   visible); Company (link8) then hides too at <900px (Addons/link7
   and Software/link6 stay visible longest). */
/* Support was merged into Company on 2026-09-01, so navmain-link9 no
   longer exists and its rule went with it. The nav is eight items plus
   the CTA now, which is the point of the merge — one fewer thing to
   hide on a narrow screen.

   REPOINTED 2026-09-02, when Wayne reordered the nav and "Get a Quote"
   landed in slot 7. Vendor layout.css hides link7 below 1030px purely
   by position, which would have made the quote CTA — the one item on
   this nav whose whole job is conversion — the FIRST thing to vanish on
   a 13" laptop. The order is Wayne's call; which item is expendable at
   1030px is a separate question, and the honest answer is not "the one
   that asks for the sale".

   So the priority is restated here against the new numbers, least
   essential first: Company (link8) goes at 1030px, Addons (link6)
   follows at 900px, and Get a Quote (link7) is pinned visible at both
   widths against the vendor rule. */
/* link7 (Get a Quote) was pinned visible here against the vendor rule,
   because hiding the one item whose job is conversion first was the
   wrong trade. Wayne removed that item from the nav on 2026-09-03, so
   the pin now protects nothing and is gone with it. The slot stays
   unused rather than being backfilled — see header.php. */
@media (max-width: 1030px) {
    .navmain .navmain-link8 { display: none; }
}

@media (max-width: 900px) {
    .navmain .navmain-link6 { display: none; }
    .navmain .navmain-link8 { display: none; }
}

/* The Support column inside the Company mega menu.

   THE CARDS MUST STACK HERE. vendor layout.css sizes every mega-menu
   card at var(--column-2) — half the container — which is right in the
   661px first column and wrong in this 283px one: three cards came out
   119px wide with `overflow: hidden` slicing the words in half
   ("Knowl…", "Suppo… Ticke…"). Wayne saw exactly that.

   FIVE CLASSES, deliberately. The vendor rule is
   `.navmain .navmain-dropdown-megamenu .navmain-subcontainer
   .navmain-link` at (0,4,0); anything shorter ties or loses and the
   width silently does not apply. This is (0,5,0) and wins outright.
   That tie has been lost repeatedly in this file — see the
   .client-tabs note in app.css for the same lesson. */
.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-columns-col2 .navmain-link {
    width: 100%;
}

.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-columns-col2 .navmain-links {
    /* Explicit rather than relying on the flex-wrap above: with
       full-width children the row/column distinction stops being
       cosmetic and starts deciding whether they stack at all. */
    flex-direction: column;
    gap: 0.5rem;
}

/* It keeps a heading so it reads as its own thing rather than four
   more Company links. */
.navmain .navmain-col-heading {
    margin: 0 0 0.5rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--color-body, #6b7280);
}

/* The vendor's own per-network background-color classes
   (.social-icon-facebook etc.) never apply here — our markup emits a
   plain .social-icon span with an inline SVG, not those network
   modifier classes, and several of our networks (tiktok/threads/
   snapchat/twitch) have no vendor color rule anyway. Left unstyled,
   vendor's `.social-icon a { width: 50px }` with no height/centering
   let the bare 18x18 SVG sit flush at the top-left of its own box —
   visually a stray icon fragment pinned to the viewport edge, not a
   real button. Give every icon real button chrome, each in its own
   real brand color, instead. */
.social-sidewidget .social-icon a {
    height: 50px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-secondary-background); /* fallback for any network without a color rule below */
}
.social-sidewidget .social-icon a:hover {
    filter: brightness(1.12);
    padding: 0; /* cancel vendor's hover padding-left:30px, which would break flex centering */
}
.social-sidewidget .social-icon svg { color: #fff; }

.social-sidewidget .social-icon-facebook a { background: #1877f2; }
.social-sidewidget .social-icon-x a { background: #000; }
.social-sidewidget .social-icon-youtube a { background: #ff0000; }
.social-sidewidget .social-icon-linkedin a { background: #0a66c2; }
.social-sidewidget .social-icon-instagram a { background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); }
.social-sidewidget .social-icon-tiktok a { background: #010101; }
.social-sidewidget .social-icon-discord a { background: #5865f2; }
.social-sidewidget .social-icon-telegram a { background: #26a5e4; }
.social-sidewidget .social-icon-whatsapp a { background: #25d366; }
.social-sidewidget .social-icon-pinterest a { background: #e60023; }
.social-sidewidget .social-icon-reddit a { background: #ff4500; }
.social-sidewidget .social-icon-github a { background: #181717; }
.social-sidewidget .social-icon-twitch a { background: #9146ff; }
.social-sidewidget .social-icon-threads a { background: #000; }
.social-sidewidget .social-icon-snapchat a { background: #fffc00; }
.social-sidewidget .social-icon-snapchat svg { color: #000; } /* yellow brand bg needs a dark icon for contrast */

/* ---- Hero slider (real .banner, .background, .contentcontainer,
   the banner-price fields, .banner-heading, .banner-text,
   .list.list-col2, and .banner-buttons classes — vendor/css/layout.css
   plus animate.css already style and animate all of them; this is
   only a pre-JS list-style reset so the slide list doesn't flash as
   bare bullets before bxSlider initializes and wraps it in
   .bx-wrapper). Real Nebulous's hero/category banner is full-bleed
   (edge to edge, only the .contentcontainer INSIDE it is
   width-constrained) even though .site-main.nebulous-main itself has
   a max-width + padding for every other section on the page — the
   classic full-bleed-inside-a-constrained-parent breakout (100vw +
   negative margin via left/right: 50%) undoes just that one
   ancestor's constraint. ---- */

.nebulous-slider { list-style: none; margin: 0; padding: 0; }

.nebulous-slider,
.category-hero-banner {
    width: 100vw;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
    /* Whatever follows a full-bleed banner used to butt straight up
       against it — measured 2026-08-25 as a literal 0px gap on the
       product, category and domain-search pages alike, because
       .site-main's own 2.5rem padding sits ABOVE the banner, not below
       it. Wayne: "it's sitting too close to the top banner". */
    margin-bottom: 2.5rem;
}

/* Once bxSlider initializes (see vendor/js/slider.js), it moves the
   <ul class="nebulous-slider"> INSIDE a new .bx-viewport/.bx-wrapper
   div pair it inserts — from that point on, .bx-wrapper (not the ul)
   is what actually determines the rendered width, and bxSlider sets
   it an inline `max-width: 100%` (100% of ITS parent, the still-
   constrained .site-main.nebulous-main) that silently re-caps the
   breakout above right back down (found live 2026-08-16 — the slider
   rendered as a narrow, off-center box with the prev/next arrows
   stranded out at the true page edges). Repeating the same full-bleed
   rule on .bx-wrapper, with !important to beat that inline style, is
   the only way to keep the breakout once the plugin has taken over. */
.bx-wrapper {
    width: 100vw;
    max-width: 100vw !important;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
    /* THE SLIDER ARROWS PUNCHED THROUGH THE ACCOUNT MENU (2026-09-03).
       Wayne: "The right slider arrow is showing through the client area
       menu when it's open."

       Both .bx-next and .account-menu-panel are z-index 9999, and
       measured live, NEITHER had a positioned ancestor forming a
       stacking context — so both sat in the root context on the same
       level, where the tie is broken by source order alone. The slider
       is in main and the toolbar is in the header, so the arrow always
       won, and no z-index number on the panel would have settled it for
       long.

       isolation: isolate contains the arrows' 9999 INSIDE this wrapper
       instead of raising anything. The wrapper then takes part in the
       page as one z-index:auto positioned box, which any real overlay
       outranks. This fixes the whole class — every future dropdown,
       modal and flyout now sits above the banner without knowing the
       banner exists. */
    isolation: isolate;
}

/* ---- Exact full-bleed, when JS has measured the page ----

   The 100vw/-50vw rules above are an approximation that misses on this
   layout for two compounding reasons: vw includes the scrollbar, and
   `left: 50%` is half of .site-main's content box, which is off-centre
   because that element is padded 4rem left / 1.5rem right to reserve a
   gutter for the social rail.

   nebulous.js measures both and publishes --bleed-left (the content
   box's true distance from the viewport's left edge) and --bleed-width
   (the real visible width), then adds .has-bleed-vars. Anchoring to
   those lands the banner exactly on the page edges at any width.

   Scoped to the class so that with JS disabled the approximation above
   still applies — slightly off, but a banner rather than none. */

.has-bleed-vars .nebulous-slider,
.has-bleed-vars .category-hero-banner,
.has-bleed-vars .bx-wrapper {
    width: var(--bleed-width);
    max-width: var(--bleed-width) !important;
    left: auto;
    right: auto;
    margin-left: calc(-1 * var(--bleed-left));
    margin-right: 0;
}

/* ONCE bxSlider RUNS, THE SLIDER IS NESTED.
 *
 * bxSlider wraps <ul class="nebulous-slider"> in its own .bx-wrapper,
 * so both match the rule above and the breakout is applied TWICE — the
 * homepage slider slid a further 316px left and stopped short of the
 * right edge (found immediately, 2026-08-26).
 *
 * The old -50vw version tolerated the nesting by accident: on the inner
 * element `left: 50%` resolved against the already-full-width wrapper,
 * which very nearly cancelled the negative margin. A fixed pixel offset
 * has no such luck, so the inner copy has to be reset explicitly.
 *
 * The wrapper keeps the breakout; the list inside simply fills it. */
.has-bleed-vars .bx-wrapper .nebulous-slider {
    width: 100%;
    max-width: 100% !important;
    margin-left: 0;
    margin-right: 0;
    left: auto;
    right: auto;
}

/* The account banner renders outside <main> and is already full width;
   it must not pick any of this up. */
.has-bleed-vars .acct-hero.acct-hero-plain {
    width: auto;
    max-width: none !important;
    margin-left: 0;
    margin-right: 0;
}

/* Vendor slider.css pins the prev/next arrows at left/right: 10px off
   .bx-wrapper's own edge — since .bx-wrapper is a 100vw full-bleed
   breakout above, that puts the right arrow right at (or past, once a
   scrollbar eats into 100vw's true visible width) the true edge of the
   page. Pull both in a bit further so they read as sitting over the
   slide, not hanging off the browser edge. Left arrow pulled in an
   extra 25px on top of that so it clears the fixed .social-sidewidget
   sidebar it was overlapping; right arrow moved the same 25px to
   keep both arrows symmetric. */
.bx-wrapper .bx-prev { left: 55px; }
.bx-wrapper .bx-next { right: 55px; }

/* bxSlider's mode:'fade' (see nebulous.js) locks .bx-viewport's height
   to whichever slide it measures at init — with no explicit height on
   .banner itself, that measurement is just each slide's own natural
   content height, and a 2-column screenshot slide (banner-design2column)
   is naturally much taller than a plain 1-column slide
   (banner-design1column). Without a shared floor, whichever slide
   loads second sits inside a viewport still sized for the first,
   leaving a blank gap below its own shorter content — confirmed live
   2026-08-17. min-height here (not height, so a slide needing more
   room can still grow) gives every slide the same floor so
   background-size:cover (real vendor/css/layout.css's own .banner
   rule) fills the box edge-to-edge on every slide, not just the
   tallest one. Value matches banner-design2column's own real
   (cPanel-screenshot) content height at desktop widths. Desktop-only
   — the two-column layout stacks to a single column on narrow
   viewports (see vendor/css/layout.css's own @media rules for
   .banner.banner-design2column), so a desktop-sized floor would just
   add dead space there instead of fixing anything. */
/* Real bug found live 2026-08-21: the comment above assumed real
   vendor/css/layout.css's `.banner { background-size: cover; ... }`
   rule would cover this — it doesn't, because the actual
   background-image (hero-slides.php's own inline style) is set on
   the CHILD `.background` div, not on `.banner` itself, and CSS
   background properties never cascade from parent to child. With no
   background-size/position/repeat of its own, `.background` fell back
   to browser defaults (auto/repeat/0% 0%) — any uploaded image whose
   own pixel dimensions didn't happen to closely match the rendered
   box (confirmed live: a 2160x864 upload in a 1920x584 box) rendered
   cropped to its native top-left corner instead of scaled to fill,
   with real tiling a further risk on a wider viewport/smaller image.
   Applying the same 3 properties directly to `.background` (the
   element that actually holds the image) is the real fix. */
/* Same 0px gap as .category-hero-banner above — the slider that drives
   category and homepage banners needs it too, or the first section
   below sits flush against the artwork. */
.nebulous-slider { margin-bottom: 2.5rem; }

.banner .background {
    background-size: cover;
    background-position: center center;
    background-repeat: no-repeat;
}

@media (min-width: 781px) {
    .banner .background { min-height: 36.5rem; }

    /* Category banners run a third shorter than the homepage's — Wayne
       2026-08-25, measuring against index.php's 36.5rem/584px.
       24.375rem = 390px.

       Scoped to #category-hero-slider rather than lowering the rule
       above, because that 36.5rem floor is load-bearing for the
       HOMEPAGE specifically: bxSlider's fade mode locks .bx-viewport
       to whichever slide it measures at init, so a carousel of slides
       with different natural heights needs a shared floor or the
       shorter ones leave a gap (see the comment above). A category
       page renders exactly ONE slide
       (templates/nebulous/category-hero.php), so it has no such
       mismatch to protect against and can size to its own content.

       Still min-height, not height — a category whose slide carries
       six feature bullets or an unusually long subhead grows past 390
       rather than clipping its own text. */
    #category-hero-slider .banner .background { min-height: 24.375rem; }

    /* min-height alone only sets a floor — measured live 2026-08-25,
       the VPS slide's own content needed 444px, so it grew straight
       back past the 390 target. Real vendor spacing at fault, all of
       it inherited from a banner designed for 584px: .contentcontainer
       pads 50px top and bottom, and the heading, subhead and feature
       list each carry a 30px bottom margin. 314px of content in 130px
       of gaps.

       Tightened below to land the stack around 376px so the 390 floor
       is what actually decides the height, rather than the content
       overshooting it. Scoped to the category slider so the homepage
       carousel keeps the vendor's original rhythm. */
    /* 1rem, not the 1.5rem this sat at before the headline panel
       existed. The panel brought its own 18px of top and bottom
       padding, which would have pushed the stack past the 390 floor
       and reintroduced the uneven heights that took three passes to
       remove. The container gives back most of what the panel takes,
       and the panel's own edge now reads as the inset instead. */
    #category-hero-slider .contentcontainer { padding-block: 1rem; }
    #category-hero-slider .banner-heading { margin-bottom: 0.75rem; }
    #category-hero-slider .banner-text { margin-bottom: 1.125rem; }
    #category-hero-slider .list ul { margin-bottom: 1rem; }

    /* Keep every line of text inside the artwork's calm left region.
       The vendor lets .banner-text and the two-column feature list run
       the full 1250px content width, which on a 1920px viewport pushes
       the subhead's first line and the whole second feature column out
       over the brightest part of the image — measured live, the
       subhead ended at x=1598 and "Instant Provisioning" sat at x=815,
       both well past the point where the new banner art stops being
       quiet. Capping the text column keeps the contrast the artwork
       was composed to provide, instead of relying on text-shadow to
       rescue it. */
    #category-hero-slider .banner-text,
    #category-hero-slider .list ul { max-width: 38.75rem; }

    /* ...but not once they're inside the headline panel, which is itself
       capped at 46rem and already sets the measure. Leaving the 38.75rem
       cap on inside it made the feature list wrap onto a third row on the
       categories with longer bullets — ai-website-builder's list measured
       107px against the usual 81px, which by itself put that banner 14px
       over the floor. Verified across all 18 category banners before
       committing: 17 land on exactly 390px with this, against 16 without. */
    #category-hero-slider .banner-headline-panel .banner-text,
    #category-hero-slider .banner-headline-panel .list ul { max-width: none; }

    /* Every category banner should come out the same height — Wayne
       2026-08-25, pointing at vps-hosting as the reference.
       min-height alone can't deliver that: it sets a floor, so any
       slide whose content exceeds 390 grows past it while the rest sit
       at exactly 390. VPS was the tall one at 414, because the vendor's
       25.6px subhead wraps to three lines inside the capped text
       column.
       Dropping it to 21px fits the longest subhead currently on the
       site into two lines, which puts the tallest slide (three-line
       heading equivalent, four features, both buttons) under the floor
       — so the floor, not the copy, decides the height on every page.
       Chosen over clamping the line count because clamping would
       silently truncate real sentences; this keeps every word visible.
       Recheck this if a subhead ever gets substantially longer. */
    #category-hero-slider .banner-text {
        font-size: 1.3125rem;
        line-height: 1.45;
    }

    /* Wayne 2026-08-25 wants every category banner on the two-column
       screenshot layout — copy left, a small product snapshot right.
       The vendor sizes that snapshot for a 584px banner: measured live
       on reseller-hosting, screenshot-whm.png renders 500x452, which
       plus padding forced the banner to 541px and blew straight past
       the 390 target.

       18rem caps it at 288px; with the ~20px browser-chrome bar
       (picture::before) and 64px of container padding that totals 372
       — under the floor, so 390 is again what sets the height rather
       than the image. width:auto keeps each snapshot's own aspect
       ratio, since they won't all be shaped alike, and margin-left
       keeps it flush right in its column as the vendor intends. */
    #category-hero-slider .banner.banner-designscreenshot picture img {
        max-height: 17rem;
        width: auto;
        margin-left: auto;
    }

    /* The two-column slides still came out 401px against 390 for the
       rest. Measured: col1 is 303px tall plus a 50px bottom margin,
       col2 is 323px plus a 30px top margin — both resolve to exactly
       353px, and 353 + 48px of container padding is 401. Vendor
       margins sized for the 584px banner again.
       Dropping col1's bottom margin (it's the last thing in that
       column, so the container's own padding already provides the
       gap) and taking the image cap from 18rem to 17rem brings the
       taller column to 337px, which puts the pair under the floor. */
    #category-hero-slider .banner-twocolumn-col1 { margin-bottom: 0; }
}

/* Wayne asked 2026-08-21 for every hero-slide banner site-wide to align
   like the real two-column (screenshot) layout — left-aligned heading/
   subtext with the buttons flush left underneath — rather than vendor
   layout.css's own default center alignment for a single-column
   (banner-design1column, no screenshot) slide. Real vendor CSS only
   left-aligns `.banner.banner-design2column .banner-heading/.banner-text`
   and left-justifies `.banner.banner-design2column .banner-buttons`
   (layout.css ~line 2566) — this drops the `.banner-design2column`
   scope so every banner, screenshot or not, gets the same left
   alignment, loading after layout.css so it wins on equal specificity
   with no !important needed. */
.banner .banner-heading,
.banner .banner-text {
    text-align: left;
}

.banner .banner-buttons {
    justify-content: flex-start;
}

/* Site-wide readable panel behind every banner and slider's copy —
   Wayne 2026-08-25, matching what product.php's hero already got.
   Same reasoning as there: a scrim dims the whole image evenly and
   still loses to any artwork carrying its own bright detail behind the
   words, whereas a real surface wins and gives the copy somewhere
   deliberate to sit.

   Heading, subhead and features are inside it; the buttons are not.
   They carry solid fills already, so they read fine straight on the
   artwork, and leaving them out mirrors the product hero, where the
   purchase card also sits below the panel rather than within it.

   inline-block so the panel shrinks to its content instead of ruling a
   slab across the full banner width — a short heading gets a short
   panel. Tokens deliberately identical to
   .category-hero-banner-headline so the two never drift apart; if one
   changes, change both. */
.banner .banner-headline-panel {
    display: inline-block;
    /* 46rem, not 42. ai-website-builder's subhead is 118 characters and
       wrapped to a third line inside the narrower panel, which alone put
       that banner 27px over the floor. The extra width buys the line back
       without shrinking the type further. It ends around 56% of a 1920px
       viewport, just inside where the banner artwork starts carrying
       detail. */
    max-width: 46rem;
    padding: 0.9375rem 1.375rem;
    margin-bottom: 0.9375rem;
    background: rgba(10, 12, 16, 0.6);
    -webkit-backdrop-filter: blur(14px) saturate(120%);
    backdrop-filter: blur(14px) saturate(120%);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-left: 3px solid #f09546;
    border-radius: var(--border-radius-default);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.38);
}

/* The feature list is the last thing in the panel and carries its own
   bottom margin for the buttons that used to follow it. Inside the
   panel that margin becomes dead space above the panel's own padding,
   so it's zeroed and the padding does the job. */
.banner .banner-headline-panel > *:last-child {
    margin-bottom: 0;
}

/* Real vendor/css/layout.css only adds the thin browser-chrome bar
   (picture::before) above a two-column screenshot — no drop shadow or
   rounded bottom corners on the screenshot itself, so add a light
   finishing touch to make it read as a real floating browser window
   rather than a flat rectangle, matching the reference screenshots. */
.banner.banner-design2column.banner-designscreenshot picture img {
    border-radius: 0 0 3px 3px;
    box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4);
}

/* ---- Real Nebulous's own layout.css hardcodes font-family: "Font
   Awesome 6 Pro" on every icon pseudo-element (dropdown carets, the
   mobile hamburger icon, the mobile submenu child indicator) — but
   the webfont genuinely bundled with the purchased WizardPanel addon
   (vendor/css/fontawesome.min.css) is Font Awesome PRO 5.10.1, whose
   loaded @font-face families are literally named "Font Awesome 5
   Pro"/"5 Brands"/etc, not "6 Pro" — a real mismatch baked into the
   purchased product itself, not something this app introduced. Every
   affected codepoint (\f0da caret-right, the burger icon, etc.) is
   stable across FA5→FA6, so simply redeclaring the correct loaded
   family name here (this file loads after layout.css, so it wins on
   equal specificity) is enough — found live 2026-08-16 as the
   invisible "black arrow" next to Shop/Company nav dropdowns. ---- */

.navmain .navmain-dropdown a:before,
.navmain .navmain-dropdown-single .navmain-subcontainer .navmain-link a:before,
.navmain .navmain-mobile-icon,
.navmain-mobile .navmain-dropdown .navmain-mobile-child {
    font-family: "Font Awesome 5 Pro";
}

/* ---- Addons' own top-level label is a <span>, not an <a> (2026-08-20
   — see header.php's nullable $href) since it has no single landing
   page of its own. Vendor layout.css only ever styles the real <a>
   (.navmain ul li a / .navmain .navmain-dropdown a:before for the
   caret) — none of that reaches a <span>, so every rule needed to
   make it look/behave like every other top-level nav item is
   reproduced here explicitly against .navmain-dropdown-label. ---- */
.navmain .navmain-dropdown-label {
    display: block;
    margin: 0;
    font-weight: 700;
    cursor: default;
}
.headermain-design1 .navmain .navmain-dropdown-label {
    color: var(--navmain-headerdesign1-button-foreground);
    text-shadow: var(--navmain-headerdesign1-button-foreground-shadow) 1px 1px;
    padding: 0 var(--navmain-headerdesign1-button-padding);
}
.headermain-design2 .navmain .navmain-dropdown-label {
    color: var(--navmain-headerdesign2-button-foreground);
    text-shadow: var(--navmain-headerdesign2-button-foreground-shadow) 1px 1px;
    padding: 0 var(--navmain-headerdesign2-button-padding);
}
.navmain .navmain-dropdown-label:before {
    font-family: "Font Awesome 5 Pro";
    font-weight: 900;
    display: inline-block;
    font-style: normal;
    font-variant: normal;
    text-rendering: auto;
    font-size: 0.8em;
    line-height: 1;
    padding: 0 5px 0 0;
    width: 10px;
    text-align: left;
}
/* ---- THE HOVER CARET WENT WHITE ON A WHITE BAR ----------------
   Wayne 2026-09-03: "the chevrons should all point down when you hover
   over the menu when it opens, not disappear", then "just keep them
   black".

   They were never disappearing and the glyph was never wrong. The
   theme already swaps \f0da (caret right) for \f0d7 (caret down) on
   hover, which is exactly the affordance asked for. What broke is the
   COLOUR: vendor variables.css sets
   --navmain-headerdesign1-icon-hover-foreground to #f1f1f1, because
   headerdesign1 was authored for a DARK nav bar. This install runs it
   on rgb(242,243,245) — near-white on near-white, about 1.01:1, which
   is invisible rather than absent.

   Overridden here rather than edited in vendor/css/variables.css:
   that file belongs to the purchased theme and a theme update would
   put #f1f1f1 straight back. nebulous.css loads after it, so
   redefining the variable is enough and no rule has to be duplicated
   — this reaches both the <a> caret and the <span> label caret at
   once.

   headerdesign2 is deliberately untouched: its normal icon colour is
   already white, so that design really is on a dark bar and its white
   hover is correct there. ---- */
:root,
body.template-nebulous {
    --navmain-headerdesign1-icon-hover-foreground: #222222;
}

.headermain-design1 .navmain .navmain-dropdown-label:before {
    content: var(--navmain-headerdesign1-icon);
    color: var(--navmain-headerdesign1-icon-foreground);
    text-shadow: var(--navmain-headerdesign1-icon-foreground-shadow) 1px 1px;
}
.headermain-design2 .navmain .navmain-dropdown-label:before {
    content: var(--navmain-headerdesign2-icon);
    color: var(--navmain-headerdesign2-icon-foreground);
    text-shadow: var(--navmain-headerdesign2-icon-foreground-shadow) 1px 1px;
}
.headermain-design1 .navmain .navmain-dropdown:hover .navmain-dropdown-label:before {
    content: var(--navmain-headerdesign1-icon-hover);
    color: var(--navmain-headerdesign1-icon-hover-foreground);
}
.headermain-design2 .navmain .navmain-dropdown:hover .navmain-dropdown-label:before {
    content: var(--navmain-headerdesign2-icon-hover);
    color: var(--navmain-headerdesign2-icon-hover-foreground);
}

/* ---- Mega-menu category icon tiles. Real Nebulous reserves this
   exact slot via the .navmain-icon class + the
   --navmain-headerdesign{1|2}-megamenu-icon-spacing (75px) padding
   already baked into vendor/css/layout.css, rendering an admin-
   uploaded icon image as an ::before pseudo-element background at
   left:20px/vertically centered. This app has no per-category icon
   upload field, so a real Font Awesome glyph on a colored tile
   (header.php's $nebulousCategoryMenuIcons map) fills the same real
   slot as a genuine DOM element instead of a fabricated uploaded
   image. ---- */

.navmain-icon a > i {
    position: absolute;
    left: 20px;
    top: 50%;
    transform: translateY(-50%);
    width: 40px;
    height: 40px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 1.1rem;
}

/* ---- Per-category themed hero banner (category.php's fallback when
   no rich TemplateSlides-driven slide is configured for that
   category yet) ---- */

.category-hero-banner {
    background-color: var(--color-primary-background);
    background-size: cover;
    background-position: center;
    /* 2rem, down from 3.5rem. On product.php this banner wraps a full
       purchase form, so its own padding is multiplied by everything
       inside it — measured 2026-08-25, the shop-app hero came to 877px
       on an 864px viewport, taller than the screen it sits on. */
    padding: 2rem 1.5rem;

    /* SHOW THE ARTWORK.
     *
     * The banner was as tall as its text needed and no taller — 199px
     * on a 1754px page, an 8.8:1 letterbox. The banner images are
     * 1920x800, which is 2.4:1, so `cover` scaled them to fill the width
     * and cropped 532px of height: only 27% of each image survived.
     * Wayne, 2026-08-26: "they're so zoomed in we can't see the full
     * images that looked so good".
     *
     * A min-height that tracks the viewport width keeps the box near
     * the artwork's own proportions instead of fighting them — roughly
     * 3.5:1 at desktop widths, so about two thirds of the image shows.
     * Capped, because a banner that scales without limit eats the whole
     * screen on a wide monitor.
     *
     * min-height, not height: product.php wraps a purchase form in this
     * same banner and must still be free to grow past it.
     */
    min-height: clamp(240px, 28vw, 500px);
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* A banner backed by a strip cut does not need the extra height — the
   artwork is already the right shape, so the box can go back to being
   short and still show essentially all of it. aspect-ratio matches the
   6:1 cut exactly; min-height is the floor for a narrow window, where
   6:1 would be a sliver. */
.category-hero-banner.category-hero-banner-strip {
    aspect-ratio: 6 / 1;
    min-height: 190px;
}

/* As a flex item the content box stopped stretching: its own
   `margin: 0 auto` then centred it at its CONTENT width rather than at
   the 1250px it is capped to, which moved the headline card from the
   left edge into the middle of the banner. Stretch it back. */
.category-hero-banner > .category-hero-banner-content { width: 100%; }

/* The banner is .site-main's first child, and that element is padded
   2.5rem on top — which put a strip of page above every front-site
   banner. Pull it back by exactly that padding. Scoped to :first-child
   so a banner further down the page is not yanked over whatever
   precedes it. */
.site-main > .category-hero-banner:first-child,
.site-main > .nebulous-slider:first-child,
.site-main > .bx-wrapper:first-child {
    margin-top: -2.5rem;
}

/* Two layers, not one. The original was a single flat top-to-bottom
   wash, which dims the image evenly but gives the text side no more
   contrast than the empty side. The left-to-right layer sits under it
   so the half the copy occupies is darker than the right half — where
   a product screenshot or the artwork's subject sits.

   Both layers are deliberately much lighter than they look like they
   should be. They first shipped at 0.72/0.45, which composited to
   ~0.85 at the left edge against the original single wash's 0.55, and
   the banner image visibly dimmed — Wayne flagged it the same day.
   The panel below is what actually makes the copy readable now, so
   this only has to add a little depth: ~0.50 combined at the left,
   ~0.15 at the right, i.e. lighter than the original everywhere and
   considerably lighter on the side the artwork lives on.
   Keep it that way. If a heading ever needs help, thicken the panel,
   not this — dimming the whole image to rescue text in one corner is
   what went wrong the first time. */
.category-hero-banner::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        linear-gradient(90deg, rgba(0, 0, 0, 0.38) 0%, rgba(0, 0, 0, 0.26) 40%, rgba(0, 0, 0, 0.06) 72%, rgba(0, 0, 0, 0.03) 100%),
        linear-gradient(180deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.12));
}

/* The readable panel behind the heading and tagline.
   A scrim alone can't win against an image that contains its own
   large pale lettering — the competing text stays legible through any
   wash dark enough to keep the photo visible. A real surface does win,
   and it also gives these headings somewhere deliberate to sit rather
   than floating loose on the artwork.
   Dark translucent glass rather than a solid fill so the image still
   reads through it, with the brand orange as a left rule to tie it to
   the banner headings and the Order Now button.

   0.6 alpha, not the 0.72 this first shipped at — Wayne 2026-08-25,
   it read as a solid slab. The blur carries more of the separation
   instead (10px to 14px), which softens what's behind the copy
   without flattening it to black, so the artwork stays visible
   through the panel. Contrast is still ~16:1, far past AA, so there's
   headroom here if it wants to go lighter again; below roughly 0.45
   the baked-in lettering on some banner images starts competing with
   the heading again, which is the whole reason the panel exists.
   The blur remains an enhancement rather than a dependency: at 0.6 the
   panel is still legible where backdrop-filter isn't supported. */
.category-hero-banner-headline {
    display: block;
    max-width: 42rem;
    padding: 1.15rem 1.5rem;
    background: rgba(10, 12, 16, 0.6);
    -webkit-backdrop-filter: blur(14px) saturate(120%);
    backdrop-filter: blur(14px) saturate(120%);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-left: 3px solid #f09546;
    border-radius: var(--border-radius-default);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.38);
}

/* p already carries max-width: 36rem from the generic rule below; inside
   this narrower panel that cap never binds, so the panel's own padding
   is what sets the measure. Nothing to override — noted so the two
   max-widths aren't later "reconciled" into one and the panel widened
   by accident. */
.category-hero-banner-headline p { max-width: none; }

/* Vendor layout.css centres every heading site-wide
   ("h1, h2, h3, h4, h5, h6, .headingstyle2, .headingstyle3
   { text-align: center }"), so inside this panel the h1 came out
   centred over a left-aligned paragraph — the two ragged edges
   disagreeing is what made it read as unfinished. Both left now. */
.category-hero-banner-headline h1,
.category-hero-banner-headline p {
    text-align: left;
}

.category-hero-banner-content {
    position: relative;
    max-width: var(--container-maxwidth);
    margin: 0 auto;
    padding-left: 2.5rem;
}

/* product.php only — a product with a screenshot_path shows it as a
   real foreground image inside the hero banner itself (right side),
   pushing the heading/tagline text to a left column, instead of the
   text simply centering the whole banner width. */
.category-hero-banner-content--split {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 2.5rem;
    flex-wrap: wrap;
    padding-right: 2.5rem;
}
.category-hero-banner-content--split .category-hero-banner-text { flex: 1 1 320px; }
/* Real WHMCS/Zomex hero banners frame their screenshot in a white
   "browser window" card (title bar + 3 dots) rather than a bare image
   floating straight on the banner background — Wayne's own reference
   mockup, applies to every product's banner, not just these two. */
/* The screenshot sits flush to the card's left, right and bottom edges,
   with only the chrome bar inset above it — which is how a real browser
   window is actually shaped. It previously carried 12px of white on
   three sides, and that inset is what made it read as a picture frame
   with a mat rather than a window, and what Wayne saw as the image "not
   filling" (2026-08-25). Nothing about the image changed; the white
   around it did.
   overflow:hidden lets the card's own 10px radius clip the image's
   bottom corners, so no white crescents peek out underneath — the
   image no longer needs a radius of its own. */
.category-hero-banner-screenshot {
    flex: 0 1 620px;
    max-width: 620px;
    background: #fff;
    border-radius: 10px;
    padding: 0;
    overflow: hidden;
    box-shadow: 0 20px 45px rgba(0, 0, 0, 0.45);
}
/* The bar keeps the inset the card gave up, so the traffic-light dots
   still sit in from the window edge like real chrome. */
.category-hero-banner-screenshot-bar {
    display: flex;
    gap: 6px;
    padding: 13px 15px 11px;
}
.category-hero-banner-screenshot-bar span {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    display: inline-block;
}
.category-hero-banner-screenshot-bar span:nth-child(1) { background: #ff5f56; }
.category-hero-banner-screenshot-bar span:nth-child(2) { background: #ffbd2e; }
.category-hero-banner-screenshot-bar span:nth-child(3) { background: #27c93f; }
/* The card gives the image a 596x320 slot, i.e. 1.86:1. object-fit:
   contain meant anything taller than that got its height clamped and
   was letterboxed with white bars down both sides — both product
   screenshots are 1600x992 (1.61:1), so both showed them. Found live
   2026-08-25.
   cover fills the slot instead, cropping the overflow rather than
   padding it. Paired with height:320px (not auto) so the box is a
   fixed shape for cover to fill — with height:auto there's nothing for
   it to crop against and it does nothing. Screenshots authored at
   roughly 1.86:1 lose nothing to the crop; anything squarer loses a
   little off the top and bottom, which beats white bars. */
.category-hero-banner-screenshot img {
    display: block;
    width: 100%;
    height: 320px;
    object-fit: cover;
    object-position: center top;
    /* No radius of its own — the card clips it via overflow:hidden, so
       the bottom corners follow the card's 10px instead of leaving
       white crescents under square ones. */
}
@media (max-width: 820px) {
    .category-hero-banner-content--split { flex-direction: column; padding-right: 0; }

    /*
     * FLEX-BASIS CHANGES AXIS WITH THE DIRECTION, AND max-width DOES NOT.
     *
     * These two carry `flex: 1 1 320px` and `flex: 0 1 620px`, which are
     * WIDTHS for the side-by-side layout above this breakpoint. Turning
     * the container into a column turns both into HEIGHTS — so the
     * screenshot column became a 620px-tall box around a 232px card,
     * leaving 388px of empty banner under it. Wayne: "Big empty space
     * under the Xray card."
     *
     * `max-width: 100%` was already corrected here for exactly the same
     * reason, which is the tell: whoever wrote it saw the cross-axis
     * half of the problem and not the main-axis half. Both belong here.
     *
     * That dead space was also inflating the banner's own content from
     * ~725px to 1113px, so this is half of why the hero was tall enough
     * to overflow its frame in the first place.
     */
    .category-hero-banner-content--split .category-hero-banner-text,
    .category-hero-banner-screenshot {
        flex-basis: auto;
    }

    .category-hero-banner-screenshot { max-width: 100%; }
}

/* !important needed — the vendor's own "body.template-nebulous h1, h2,
   h3, h4 { color: var(--color-heading) }" rule (body + class + element)
   is MORE specific than ".category-hero-banner-content h1" (class +
   element) alone, so it silently won and rendered this heading in the
   dark navy heading color instead of white, same specificity-fight bug
   class already fixed for .pricing-card-head h3 and
   .nebulous-footer .footer-column h3 elsewhere in this file. */
.category-hero-banner-content h1 { color: #fff !important; margin: 0 0 0.5rem; }
.category-hero-banner-content p { color: #e6e9ee; margin: 0; max-width: 36rem; }

/* The price/billing-cycle/Add-to-cart purchase box, relocated from
   product.php's own standalone .product-detail section into the
   banner itself for products that have a banner image — a light card
   floating on the dark banner, matching Wayne's own reference. Every
   rule here needs equal-or-higher specificity than the two generic
   ".category-hero-banner-content h1/p" rules above (and comes AFTER
   them in the cascade) so real text — the price, notes, form labels —
   isn't silently rendered in the banner's own white-on-dark colors. */
/* Deliberately still light, not the translucent glass the headline
   panel uses — Wayne asked 2026-08-25 whether it should match.
   It shouldn't, for three reasons: this is the conversion element and
   should be the highest-contrast thing in the hero rather than
   competing with the artwork behind it; everything inside it (small
   grey labels, radio inputs, hairline option borders) is tuned for a
   light surface and would each need recolouring on a dark one; and the
   hero now reads as one dark panel plus one white screenshot card, so
   a white purchase card balances that composition where a second dark
   one would leave the screenshot card odd.
   What it did need was to stop looking like an unstyled rectangle: a
   warm off-white rather than stark #fff (a chosen neutral, biased a
   touch toward the brand orange), a real hairline border, a deeper and
   slightly warm shadow, and an orange top rule that mirrors the
   headline panel's left rule so the two read as one family. */
.category-hero-banner-purchase {
    background: #fbfaf8;
    border: 1px solid rgba(20, 22, 26, 0.09);
    border-top: 3px solid #f09546;
    border-radius: var(--border-radius-default);
    padding: 1.25rem 1.5rem 1.4rem;
    margin-top: 1.1rem;
    max-width: 24rem;
    box-shadow: 0 22px 48px rgba(12, 10, 6, 0.42);
}
/* .product-detail-form's own default (display:flex, row) suits its
   original wider standalone-section home; inside this narrower banner
   card it needs to stack instead, or the billing-cycle picker's own
   3-wide min-width fight squeezes the note/button into the same row
   and wraps everything. Each cycle option also switches from the
   centered/stacked card look to a compact left-label/right-price row,
   which actually fits a narrow column better. */
.category-hero-banner-purchase .product-detail-form {
    flex-direction: column;
    align-items: stretch;
    gap: 0.85rem;
}
.category-hero-banner-purchase .billing-cycle-picker { flex-direction: column; }
/* Compact inside the banner specifically. Three options at 64px each
   made the picker 265px on its own — over half the purchase card, and
   the single biggest contributor to the hero's height. The standalone
   .billing-cycle-option below keeps its roomier sizing for
   product pages that render the box outside a banner. */
.category-hero-banner-purchase .billing-cycle-option {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    min-width: 0;
    padding: 0.4rem 0.8rem;
    line-height: 1.25;
}
.category-hero-banner-purchase .billing-cycle-picker { gap: 0.5rem; margin-bottom: 0.9rem; }
.category-hero-banner-purchase .billing-cycle-name { font-size: 0.72rem; }
.category-hero-banner-purchase .billing-cycle-option input[type="radio"] { position: static; margin: 0 0.6rem 0 0; }
.category-hero-banner-purchase button[type="submit"] { width: 100%; }
/* SSL products put a CSR paste box in the purchase card, and the markup
   asks for rows="10" — sensible on a standalone form, but inside a hero
   banner that one field measured 307px and pushed geotrust-quickssl to
   926px, taller than the viewport and well past the two heights Wayne
   already flagged as too tall. Capped here only; still resizable, so
   anyone pasting a long CSR can drag it open, and the field is scrollable
   regardless. The standalone .product-detail form keeps rows="10". */
.category-hero-banner-purchase textarea {
    height: 7.5rem;
    min-height: 7.5rem;
    resize: vertical;
}
/* The CSR explainer is a full paragraph of instructions; it reads fine a
   touch smaller in a 24rem card. */
.category-hero-banner-purchase .note { font-size: 0.82rem; line-height: 1.45; }
.category-hero-banner-purchase p { color: var(--color-body); max-width: none; margin: 0 0 0.75rem; }
.category-hero-banner-purchase .product-detail-price {
    color: var(--color-primary-background);
    font-family: var(--font-heading);
    font-size: 1.75rem;
    font-weight: 700;
    margin: 0 0 0.7rem;
}
.category-hero-banner-purchase .error { color: var(--color-red); }
.category-hero-banner-purchase .success { color: var(--color-green, #1f8a4c); }
.category-hero-banner-purchase .stock-warning { color: var(--color-red); }
.category-hero-banner-purchase .stock-note,
.category-hero-banner-purchase .note { color: var(--color-body); font-size: 0.9rem; }

.nebulous-slide-buttons { display: flex; gap: 1rem; }
.nebulous-slide-button-secondary {
    display: inline-block;
    padding: 0.65rem 1.4rem;
    background: var(--color-secondary-background);
    color: var(--color-secondary-foreground);
    border-radius: var(--border-radius-default);
    text-decoration: none;
    font-weight: 700;
}

/* ---- Ai Site Whiz per-product page content (product.php) ----
   Overview pull-quote + categorized feature-card grid, only rendered
   when an admin has configured them via Site Whiz's Product tab — a
   product with neither renders exactly as it always has (the legacy
   flat description-bullet list stays untouched for every other
   product in the catalog). Reuses the same real box/heading/accent
   tokens every other card component on this template already does. */
.product-overview-card {
    position: relative;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1.75rem 2rem 1.75rem 2.25rem;
    margin: 2rem 0;
    overflow: hidden;
}
.product-overview-card::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 4px;
    background: var(--color-primary-background);
}
.product-overview-card p {
    margin: 0;
    color: var(--color-body);
    font-size: 1.05rem;
    line-height: 1.6;
}

.product-feature-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1.25rem;
    margin-bottom: 2.5rem;
}
.product-feature-card {
    position: relative;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1.6rem 1.7rem;
    overflow: hidden;
}
.product-feature-card::after {
    content: "";
    position: absolute;
    top: 0; right: 0;
    width: 0; height: 0;
    border-style: solid;
    border-width: 0 30px 30px 0;
    border-color: transparent var(--color-primary-background) transparent transparent;
    opacity: 0.85;
}
.product-feature-card-head { display: flex; align-items: center; gap: 0.85rem; margin-bottom: 1rem; }
.product-feature-card-icon {
    width: 42px; height: 42px; flex-shrink: 0;
    border-radius: 50%;
    background: color-mix(in srgb, var(--color-primary-background) 18%, transparent);
    display: flex; align-items: center; justify-content: center;
    color: var(--color-primary-background);
}
.product-feature-card-icon svg { width: 55%; height: 55%; }
.product-feature-card-head h3 { margin: 0; font-family: var(--font-heading); font-size: 1.05rem; color: var(--color-heading); }
.product-feature-card ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.product-feature-card li { position: relative; padding-left: 1.3rem; font-size: 0.92rem; color: var(--color-body); line-height: 1.5; }
.product-feature-card li::before { content: "\2713"; position: absolute; left: 0; top: 0; font-weight: 700; color: var(--color-primary-background); font-size: 0.8rem; }
@media (max-width: 760px) { .product-feature-grid { grid-template-columns: 1fr; } }

/* ---- Category tiles / section headings (reused generic partials) ---- */

.category-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1.25rem; margin-bottom: 2.5rem; }
.category-tile {
    display: block; padding: 1.5rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    text-align: center; text-decoration: none;
    color: var(--color-heading);
    font-family: var(--font-heading); font-size: 1.1rem; font-weight: 700;
}
.category-tile:hover { border-color: var(--color-primary-background); }
.category-tile span { display: block; margin-top: 0.4rem; font-family: var(--font-body); font-size: 0.85rem; font-weight: 400; color: var(--color-body); }

.section-heading {
    text-align: center;
    margin: 0 0 1.125rem; /* 18px — standard spacing below every section heading */
    background-image: url("vendor/images/heading-background-nebulous.png");
    background-position: left center;
    background-repeat: repeat-x;
}
.section-heading h2,
.section-heading h1 {
    display: inline-block;
    padding: 10px 20px;
    margin: 0;
    background: var(--color-box-background);
    border: 1px solid var(--color-border);
    border-radius: var(--border-radius-default);
}

.overview-text {
    max-width: 900px;
    margin: 0 auto 2.5rem;
    color: var(--color-body);
    line-height: 1.7;
}
.overview-text p { margin: 0 0 1rem; }
.overview-text p:last-child { margin-bottom: 0; }

/* ---- Pricing / comparison cards ---- */

.pricing-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.5rem; margin-bottom: 2.5rem; }
.pricing-card {
    display: flex; flex-direction: column;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    overflow: hidden;
    position: relative;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.pricing-card:hover { transform: translateY(-6px); box-shadow: var(--shadow); }
.pricing-card.featured { border-color: var(--color-primary-background); box-shadow: var(--shadow); }
/* Corner ribbon. The rotated box has to stay INSIDE the card's grey
   head band: at the original size (0.72rem text, 2.5rem side padding,
   0.9rem top) its bounding box measured 121x121, which put its lower
   corner 19px BELOW the 73px head and down onto the card image — the
   "banner overlapping the top grey bar". Because a rotated square's
   bounding box is (w+h)/sqrt(2) on each side, the side padding drives
   the height far more than the padding value suggests, which is how it
   grew unnoticed.

   Not a mobile-only fix: the ribbon rendered at exactly 121x121 at
   every width, so it spilled on desktop too — and worse in proportion
   there (44% of a 273px card vs 33% of a 365px one). Corrected at the
   base rule rather than patched in the responsive block.

   RESTORED to the original diagonal corner ribbon 2026-08-25, at
   Wayne's request. It was shrunk, then rebuilt as a flat badge on the
   image band, chasing a reported "banner overlapping the grey bar" —
   but the ribbon was never the cause. The real overlap was the card
   BANNER IMAGE bursting its box (see the note on
   `body.template-nebulous img` in the responsive layer: a blanket
   `height: auto` beat `.pricing-card-image img`'s `height: 100%`,
   so the image rendered 123px tall in a 104px wrapper and its top
   edge sat ~10px over the header). With that fixed the corner ribbon
   sits correctly again, so these are the original values.

   The card needs `overflow: hidden` for this to read as a corner
   ribbon rather than a tag stuck to the outside — see .pricing-card. */
.pricing-card-ribbon {
    position: absolute; top: 0.9rem; right: -2.2rem; transform: rotate(45deg);
    background: var(--color-primary-background); color: var(--color-primary-foreground);
    font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
    padding: 0.25rem 2.5rem;
}
.pricing-card-ribbon-popular { background: var(--color-badge-hot-background); }
.pricing-card-ribbon-hot { background: var(--color-badge-hot-background); }
.pricing-card-ribbon-new { background: var(--color-badge-new-background); }
.pricing-card-ribbon-free { background: var(--color-badge-free-background); }
.pricing-card.badge-popular { border-color: var(--color-badge-hot-background); }
.pricing-card.badge-hot { border-color: var(--color-badge-hot-background); }
.pricing-card.badge-new { border-color: var(--color-badge-new-background); }
.pricing-card.badge-free { border-color: var(--color-badge-free-background); }
/* Fixed height (not just padding) so this box is IDENTICAL across
   every card regardless of the icon's own aspect ratio or whether a
   product even has one — otherwise the OVERVIEW/FEATURES divider
   below it lands at a different y-position per card. Always rendered
   now (see index.php/pricing-cards.php), so a product with no icon
   still reserves the same empty space rather than collapsing to 0. */
/* Mini banners, not icons (Wayne 2026-08-25: "put mini banners that fill
   the entire space where the icons are").

   This slot used to centre a small 56px-tall icon in a wide white box,
   which left most of the card's most prominent area empty. It now holds
   a purpose-drawn 2.85:1 banner filling the slot, framed so it reads as
   a deliberate image panel rather than bleeding into the card edges.

   The 8px padding plus the img's own 1px border IS the frame — that is
   why the height grew from 96px to 104px, keeping the visible image band
   at the original 96px rather than shrinking it. Sizing the img in
   percentages lets one CSS rule serve both this grid and the plan cards
   on category pages, which share this class at a different card width.

   Superseded here: the old max-width 140px / max-height 56px pair, which
   existed so a short-but-wide vendor logo (the WHM one, found live
   2026-08-16) would not render illegibly small. Every card now uses a
   banner drawn to this exact ratio, so there is no mixed-aspect artwork
   left for those caps to arbitrate. Restore them if bare logos ever
   return to this slot. */
/* The box follows the ARTWORK's ratio instead of cropping to a fixed
   height. Every banner is drawn 816x288 (2.83:1); the old fixed 104px
   height gave a content box of 345x88 on a phone, which is 3.92:1, so
   `object-fit: cover` was silently cutting 28% off the HEIGHT of every
   card banner — top and bottom equally. On artwork with a figure near
   the top (the Site Whiz mascot) that removed his head.

   aspect-ratio on the image, with the wrapper sizing to it, means the
   banner is never cropped at any card width. The fixed height existed
   so the OVERVIEW divider lined up across cards in a row; that still
   holds, because cards in a row share a width and therefore compute
   the same height from the same ratio.

   object-fit stays `contain` as a safety net: if a future banner is
   ever supplied at a different ratio it will letterbox against the
   card background rather than quietly lose its edges — visible, not
   silent. */
.pricing-card-image {
    height: auto;
    background: var(--color-box-background);
    display: flex; align-items: center; justify-content: center;
    padding: 8px;
    box-sizing: border-box;
}
.pricing-card-image img {
    width: 100%;
    height: auto;
    aspect-ratio: 816 / 288;
    max-width: none; max-height: none;
    object-fit: contain;
    display: block;
    border: 1px solid var(--color-box-background-divider);
    border-radius: 4px;
}
.pricing-card-head {
    background: var(--color-box-head-background); color: var(--color-box-head-foreground);
    padding: 1.25rem 1.5rem; text-align: center;
    transition: background-color 0.2s ease;
}
.pricing-card:hover .pricing-card-head { background: var(--color-primary-background); }
.pricing-card-head h3 { font-family: var(--font-heading); margin: 0; color: inherit; }
/* Specificity fight, same class of bug as .nebulous-footer .footer-column h3
   above: a vendor rule "body.template-nebulous h1, body.template-nebulous h2,
   body.template-nebulous h3, body.template-nebulous h4 { color: ... }" is
   MORE specific (2 elements + 1 class) than ".pricing-card-head h3" alone
   (1 element + 1 class), so it silently won and rendered this dark-on-gray
   instead of white. Two classes here beats it. */
.pricing-card .pricing-card-head h3 { color: #fff; }

/* Card titles: always two lines tall, never three.

   The heading inherited the theme's h3 size (27.2px), which fits about
   fourteen characters across a card. Product names run to 33 — so a row
   of cards showed titles on one, two and three lines, giving the coloured
   head three different heights (74 / 106 / 139px measured on the SSL
   category) and knocking every card below it out of alignment. Reported
   as "the text wraps and sets different heights, looks off".

   Fixed by making the longest title fit two lines rather than by
   truncating it or shrinking each card to its own content:

   - the size drops to 1.35rem, which fits ~19 characters a line, so all
     69 product and category names on the site — the longest is 33 —
     wrap to at most two;
   - min-height reserves both lines whether or not the second is used, so
     a one-word title and a three-word title occupy the same band;
   - the flex centring keeps a single line optically centred in that
     reserved space instead of sitting at the top of it;
   - text-wrap: balance splits a two-line title evenly rather than
     leaving one orphaned word underneath.

   Stated in em against the heading's own font-size, so changing the size
   above keeps the reserved height correct automatically. */
.pricing-card .pricing-card-head h3 {
    font-size: 1.35rem;
    line-height: 1.25;
    min-height: 2.5em;
    display: flex;
    align-items: center;
    justify-content: center;
    text-wrap: balance;
}
.pricing-card-features-label {
    background: var(--color-tertiary-background);
    border-top: 1px solid var(--color-box-background-divider);
    border-bottom: 1px solid var(--color-box-background-divider);
    text-align: center;
    font-weight: 700;
    text-transform: uppercase;
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    color: var(--color-heading);
    padding: 0.6rem;
}
.pricing-card-body { flex: 1; padding: 1.5rem; }
.pricing-card-body ul { list-style: none; margin: 0; padding: 0; }
.pricing-card-body li { padding: 0.4rem 0; border-bottom: 1px solid var(--color-box-background-divider); font-size: 0.92rem; color: var(--color-primary-background); font-weight: 600; }
.pricing-card-body li:last-child { border-bottom: none; }
.pricing-card-footer { text-align: center; }
.pricing-card-price {
    display: block;
    background: var(--color-box-footer-background);
    border-top: 1px solid var(--color-box-footer-background-border);
    padding: 1rem 1.5rem;
    font-family: var(--font-heading); font-size: 1.6rem; font-weight: 700; color: var(--color-heading);
}
.pricing-card-price-term { font-size: 0.95rem; font-weight: 400; color: var(--color-body); }
/* A real border between the price and buttons, not one shared gray
   block — matches the reference: gray price bar, then a divider line,
   then the buttons on their own white row. */
.pricing-card-buttons {
    display: flex; gap: 0.6rem; justify-content: center;
    background: var(--color-box-background);
    border-top: 1px solid var(--color-box-background-divider);
    padding: 1.25rem 1.5rem;
}
.pricing-card-buttons .button1 { flex: 1; max-width: 130px; padding: 0.6rem 0.5rem; font-size: 0.85rem; }

/* ---- Product detail page (public/product.php) ---- */

.breadcrumb { color: var(--color-body); font-size: 0.9rem; margin-bottom: 1.5rem; }
.breadcrumb a { color: var(--color-body); }
.product-detail {
    display: grid; grid-template-columns: minmax(0, 340px) 1fr; gap: 2.5rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 2rem; margin-bottom: 2.5rem;
}
.product-detail-media { display: flex; align-items: center; justify-content: center; background: var(--color-box-head-background); border-radius: var(--border-radius-default); padding: 1.5rem; }
.product-detail-media img { max-width: 100%; max-height: 220px; width: auto; object-fit: contain; }
/* A product with no image_path renders no .product-detail-media at
   all, but the grid still had a fixed 340px first column reserved —
   the lone .product-detail-info child landed IN that narrow column
   instead of spanning the full width (headings wrapped oddly, huge
   blank gap on the right). :only-child lets it span both tracks. */
.product-detail-info:only-child { grid-column: 1 / -1; }
/* A second, larger image (products.screenshot_path) shown right below
   the hero banner (products.image_path) — distinct from image_path's
   own banner-background role. */
.product-detail-screenshot {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1.5rem; margin: 2rem 0 2.5rem; text-align: center;
}
.product-detail-screenshot img { max-width: 100%; border-radius: var(--border-radius-default); }
.product-detail-info h1 { margin-top: 0; }
.product-detail-price { font-family: var(--font-heading); font-size: 2rem; font-weight: 700; color: var(--color-primary-background); margin: 0.25rem 0 1.25rem; }
.product-detail-features { list-style: none; margin: 0 0 1.5rem; padding: 0; }
.product-detail-features li { padding: 0.5rem 0; border-bottom: 1px solid var(--color-box-background-divider); }
.product-detail-features li:last-child { border-bottom: none; }
.product-detail-form { display: flex; align-items: flex-end; gap: 1rem; margin-bottom: 0.5rem; }
.product-detail-form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.85rem; }
.product-detail-form input[type="number"] { width: 5rem; }

/* The SSL order form stacks; it cannot share the row layout above.

   .product-detail-form is display:flex in a ROW, which is right for what
   it was built for — a quantity box beside an Add to cart button. The
   SSL form carries four things instead: a domain field, a ten-row CSR
   textarea, an explanatory note and the button. Flexed into one row they
   each got a narrow column, so the label "CSR (Certificate Signing
   Request)" broke over three lines, the textarea rendered about 100px
   wide with the placeholder wrapping mid-word, and "Add to cart" stacked
   into three lines inside its own button. Reported as "this looks off",
   and it was every element at once rather than any single rule.

   A CSR is a block of base64 the customer pastes, so the textarea is the
   one control on the page that genuinely needs full width and a
   monospace face. */
.product-detail-form-ssl {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    max-width: 44rem;
}
.product-detail-form-ssl label {
    font-size: 0.9rem;
    font-weight: 600;
    gap: 0.45rem;
}
.product-detail-form-ssl input[type="text"] {
    max-width: 24rem;
    width: 100%;
}
.product-detail-form-ssl textarea {
    width: 100%;
    min-height: 11rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.82rem;
    line-height: 1.45;
    resize: vertical;
}
.product-detail-form-ssl .note {
    margin: 0;
    font-size: 0.88rem;
    color: var(--color-body);
}
/* Sized to its own label rather than stretched by align-items:stretch —
   the same fault the plan table's Order Now button had. */
.product-detail-form-ssl button {
    align-self: flex-start;
    white-space: nowrap;
}
@media (max-width: 700px) {
    .product-detail { grid-template-columns: 1fr; }
}

/* Warning shown on product.php when a 2nd+ provisioning product is
   about to be added while one is already in the cart — the visible
   banner itself is the "prompt" (this app has no confirm-dialog
   infra), not a blocking modal. */
/* GREY TEXT ON A NEAR-BLACK BOX, and neither was chosen.
 *
 * This took --color-box-head-background, which is a DARK token meant
 * for a panel's header band (#262626 here, #000000 in the vendor
 * sheet), and set no text colour at all — so the words inherited
 * --color-body, a mid grey meant for body copy on white. The result was
 * #696969 on #262626 sitting in the most important position on the
 * page: the message telling someone their cart already has hosting in
 * it. Reported as "greyed out text", which is exactly what it was.
 *
 * It is now what it always should have been — a light attention panel
 * with its own explicit foreground, matching the warning treatment used
 * on the Configure step. Both colours are literal rather than tokens
 * because this panel must stay readable regardless of which theme
 * palette is loaded around it; a token that is dark in one sheet and
 * light in another is how it broke in the first place.
 */
.provisioning-multi-warning {
    border: 1px solid #d98324;
    border-left-width: 4px;
    border-radius: var(--border-radius-default);
    background: #fdf3e3;
    color: #6b4310;
    padding: 1rem 1.25rem;
    margin-bottom: 1.25rem;
}
.provisioning-multi-warning .note,
.provisioning-multi-warning p {
    margin: 0;
    color: #6b4310;
}
.provisioning-multi-warning strong { color: #4a2e08; }
.provisioning-multi-warning-actions { margin-top: 0.75rem; }

/* ---- Configurable Options panel (product.php, WHMCS 9.0 parity Phase 3) ---- */

.configurable-option-group {
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
}
.configurable-option-group legend { font-weight: 600; padding: 0 0.4rem; }
.configurable-option-label { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: 0.6rem; font-size: 0.9rem; }
.configurable-option-label:last-child { margin-bottom: 0; }
.configurable-option-select { padding: 0.4rem 0.6rem; border-radius: 4px; border: 1px solid var(--color-box-background-border); }

/* ---- Billing-cycle picker (product.php, when a product has 2+ variants) ---- */

.billing-cycle-picker { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1.25rem; width: 100%; }
.billing-cycle-option {
    position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
    flex: 1; min-width: 120px;
    background: var(--color-box-background);
    border: 2px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 0.85rem 0.75rem;
    cursor: pointer;
    transition: border-color 0.15s ease, transform 0.15s ease;
}
.billing-cycle-option:hover { transform: translateY(-2px); }
.billing-cycle-option:has(input:checked) { border-color: var(--color-primary-background); box-shadow: var(--shadow); }
.billing-cycle-option input[type="radio"] { position: absolute; top: 0.6rem; right: 0.6rem; margin: 0; }
.billing-cycle-name { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-body); }
.billing-cycle-price { font-family: var(--font-heading); font-size: 1.15rem; font-weight: 700; }
.billing-cycle-save {
    font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em;
    color: var(--color-primary-foreground); background: var(--color-primary-background);
    border-radius: 999px; padding: 0.1rem 0.5rem; margin-top: 0.15rem;
}

/* ---- Content blocks (Features / Why Choose Us) — alternating image/text ---- */

/* ---- Data tables ----
   .cart-table is styled in assets/css/app.css, but that file is never
   loaded while a discovered template is active: partials/header.php
   hands the whole document to the template and returns before its
   <link> is reached (see that file's own multi-template dispatch note).
   So every .cart-table on the storefront rendered with zero cell
   padding and no rules — the domain results, and roughly ten
   account/*.php pages that use the same class. Found 2026-08-25 while
   Wayne was looking at the domain results specifically.
   Values match app.css's own so the two themes agree. */
.cart-table { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; }
.cart-table th,
.cart-table td {
    text-align: left;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--color-box-background-border);
    vertical-align: middle;
}
.cart-table thead th {
    background: var(--color-box-head-background);
    color: #fff;
    font-family: var(--font-heading);
    font-size: 0.85rem;
    letter-spacing: 0.02em;
}
.cart-table tbody tr:last-child td { border-bottom: none; }
.cart-table tbody tr:hover { background: rgba(240, 149, 70, 0.05); }

/* ---- Domain search results (domain-search.php) ----
   The results table previously inherited .cart-table with no styling of
   its own, so an availability lookup rendered as a bare grid on a white
   page. These give it the same card treatment the rest of the storefront
   uses, and stop the per-row "Years + Add to cart" form stacking. */
.domain-results {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    overflow-x: auto;
    margin-bottom: 2.5rem;
}
.domain-results table { margin: 0; width: 100%; }
.domain-results-name { font-family: var(--font-heading); font-weight: 600; }
/* .product-detail-form's flex column default would stack the label above
   the button; this row wants them side by side and vertically centred. */
.domain-results-add {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin: 0;
}
.domain-results-add label { display: flex; align-items: center; gap: 0.4rem; margin: 0; white-space: nowrap; }
.domain-results-add select { width: auto; min-width: 4.5rem; }

/* domain-search.php reuses the product hero's banner markup, but has no
   purchase card in it — so the headline panel is the only thing in the
   left column and would otherwise sit oddly small beside the snapshot. */
.category-hero-banner-content--split .category-hero-banner-text:only-child { flex: 1 1 100%; }

/* ---- Single-product spotlight (partials/addon-spotlight.php) ---- */

.addon-spotlight {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: 2.5rem;
    /* Tight on purpose. Measured 2026-08-25 on a 1758x864 viewport:
       the 390px banner plus this section at its first size (354px tall
       with 40/48 margins) came to 832px, so the fold was entirely used
       up before a single line of page content — Wayne flagged it as too
       tall. */
    margin: 2rem 0 2.5rem;
}

/* min-width: 0 on both tracks. A grid item containing a replaced
   element takes an automatic minimum equal to that element's intrinsic
   width, which beats fr sizing and shoves the other column to a sliver
   — the same trap already documented on .nebulous-alt-block below,
   which is where this rule comes from rather than being rediscovered. */
.addon-spotlight-media,
.addon-spotlight-copy { min-width: 0; }

/* The screenshot reuses the product hero's white browser-window frame
   rather than a second treatment for the same idea, so it inherits the
   flush-to-the-edges fix made there. Width is released because the
   frame's own 620px cap is sized for the hero's narrower column. */
.addon-spotlight-media .category-hero-banner-screenshot {
    flex: none;
    /* 480, not the column's full 585. The media column was the taller
       of the two (354px against the copy's 283px), so it alone set the
       section height while leaving the copy vertically centred in dead
       space. Sizing it nearer the copy balances the pair and takes
       ~60px off the section. */
    max-width: 480px;
    margin-left: auto;
}

/* The hero's own rule pins this image to height:320px with object-fit:
   cover, which is right there — that slot is a fixed 596x320 box. Here
   the frame is fluid, so a fixed height would crop rather than scale.
   Let it follow the image's own 1.86:1 instead. */
.addon-spotlight-media .category-hero-banner-screenshot img {
    height: auto;
    object-fit: fill;
}

.addon-spotlight-copy h2 {
    font-family: var(--font-heading);
    text-align: left;
    margin: 0 0 1rem;
    font-size: 2rem;
    line-height: 1.15;
}

.addon-spotlight-badge {
    display: inline-block;
    background: var(--color-badge-hot-background);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.3rem 0.7rem;
    border-radius: 3px;
    margin-bottom: 0.9rem;
}

.addon-spotlight-lead { font-size: 1.05rem; line-height: 1.6; margin: 0 0 1.5rem; }

.addon-spotlight-buy {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem 1.75rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--color-box-background-border);
}
.addon-spotlight-price { font-family: var(--font-heading); font-size: 1.9rem; font-weight: 700; line-height: 1; }
.addon-spotlight-price-from { display: block; font-family: var(--font-body); font-size: 0.75rem; font-weight: 400; text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-body); margin-bottom: 0.3rem; }
.addon-spotlight-price-term { font-size: 0.95rem; font-weight: 400; color: var(--color-body); }
.addon-spotlight-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }

@media (max-width: 900px) {
    .addon-spotlight { grid-template-columns: 1fr; gap: 2rem; }
    /* Copy first on narrow screens — the reader wants to know what it
       is before they're shown a screenshot of it. */
    .addon-spotlight-media { order: 2; }
    .addon-spotlight-copy { order: 1; }
}

/* ---- Feature tiles ----
   These were bare centred text on a white page: a heading and a
   paragraph with nothing holding them together, which is most of why
   the Addons pages read as plain. Same content, now in real tiles with
   a tinted icon disc. */
.content-block-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 2.5rem; }
.content-block {
    text-align: left;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1.75rem 1.6rem;
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.content-block:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow);
    border-color: rgba(240, 149, 70, 0.55);
}
/* The icon column holds an emoji today (content_blocks.icon — real
   UTF-8, e.g. F09FAA84 is a wand; a console showing "?" is the
   console's font, not bad data). Sized as a disc so a one-character
   emoji and a Font Awesome glyph both sit correctly if that column is
   ever migrated. */
.content-block-icon {
    width: 2.75rem;
    height: 2.75rem;
    margin-bottom: 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    line-height: 1;
    border-radius: 50%;
    background: rgba(240, 149, 70, 0.12);
    border: 1px solid rgba(240, 149, 70, 0.35);
    color: #f09546;
}
.content-block h3 { font-family: var(--font-heading); margin: 0 0 0.5rem; text-align: left; font-size: 1.12rem; line-height: 1.3; }
.content-block h4 { font-family: var(--font-heading); font-weight: 400; color: var(--color-primary-background); margin: 0 0 0.5rem; text-align: left; }
.content-block p { color: var(--color-body); font-size: 0.92rem; margin: 0; }

.nebulous-alt-block { display: flex; align-items: center; gap: 2.5rem; margin-bottom: 2.5rem; }
.nebulous-alt-block.reverse { flex-direction: row-reverse; }
/* min-width:0 is required here — a flex item that's a replaced element
   (img) defaults to an automatic min-width equal to its OWN intrinsic
   size, which silently wins over flex-shrink and max-width:100%,
   crushing the text sibling down to a sliver instead of an even
   50/50 split. Found live 2026-08-17 (text column measured 191px vs
   the image's 931px on a 1162px-wide row). */
/* max-height stops a small source image (e.g. a 300x296 logo) from
   being upscaled past its own resolution just to fill the 50% column
   — found live 2026-08-17 rendering the R1Soft logo at 553px tall
   from a 296px-tall source. object-fit keeps it centered/undistorted
   inside that cap; real screenshots (cPanel) are already smaller than
   this cap at their natural render size, so they're unaffected. */
/* 420px, up from 320 (Wayne 2026-08-25: "images are too small"). The
   sources are 700px square, so 320 was throwing away more than half of
   them. border-radius is dropped now the artwork is a transparent PNG:
   there is no longer a rectangle to round off, and rounding a
   transparent image just clips its corners. */
.nebulous-alt-block img { max-width: 100%; max-height: 420px; min-width: 0; object-fit: contain; flex: 1; }
.nebulous-alt-block-text { flex: 1; text-align: left; }
/* Explicit left-align — the vendor CSS's global heading rule defaults
   to center (same recurring bug already documented for the pricing-
   card header and footer-column headings above), which silently won
   here too since these headings had no override of their own. */
.nebulous-alt-block-text h3 { margin: 0 0 0.25rem; text-align: left; }
.nebulous-alt-block-text h4 { margin: 0 0 0.75rem; color: var(--color-primary-background); font-family: var(--font-heading); font-weight: 400; text-align: left; }
.nebulous-alt-block-text p { text-align: left; }
.nebulous-alt-block-checklist {
    list-style: none;
    margin: 0 0 1rem;
    padding: 0;
    /* `columns: 2` (what this was) is a fixed count at every width, so
       it kept splitting an already-narrow text column in half: measured
       154px per column at 768px and 171px at 412px, which wraps
       "File manager and FTP accounts" across three lines in a strip
       narrower than a business card.

       column-width instead of column-count makes the browser fit as
       many columns as there is room for — two on a desktop half-width
       column, one on a phone or tablet — with no breakpoint at all.
       That is why this lives here rather than in the responsive block:
       it removes the need for a media query instead of adding one, and
       it is correct at widths nobody has tested. */
    columns: 15rem 2;
    column-gap: 1.5rem;
}
.nebulous-alt-block-checklist li {
    position: relative;
    padding: 0.3rem 0 0.3rem 1.6rem;
    font-size: 0.92rem;
    color: var(--color-body);
    break-inside: avoid;
}
.nebulous-alt-block-checklist li::before {
    content: "\2713";
    position: absolute;
    left: 0;
    top: 0.3rem;
    width: 1.1rem;
    height: 1.1rem;
    line-height: 1.1rem;
    text-align: center;
    font-size: 0.7rem;
    font-weight: 700;
    color: #fff;
    background: var(--color-badge-new-background);
    border-radius: 3px;
}

/* ---- Testimonials ---- */

.testimonial-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; margin-bottom: 2.5rem; }
.testimonial-card { background: var(--color-box-background); border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); padding: 1.5rem; }
.testimonial-card p { font-style: italic; margin: 0 0 0.75rem; }
.testimonial-card .testimonial-author { font-weight: 700; color: var(--color-heading); font-style: normal; }
.testimonial-card img { width: 3rem; height: 3rem; border-radius: 50%; object-fit: cover; margin-bottom: 0.75rem; }

/* ---- FAQs ---- */

.faq-section { display: grid; grid-template-columns: minmax(220px, 1fr) minmax(0, 1.6fr); gap: 3rem; align-items: start; margin-bottom: 2.5rem; }
.faq-panel h3 { font-family: var(--font-heading); color: var(--color-heading); margin: 0 0 0.75rem; font-size: 1.5rem; }
.faq-panel p { color: var(--color-body); margin: 0 0 1.25rem; }

.faq-list { border: 1px solid var(--color-border); border-radius: var(--border-radius-default); overflow: hidden; }
.faq-list details { border-bottom: 1px solid var(--color-border); }
.faq-list details:last-child { border-bottom: none; }
.faq-list summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1rem 1.25rem;
    font-weight: 600;
    color: var(--color-heading);
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary::before {
    content: "+";
    flex: 0 0 auto;
    width: 1.5rem;
    height: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    border-radius: 50%;
    font-size: 0.95rem;
    line-height: 1;
    color: var(--color-body);
    transition: color 0.15s ease, border-color 0.15s ease;
}
.faq-list details[open] summary { color: var(--color-primary-background); }
.faq-list details[open] summary::before {
    content: "\2212";
    color: var(--color-primary-background);
    border-color: var(--color-primary-background);
}
.faq-list details p { margin: 0; padding: 0 1.25rem 1.1rem calc(1.5rem + 0.85rem + 1.25rem); color: var(--color-body); }

@media (max-width: 780px) {
    .faq-section { grid-template-columns: 1fr; gap: 1.75rem; }
}

/* ---- SSL configuration page ---- */

/* The step between paying and owning. Deliberately plain: the customer
   is here to copy two blocks of text and follow four lines of
   instruction, so the page gets out of the way. The PEM boxes are
   monospaced and selectable rather than pretty — a certificate that
   wraps or smart-quotes is a certificate that will not install. */
.ssl-configure { max-width: 52rem; margin: 0 auto 2.5rem; }
.ssl-configure-domain {
    margin: 0 0 1.5rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--card-edge);
    color: var(--color-body);
}
.ssl-configure-status { color: var(--color-primary-background); font-weight: 600; }
.ssl-configure-notice {
    margin: 0 0 1.5rem;
    padding: 0.85rem 1.1rem;
    border-radius: var(--border-radius-default);
    border-left: 4px solid var(--color-badge-new-background);
    background: var(--color-box-background);
}
.ssl-configure-notice-error { border-left-color: #c0392b; }
.ssl-configure-step {
    background: var(--color-box-background);
    border: 1px solid var(--card-edge);
    border-radius: var(--border-radius-default);
    box-shadow: var(--card-lift);
    padding: 1.5rem;
    margin-bottom: 1.25rem;
}
.ssl-configure-step h3 {
    margin: 0 0 0.6rem;
    font-family: var(--font-heading);
    color: var(--color-heading);
    font-size: 1.2rem;
}
.ssl-configure-step p { margin: 0 0 0.85rem; color: var(--color-body); }
.ssl-configure-step p:last-child { margin-bottom: 0; }
.ssl-configure-pem {
    width: 100%;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    line-height: 1.4;
    white-space: pre;
    overflow-x: auto;
    resize: vertical;
}
.ssl-configure-list { margin: 0 0 0.85rem; padding-left: 1.2rem; color: var(--color-body); }
.ssl-configure-list li { margin-bottom: 0.4rem; }
.ssl-configure-form label { display: flex; flex-direction: column; gap: 0.4rem; font-weight: 600; margin-bottom: 0.75rem; }
.ssl-configure-form select { max-width: 24rem; }

/* "This is not the same thing as that" — the panel that tells a CSR
   from a certificate. Neutral rather than alarming: it is guidance
   before a mistake, not an error after one. */
.ssl-note-panel {
    background: var(--color-background);
    border: 1px solid var(--card-edge);
    border-radius: var(--border-radius-default);
    padding: 1rem 1.25rem;
    margin: 0 0 1rem;
}
.ssl-note-panel p:last-child { margin-bottom: 0; }
.ssl-note-panel code { word-break: break-all; }
.ssl-configure-optional { font-weight: 400; color: var(--color-body); }

/* The chosen file/DNS challenge — set apart from the step around it,
   because it is the one thing on the page the customer has to copy
   somewhere else exactly. */
.ssl-dcv-active {
    background: var(--color-background);
    border: 1px solid var(--card-edge);
    border-left: 4px solid var(--color-primary-background);
    border-radius: var(--border-radius-default);
    padding: 1.1rem 1.25rem;
    margin: 0 0 1rem;
}
.ssl-dcv-active h4 {
    margin: 0 0 0.6rem;
    font-family: var(--font-heading);
    color: var(--color-heading);
    font-size: 1.02rem;
}
.ssl-dcv-active code { word-break: break-all; }
.ssl-dcv-table { width: 100%; border-collapse: collapse; margin: 0 0 0.85rem; }
.ssl-dcv-table th,
.ssl-dcv-table td { text-align: left; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--card-edge); vertical-align: top; }
.ssl-dcv-table th { width: 5.5rem; color: var(--color-heading); font-weight: 600; }
.ssl-dcv-table td { word-break: break-all; }

/* The two email-free alternatives. Laid out as a row that wraps rather
   than stacked buttons, so neither reads as the primary action — email
   above it is still the default for most customers. */
.ssl-dcv-alternatives {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--card-edge);
}
.ssl-dcv-alternatives p { margin: 0; flex: 1 1 100%; color: var(--color-body); }
.ssl-configure-inline-form { margin: 0; }

/* ---- Trust CTA ---- */

.nebulous-trust-cta { text-align: center; background: var(--background-banner); color: #fff; border-radius: var(--border-radius-default); padding: 2.5rem 1.5rem; margin-bottom: 2.5rem; }
.nebulous-trust-cta h2 { color: #fff; margin: 0 0 1rem; }

/* ---- Domain search (public/domain-search.php's OWN results-page
   search form — a different, simpler element from the homepage's
   real .domainchecker section above, which vendor/css/layout.css
   already fully styles on its own). ---- */

/* align-items: flex-end (was: default stretch) — without it the button
   stretched to match the <label>'s full height (its "Domain name" text
   + input stacked), rendering as a tall slab instead of a normal-sized
   button next to the input, per Wayne's 2026-08-19 screenshot. Same
   fix as .discount-form's own alignment gap above. */
.domain-search-form { display: flex; align-items: flex-end; gap: 0.75rem; max-width: 40rem; margin: 0 auto 1.5rem; }
.domain-search-form label { flex: 1; margin: 0 !important; }
.domain-search-form input { margin: 0; }
.domain-search-form button { padding: 0.7rem 1.75rem; }

/* ---- Homepage .domainchecker overrides (domain-checker.php) ----
   Real fixes/additions on top of vendor/css/layout.css's own
   .domainchecker rules, not a rewrite of them. Two rounds of
   measurement on the live page (getBoundingClientRect/getComputedStyle/
   a canvas text-width probe) found the actual causes behind every
   symptom reported here — gap, misalignment, and truncated
   placeholder text all trace back to two real vendor-CSS quirks:
   1. The vendor ::before "www." pill OVERLAYS the input rather than
      sitting beside it (input's own box starts at the same left edge
      as the pill), so the input reserves 105px of left padding just
      to clear it — confirmed by input.left === wrap.left. That
      105px reservation alone was costing more room than the visible
      "www." pill actually needs, and was the real reason the
      placeholder text ("yourcompanywebsite.com") was rendering
      truncated as "yourcompany.co" even after widening the box.
      Replaced with a real `.domainsearch-www` span, a normal sibling
      that needs no such reservation.
   2. Browser default UA styles put a 4px margin on <input>, which
      neither Nebulous's vendor CSS nor this file zeroed — input/submit
      sat 4px lower than everything else beside them (confirmed via
      getComputedStyle().marginTop). Zeroed directly below. */

/* The vendor .domainchecker-columns-left/-right split is an even
   48%/48% (--column-2). At that split the right column (holding the
   search box) only had ~502px on this viewport — not enough room for
   www(85px, fixed) + Transfer(130px, fixed) + a genuinely readable
   text input + Search, confirmed via a canvas text-width probe
   (needed 236px usable, had 168px even after every other fix). The
   left column's heading/subheading text wraps fine at a narrower
   width, so it can afford to give room to the side that actually
   needs it. */
.domainchecker .domainchecker-columns-left { width: 36%; }
.domainchecker .domainchecker-columns-right { width: 62%; }

/* Vendor .domainchecker-columns carries its own top+bottom padding
   (--spacing-parent, ~50px) meant for when this section stands alone.
   Now that a .section-heading sits directly above it, that top padding
   stacks on top of the heading's own margin-bottom, making the gap
   look much bigger than every other section. Zero just the top side,
   scoped to only when a heading directly precedes it. */
.section-heading + .domainchecker .domainchecker-columns { padding-top: 0; }

.domainchecker .domainsearch { max-width: 760px; }

.domainchecker .domainsearch::before { display: none; }

.domainchecker .domainsearch-www {
    float: left;
    width: 85px;
    height: 65px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    font-weight: bold;
    border-radius: var(--border-radius-default) 0 0 var(--border-radius-default);
    /* Reads the SECONDARY token, not --color-box-head-background. It
       sits directly against the Search button, which is secondary — so
       when the secondary colour moved to the palette ink this prefix
       stayed on the vendor's #525252 and the two halves of one control
       no longer matched. Found by sweeping for the old grey rather than
       by looking at the search box. */
    background: var(--color-secondary-background);
    color: var(--color-secondary-foreground);
}

.domainchecker .domainsearch input { margin: 0; }

/* www span (85px, fixed) + Transfer (130px, fixed) both get real
   widths — the form (text input + Search) takes whatever remains, so
   the input keeps real room regardless of the container's overall
   width. Text input's own vendor padding-left (105px, reserved for
   the old pill-overlay design) is overridden down to a normal value
   now that nothing overlays it. */
.domainchecker .domainsearch-form { float: left; width: calc(100% - 85px - 130px); margin: 0; }
.domainchecker .domainsearch-form input[type="text"] { width: 74%; padding-left: 15px; }
/* .search.color2's own vendor rule sets a black border !important
   (--color-secondary-background-border: #000) — the only segment of
   the four with a visible outline. Matches the others (no border) by
   overriding at the same !important weight. */
.domainchecker .domainsearch-form input[type="submit"] { width: 26%; border-radius: 0; border: none !important; }

.domainchecker .domainsearch-transfer {
    float: left;
    width: 130px;
    height: 65px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2em;
    font-weight: bold;
    text-transform: uppercase;
    text-decoration: none;
    border-radius: 0 var(--border-radius-default) var(--border-radius-default) 0;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    border: solid 1px var(--color-primary-background-border);
    border-left: none;
}
.domainchecker .domainsearch-transfer:hover {
    background: var(--color-primary-active-background);
}

@media (max-width: 600px) {
    .domainchecker .domainsearch-form,
    .domainchecker .domainsearch-transfer { float: none; width: 100%; }
    .domainchecker .domainsearch-form input[type="text"],
    .domainchecker .domainsearch-form input[type="submit"] { width: 50%; }
}

/* Small colored bullet graphic before each TLD, not a claimed
   registry-brand logo — cycles a fixed palette by list position so
   it stays honest for whatever TLDs are actually configured. */
.domaintld-icon {
    display: inline-block;
    width: 0.6em;
    height: 0.6em;
    border-radius: 50%;
    margin-right: 0.4em;
    vertical-align: middle;
}
.domaintld:nth-child(6n+1) .domaintld-icon { background: #2563eb; }
.domaintld:nth-child(6n+2) .domaintld-icon { background: #f4b400; }
.domaintld:nth-child(6n+3) .domaintld-icon { background: #fb6340; }
.domaintld:nth-child(6n+4) .domaintld-icon { background: #e0245e; }
.domaintld:nth-child(6n+5) .domaintld-icon { background: #22a366; }
.domaintld:nth-child(6n+6) .domaintld-icon { background: #8b5cf6; }

/* ---- Footer ---- */

.nebulous-footer { background: #1e1e1e; color: #d7dde5; margin-top: 2rem; padding-left: 3rem; }
.nebulous-trustbar {
    /* .nebulous-footer's own padding-left: 3rem exists to clear the
       fixed .social-sidewidget sidebar for the columns below — the
       trust bar isn't meant to be inset by it at all (the reference
       shows it edge-to-edge), so cancel it back out here. */
    margin-left: -3rem; width: calc(100% + 3rem);
    background: #000; padding: 1rem 1.5rem; display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: center; text-align: center; font-weight: 600; color: #fff;
}
@media (max-width: 780px) { .nebulous-trustbar { margin-left: 0; width: 100%; } }
.nebulous-partners {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: 2.5rem; row-gap: 1.5rem;
    max-width: var(--container-maxwidth); margin: 0 auto;
    padding: 2rem 1.5rem; border-bottom: 1px solid rgba(255,255,255,0.1);
}
.nebulous-partners img { max-height: 60px; width: auto; opacity: 0.7; }
.site-footer-inner { max-width: var(--container-maxwidth); margin: 0 auto; padding: 3rem 1.5rem; display: grid; grid-template-columns: repeat(3, minmax(160px, 1fr)) minmax(260px, 1.6fr); gap: 2rem; }
.nebulous-footer .footer-column h3 {
    /* Higher specificity needed to beat "body.template-nebulous h1, h2,
       h3, h4 { color: var(--color-heading) }" — that dark navy heading
       color was silently winning here, rendering every footer column
       heading invisible (dark-on-dark) until caught live 2026-08-16. */
    color: #fff; font-family: var(--font-heading); font-size: 1rem; margin: 0 0 1rem;
    text-align: left; /* was inheriting a vendor "center" rule, out of line with the left-aligned links below it */
    /* Bold (Wayne 2026-08-25). These are <h3>, so they should have been
       bold by default — a vendor rule was flattening every heading to
       400, which left the column names sitting at the same visual
       weight as the links beneath them and gave the footer no
       hierarchy. */
    font-weight: 700;
}
/* A second heading inside an existing footer column ("Extras" under
   Domains, "Support" under Company, "Follow us" under About). It needs
   air above it or it reads as another link in the list above rather
   than the start of a new group — reported on both desktop and mobile.

   The selector has to be this specific, and this is the third instance
   of the same trap in this file: `.nebulous-footer .footer-column h3`
   above is (0,0,2,1) and uses the `margin` SHORTHAND, which resets
   margin-top to 0. A bare `.footer-column-subheading` (0,0,1,0) lost
   silently, so the rule looked present and did nothing. See the note
   on that rule and at line ~1186 for the same fight. */
.nebulous-footer .footer-column h3.footer-column-subheading { margin-top: 2rem; }
.footer-column a { display: flex; align-items: center; gap: 0.4rem; }
/* (The original `.footer-column a::before` chevron lived here. Removed:
   it was a DESCENDANT selector, so it also stamped a "›" onto the
   mailto and tel links inside the contact boxes further down. The
   replacement further down this file is scoped to direct children.) */
.footer-badge {
    display: inline-block; font-size: 0.62rem; font-weight: 700; line-height: 1;
    padding: 0.22rem 0.4rem; border-radius: 3px; color: #fff; letter-spacing: 0.03em;
}
.footer-badge-hot { background: #e53e3e; }
.footer-badge-new { background: #38a169; }
.footer-column a, .footer-column p { display: block; color: #d7dde5; text-decoration: none; font-size: 0.9rem; margin-bottom: 0.5rem; }
.footer-column a:hover { color: var(--color-primary-background); }

/* ---- Footer link rows: hairline-separated (Wayne 2026-08-25) ----
   Base rules deliberately, not inside a media query — the same
   treatment was asked for on desktop and mobile.

   `> a` (direct children only) matters here: the About column puts a
   mailto link inside a <p>, and a descendant selector would give that
   sentence a rule and a chevron too. .footer-about-more is the
   pill-shaped "More about us" button, excluded for the same reason. */
.footer-column > a:not(.footer-about-more) {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.62rem 0;
    margin-bottom: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.09);
}
/* Every row carries its own underline; this adds the line ABOVE the
   first row of a group so the group reads as enclosed rather than
   trailing off upward. `h3 + a` catches the first link after a
   heading AND after a subheading ("Extras", "Support"), so the second
   group in a column is bounded exactly like the first. */
.footer-column > h3 + a:not(.footer-about-more) {
    border-top: 1px solid rgba(255, 255, 255, 0.09);
}
/* Badges to the far edge, so a column of rows has one clean right
   margin instead of a ragged one that follows the label lengths. */
.footer-column > a .footer-badge { margin-left: auto; }
/* `> a` — DIRECT children only. As a descendant selector this also put
   a chevron on the mailto and tel links inside the contact boxes below
   (">sales@..." / ">+1 (604)..."), which are not menu items. Matches
   the row rule above, which was already scoped this way. */
.footer-column > a:not(.footer-about-more)::before {
    content: "\203A";
    color: rgba(255, 255, 255, 0.55);
    font-weight: 700;
    /* Was default size at 0.35 alpha, which read as a speck. */
    font-size: 1.45em;
    line-height: 1;
}
.footer-about-more { display: inline-flex; margin: 0.5rem 0 1.25rem; padding: 0.4rem 1.25rem; font-size: 0.82rem; }
.footer-info-boxes { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.25rem; }
/* Reversed out of the dark footer (Wayne 2026-08-25): a light panel
   with dark text, rather than near-invisible dark-on-dark. The old
   rgba(255,255,255,0.05) fill was only 5% lighter than the footer
   behind it, so the boxes barely read as boxes at all. */
.footer-info-box {
    display: flex; align-items: center; gap: 0.6rem;
    background: #d6d6d6; border-radius: var(--border-radius-default);
    padding: 0.62rem 0.8rem; font-size: 0.82rem;
    /* Set on the CONTAINER deliberately. `body.template-nebulous a`
       declares `color: inherit` at (0,0,1,2), which outranks any
       `.footer-info-box a` rule (0,0,1,1) — so colouring the link
       directly loses and it inherits the dark footer's pale text onto
       this pale panel (measured 1.06:1, invisible). Colouring the box
       means `inherit` resolves to the right value instead of fighting
       it. */
    color: #2c2c2c;
}
.footer-info-box svg { flex-shrink: 0; opacity: 1; color: #3a3a3a; }
.footer-info-box span { color: inherit; margin: 0; }
/* The two links here (mailto, tel) stay underlined so they still read
   as links now that they are no longer a different colour from the
   text beside them. */
.footer-info-box a {
    margin: 0;
    text-decoration: underline; text-underline-offset: 2px;
}
.footer-info-box a:hover { color: #7a4a12; }
@media (max-width: 900px) { .site-footer-inner { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); } }
.footer-social { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.footer-social a { display: flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; border: 1px solid rgba(255,255,255,0.25); border-radius: 50%; }
.footer-social a:hover { border-color: var(--color-primary-background); color: var(--color-primary-background); }
.site-footer-bottom { border-top: 1px solid rgba(255,255,255,0.15); text-align: center; padding: 1rem; font-size: 0.82rem; display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; align-items: center; justify-content: center; }
.nebulous-legal-links { display: flex; gap: 1rem; }
.nebulous-legal-links a { color: #d7dde5; text-decoration: none; }
.nebulous-legal-links a:hover { color: var(--color-primary-background); }
.nebulous-legal-badges { display: flex; gap: 0.75rem; align-items: center; }
.nebulous-legal-badges img { height: 24px; }

@media (max-width: 780px) { .nebulous-footer { padding-left: 0; } }

/* ---- Cart / checkout / account (reused generic markup) ---- */

/* Item cards (not a table) — matches Wayne's real WHMCS reference
   (globalwebconcepts.net/cart.php?a=view): one bordered white card per
   line item, name + inline domain-configure prompt at top-left, an
   icon-only trash button top-right, and a qty/price footer row. */
.cart-items { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1.5rem; }
.cart-item-card { background: var(--color-box-background); border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); padding: 1.1rem 1.25rem; }
.cart-item-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.cart-item-card-info h3 { margin: 0; font-size: 1rem; }
.cart-item-card-info h3 a { color: inherit; text-decoration: none; }
.cart-item-card-bottom { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-box-background-divider); }
.cart-item-qty-label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-body); margin-bottom: 0; }
.cart-item-qty-label input[type="number"] { width: 5rem; margin-top: 0; }
.cart-item-price { text-align: right; font-weight: 700; }
.cart-item-price-each { display: block; font-weight: 400; font-size: 0.78rem; color: var(--color-body); margin-top: 0.15rem; }
/* Icon-only remove — was a text "Remove {name}" pill per item, stacked
   in a separate row below the whole item table; now lives inline on
   each card's own top-right corner, matching the reference's trash
   icon. Square/flat per Style 3, red only on hover (quieter default
   than a solid-red pill, since it's now attached to every card). */
.cart-item-remove-icon {
    background: transparent !important;
    color: var(--color-body) !important;
    border: 1px solid var(--color-box-background-border) !important;
    border-radius: 3px !important;
    padding: 0.4rem !important;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
}
.cart-item-remove-icon:hover { background: #fdecec !important; color: #b3261e !important; border-color: #f3b4b0 !important; }
/* ---- Cart action buttons (Update cart / Apply / Remove / cross-sell
   add) — "Style 3: Sharp / Square, Uppercase" per Wayne's pick from
   the 6-option button-style comparison, 2026-08-19 — replaces the
   orange pill look everywhere on cart.php. !important needed for the
   same reason documented throughout this file: vendor's global
   `body.template-nebulous button { background:
   var(--color-primary-background); ... }` (specificity 0,1,2)
   outranks a bare single class (0,1,0). */
/* No visible content — its only job is to be the `form=` target for
   each card's qty input (see cart-item-qty-label above) and the no-JS
   Enter-to-submit fallback; cart.js's own AJAX auto-submit on `change`
   is the real path now that there's no visible "Update cart" button
   (matching the reference, which has no such button either). */
.cart-quantity-form { display: none; }
.cart-btn-primary {
    background: #101114 !important;
    color: #fff !important;
    border: none !important;
    padding: 0.7rem 1.5rem;
    border-radius: 3px !important;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
}
.cart-btn-primary:hover { background: #2a2c31 !important; }
.cart-btn-secondary {
    background: #fff !important;
    color: #101114 !important;
    border: 1.5px solid #101114 !important;
    padding: 0.65rem 1.4rem;
    border-radius: 3px !important;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
}
.cart-btn-secondary:hover { background: #101114 !important; color: #fff !important; }

/* Cart item's own "Configure domain →" prompt — a real link (not a
   <button>, no form to submit) styled as a call-to-action so an
   unconfigured item's next required step reads as clearly actionable
   as the cart's other buttons, not a plain quiet link easy to miss. */
.cart-btn-configure {
    display: inline-block;
    background: #1f9d55 !important;
    color: #fff !important;
    border: none !important;
    padding: 0.5rem 1.1rem;
    border-radius: 3px !important;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    text-decoration: none !important;
    cursor: pointer;
}
.cart-btn-configure:hover { background: #188046 !important; }

/* !important on margin-bottom: vendor's own `body.template-nebulous
   label { margin-bottom: 1rem; }` (0,1,2) outranks this bare
   `.discount-form label` (0,1,1) — without it the label kept its
   extra 1rem bottom margin, so align-items: flex-end lined the Apply
   button up with that invisible margin edge instead of the input's
   own visible bottom border, leaving the two looking misaligned. */
.discount-form { display: flex; align-items: flex-end; gap: 0.75rem; max-width: 320px; margin: 1.5rem 0; }
.discount-form label { flex: 1; margin-bottom: 0 !important; }
.cart-totals { text-align: right; max-width: 320px; margin-left: auto; }
.cart-totals .total { font-size: 1.2rem; font-weight: 700; }
.order-summary { background: var(--color-box-background); border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); padding: 1.25rem; margin-bottom: 1.5rem; }
.order-summary ul { padding-left: 1.25rem; }
.order-summary-item { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem; list-style: none; margin-left: -1.25rem; }
.order-summary-item span { flex: 1; }
.order-summary-item form { margin: 0; flex: none; }
/* The domain a hosting line is for, under its name. Its own line and
   quieter than the plan, because it is context for that row rather
   than a second thing being bought — an existing domain adds no charge
   and must not read like one.

   Scoped to body.template-nebulous (0,2,1): a cart skin can restyle
   .order-summary, and a bare class here would lose to it in exactly
   the way .configure-choice-card.is-active did. Verified in the
   browser rather than assumed. */
body.template-nebulous .order-summary-domain {
    display: block;
    font-size: 0.82rem;
    color: var(--color-body);
    opacity: 0.85;
    margin-top: 0.15rem;
}

/* Sidebar quantity editor (configure.js) — its own item row has 4
   children (name/qty/price/remove-form) instead of the simpler
   name+price span the generic `span { flex: 1 }` rule above assumes,
   so name/price need their own explicit flex behavior here. */
.order-summary-item-name { flex: 1 1 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.order-summary-item-qty { flex: none !important; display: flex; align-items: center; gap: 0.35rem; font-size: 0.8rem; color: var(--color-body); }
.order-summary-item-qty input { width: 3.5rem; padding: 0.25rem 0.4rem; }
.order-summary-item-price { flex: none !important; font-weight: 600; white-space: nowrap; }

/* cart.php's two-column layout: cart table + tabs on the left, a
   sticky order-summary sidebar (totals, checkout CTA, cross-sell) on
   the right — see CLAUDE.md's "Cart order-summary sidebar" section. */
.cart-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.cart-layout .order-summary { margin-bottom: 0; position: sticky; top: 1.5rem; }
.cart-layout .order-summary h2 { margin-top: 0; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-body); }
.order-summary-count { margin: -0.5rem 0 1rem; font-size: 0.85rem; color: var(--color-body); }
.cart-layout .cart-totals { max-width: none; text-align: left; margin: 0 0 1rem; }
.cart-layout .checkout-button { display: block; text-align: center; }
/* Style 3 treatment (see the cart action-button comment above) applied
   to the checkout CTA too, for consistency with Update cart/Apply. */
.checkout-button {
    display: flex !important;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    /* GREEN, not the near-black it was. Wayne 2026-09-03: "can we make
       that checkout button green?"
     *
       #197a43 rather than the #1f8a4c used elsewhere on the site for
       success states, and the difference is measured rather than
       stylistic. This button sets its own type at 0.8rem bold — about
       12.8px — which is below the 18.66px bold threshold for large
       text, so it needs 4.5:1 against white. #1f8a4c gives roughly
       4.49:1: close enough to look fine and still a fail. #197a43
       clears it at about 5.4:1 with no visible difference in hue.

       The !important flags are inherited from the rule this replaces,
       not added — `body.template-nebulous .checkout-button` paints
       every button with the theme's orange at (0,2,1), and this rule
       is (0,1,0). Removing them here would silently turn the button
       orange again. */
    background: #197a43 !important;
    color: #fff !important;
    border: none !important;
    border-radius: 3px !important;
    font-size: 0.8rem !important;
    font-weight: 700 !important;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.75rem 1.5rem !important;
}
.checkout-button:hover { background: #146334 !important; }
@media (max-width: 900px) {
    .cart-layout { grid-template-columns: 1fr; }
    .cart-layout .order-summary { position: static; }
}

/* Apply Promo Code / Estimate Taxes tabs (cart.php) */
.cart-tabs { margin: 1.5rem 0; }
.cart-tab-buttons { display: flex; gap: 0.5rem; border-bottom: 1px solid var(--color-box-background-border); margin-bottom: 1rem; }
/* !important: vendor's own global `body.template-nebulous button {
   background: var(--color-primary-background); ... }` outranks a bare
   .cart-tab-button on specificity — same "beat a broader vendor rule"
   fix already used for .clientdash-shortcuts button above. Without it
   the inactive/active tab buttons rendered as solid orange blocks,
   with the active tab's orange text on that same orange background
   fully invisible. */
.cart-tab-button { background: none !important; border: none !important; border-bottom: 2px solid transparent; padding: 0.6rem 0.25rem; margin-bottom: -1px; font-size: 0.85rem; font-weight: 600; color: var(--color-body) !important; cursor: pointer; }
.cart-tab-button.is-active { color: var(--color-primary-background) !important; border-bottom-color: var(--color-primary-background); }
.cart-tab-panel { display: none; }
.cart-tab-panel.is-active { display: block; }
.cart-tab-panel .discount-form { margin: 0; max-width: none; }
.cart-tax-estimate { font-size: 1.05rem; font-weight: 700; margin-bottom: 0.25rem; }

/* "Last Chance" cross-sell cards in the order-summary sidebar (cart.php) */
.cross-sell { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--color-box-background-border); }
.cross-sell h3 { margin: 0 0 0.75rem; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-body); }
.cross-sell-card { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--color-box-background-divider); }
.cross-sell-card:last-child { border-bottom: none; }
.cross-sell-card-info { display: flex; flex-direction: column; font-size: 0.85rem; }
.cross-sell-card-info span { color: var(--color-body); font-size: 0.8rem; }
.cross-sell-card form { margin: 0; }
/* Square, solid black with a white cart icon (was an orange circle,
   then a white/black-outline square) — matches the reference's filled
   black "add" button on each Last Chance card. */
/* !important on display/padding: without them the vendor's global
   `body.template-nebulous button { display: inline-block; padding:
   0.6rem 1.25rem; }` won on both — the button rendered as a big
   inline-block slab with the tiny 15px SVG lost somewhere in default
   left-aligned flow, reading as a plain black square with no visible
   icon (caught live, not by code review). */
.cross-sell-add { width: 2rem; height: 2rem; padding: 0 !important; border-radius: 3px !important; border: none !important; background: #101114 !important; color: #fff !important; display: inline-flex !important; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; }
.cross-sell-add:hover { background: #2a2c31 !important; }

/* Checkout page's Shipping / Domain Registrant / SSL Contact sections — same bordered-card treatment as .order-summary, just grouped per-topic instead of one flat form. */
.checkout-section { background: var(--color-box-background); border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); padding: 1.5rem; margin-bottom: 1.5rem; }
.checkout-section h2 { margin-top: 0; text-align: center; }

/* Narrower centered checkout column — was full site-main width, per
   Wayne's real WHMCS reference (globalwebconcepts.net/cart.php?a=checkout),
   2026-08-19. */
.checkout-form-wrap { max-width: 700px; margin: 0 auto; }

/* Icon-prefixed, placeholder-only fields (see checkout.php's
   checkout_field() helper + includes/functions.php's field_icon()) —
   2-column grid, a field spans both columns via .checkout-field-full
   (address lines, country, full name, email). Placeholder communicates
   the field's purpose visually, same as the reference; aria-label on
   the input itself keeps it labeled for assistive tech even though
   there's no visible caption. */
.checkout-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0.85rem; }
.checkout-field { position: relative; }
.checkout-field-full { grid-column: 1 / -1; }
.checkout-field-icon { position: absolute; left: 0.9rem; top: 50%; transform: translateY(-50%); color: var(--color-body); display: flex; pointer-events: none; }
.checkout-field input { margin-top: 0 !important; padding-left: 2.6rem !important; }
@media (max-width: 640px) {
    .checkout-fields { grid-template-columns: 1fr; }
}

/* Cart line item's inline domain-configuration prompt (public/cart.php) */
.cart-item-configure { margin-top: 0.35rem; font-size: 0.82rem; color: var(--color-body); }
.cart-item-configure a { margin-left: 0.35rem; }
.cart-item-configure-needed { color: var(--color-primary-background); font-weight: 600; }
.cart-item-configure-needed a { margin-left: 0; }

/* Configure step's 3-choice panel (public/configure.php) */
.configure-choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.25rem; margin-bottom: 2rem; }
/* A resting accent, not just a hover one.
   
   The hover wash was the only colour these carried, so at rest they
   were three plain white rectangles and the restyle looked like nothing
   had happened — which is exactly how Wayne read it, and fairly: you
   cannot see a hover state in a screenshot.
   
   The short orange top rule is the same mark the admin's System
   Settings cards and the client-area panels carry, so these now read as
   part of the same family without hovering. */
.configure-choice-card {
    position: relative;
    overflow: hidden;
    display: block; text-decoration: none; color: inherit;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1.5rem; transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
/* HIGHLIGHT: warm wash and an orange border, no lift.
   
   Same language as the mega-menu cards, and for the same reason the
   nudge came off those: a transform moves a card relative to its
   neighbours, and these three sit in a row where one rising reads as
   misalignment rather than feedback. Colour is enough.
   
   The wash values are the mega-menu's, so the two hover states match. */
.configure-choice-card:hover {
    background: #fef5ec;
    border-color: var(--color-primary-background);
    box-shadow: var(--shadow);
    transform: none;
}

/*
 * THE CARDS ARE NOW A SELECTOR, NOT A GATE. They stay on screen while
 * the chosen panel shows underneath, so one of them has to read as
 * currently selected or the panel below looks unattached to anything.
 *
 * SCOPED TO body.template-nebulous FOR SPECIFICITY, not for scope.
 * Written first as a bare .configure-choice-card.is-active (0,2,0) and
 * it lost: nexus-cart.css carries
 * body.cart-theme-nexus .configure-choice-card (0,2,1) setting the
 * background, and it loads after this file. The class landed on the
 * element and the card stayed white — the fourth time this exact
 * (0,2,1)-beats-a-bare-class trap has bitten in this codebase, and it
 * is invisible from the source: the rule is right there and simply
 * never wins.
 *
 * At (0,3,1) it beats every cart skin's own card rule regardless of
 * load order, which matters because the skin is admin-switchable.
 */
body.template-nebulous .configure-choice-card.is-active {
    background: #fef5ec;
    border-color: var(--color-primary-background);
    box-shadow: var(--shadow);
}

body.template-nebulous .configure-choice-card.is-active::before {
    opacity: 1;
}

body.template-nebulous.colortoggle-dark .configure-choice-card.is-active {
    background: #3a3a3a;
    border-color: var(--color-primary-background);
}

body.colortoggle-dark .configure-choice-card:hover {
    background: #3a3a3a;
    border-color: var(--color-primary-background);
}

/* TEXT: the heading was var(--color-primary-background) — brand orange
   at 17.6px, which is 2.31:1 on the card. It went unmeasured because
   the active template (Nexus) overrides it to a dark colour, and a
   sweep only ever sees the template that is switched on; the other
   eight inherit this and would all have failed.
   
   A card title is text, so it takes the heading colour. The orange
   stays on the border, where it marks without having to be read. */
.configure-choice-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 64px;
    height: 3px;
    background: var(--color-primary-background);
}

.configure-choice-card h2 {
    font-size: 1.1rem;
    margin: 0 0 0.5rem;
    color: var(--color-heading);
}

.configure-choice-card p { margin: 0; font-size: 0.88rem; color: var(--color-body); }
/* JS-driven dropdown (not a native <details>) — the panel is
   position:absolute so opening/closing it never reflows the rest of
   the page (a real bug reported live: the old <details> pushed the
   search results/order-summary column down every time it opened). */
.configure-ns-dropdown { position: relative; display: inline-block; vertical-align: top; }
.configure-ns-toggle {
    display: inline-flex; align-items: center; height: 100%;
    padding: 0 1rem; background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    font-weight: 600; font-size: 0.9rem; cursor: pointer; color: inherit;
}
.configure-ns-toggle:hover, .configure-ns-toggle[aria-expanded="true"] { border-color: var(--color-primary-background); }
.configure-ns-panel {
    position: absolute; top: calc(100% + 0.5rem); right: 0; z-index: 20;
    width: 280px; padding: 1rem 1.25rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    box-shadow: var(--shadow);
}
.configure-ns-panel[hidden] { display: none; }
.configure-ns-panel .note { margin-top: 0; }

/* Domain search results — "exact match" hero + a compact Suggested
   Domains list for the other active TLDs, matching the real WHMCS
   layout Wayne referenced. See domain-search-results.php's own
   docblock — this is a restyle of data already fetched (every active
   TLD, for a bare-word search), not a new suggestion algorithm. */
.domain-result-exact { margin: 1rem 0 1.5rem; padding: 1.25rem; background: var(--color-box-background); border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); }
.domain-result-exact-status { margin: 0 0 0.75rem; font-size: 1.05rem; }
.domain-result-exact-status.is-available { color: #1f9d55; }
.domain-result-exact-status.is-taken { color: var(--color-body); }
.domain-result-exact-status.is-warn { color: #b3261e; }
.domain-result-exact-form { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin: 0; }
.domain-result-exact-form label { display: flex; align-items: center; gap: 0.5rem; margin: 0; }

.domain-suggestions h3 { margin: 0 0 0.5rem; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-body); }
.domain-suggestions-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); overflow: hidden; }
.domain-suggestion-row { display: flex; align-items: center; justify-content: flex-end; gap: 0.75rem; padding: 0.65rem 1rem; border-bottom: 1px solid var(--color-box-background-border); background: var(--color-box-background); }
.domain-suggestion-row:last-child { border-bottom: none; }
.domain-suggestion-name { margin-right: auto; font-weight: 600; }
.domain-suggestion-price { color: var(--color-body); }
.domain-suggestion-row form { margin: 0; }
.domain-suggestion-add { padding: 0.35rem 0.9rem !important; font-size: 0.82rem !important; }

/* AI Domain Namespinning (WHMCS 9.0 parity Phase 4) — an expandable
   panel above the search form. Every result rendered inside it has
   ALREADY been checked through Domains::checkAvailability() by
   api/suggest-domain-names.php, so it reuses .domain-suggestions-list's
   own row markup/styling verbatim (see ai-domain-suggestions.php). */
.ai-namespin-panel { margin: 1rem 0; }
.ai-namespin-toggle {
    display: inline-flex; align-items: center; gap: 0.4rem;
    padding: 0.6rem 1.1rem; background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    font-weight: 600; font-size: 0.9rem; cursor: pointer; color: inherit;
}
.ai-namespin-toggle:hover, .ai-namespin-toggle[aria-expanded="true"] { border-color: var(--color-primary-background); }
.ai-namespin-body {
    margin-top: 0.75rem; padding: 1.25rem;
    background: var(--color-box-background); border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
}
.ai-namespin-body[hidden] { display: none; }
.ai-namespin-body label { display: block; margin-bottom: 0.75rem; font-weight: 600; font-size: 0.9rem; }
.ai-namespin-body textarea {
    display: block; width: 100%; margin-top: 0.4rem; padding: 0.6rem 0.75rem;
    border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default);
    font-family: inherit; font-size: 0.95rem; resize: vertical;
}
[data-ai-namespin-results] { margin-top: 1rem; }
[data-ai-namespin-results]:empty { margin-top: 0; }

/* Shared AJAX progress indicators — a small inline spinner appended to
   a clicked submit button (register-domain/existing-domain forms,
   which redirect on success so the spinner just needs to show until
   then), and a dimmed centered-spinner overlay for a whole region
   that's about to be replaced (cart-body swaps, domain search
   results) — see cart.js/configure.js. */
@keyframes hca-spin { to { transform: rotate(360deg); } }
.hca-spinner {
    display: inline-block; width: 14px; height: 14px; margin-left: 0.55em;
    border: 2px solid currentColor; border-right-color: transparent;
    border-radius: 50%; vertical-align: -2px;
    animation: hca-spin 0.6s linear infinite;
}
.hca-region-loading { position: relative; min-height: 60px; pointer-events: none; opacity: 0.5; transition: opacity 0.15s ease; }
.hca-region-loading::after {
    content: ''; position: absolute; top: 2.5rem; left: 50%; width: 32px; height: 32px;
    margin-left: -16px; border: 3px solid rgba(0, 0, 0, 0.15);
    border-top-color: var(--color-primary-background); border-radius: 50%;
    animation: hca-spin 0.7s linear infinite;
}
button[disabled] { cursor: not-allowed; }
.auth-shell, .landing, .account-panel {
    max-width: 480px; margin: 4rem auto; padding: 2rem;
    background: var(--color-box-background); border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
.auth-shell h1 { margin-top: 0; }
.auth-google-button {
    display: flex; align-items: center; justify-content: center; gap: 0.6rem;
    width: 100%; margin-bottom: 1.25rem;
}
.auth-divider { display: flex; align-items: center; gap: 0.75rem; margin: 1.25rem 0; color: var(--color-body); font-size: 0.85rem; }
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; height: 1px; background: var(--color-box-background-border); }
.auth-forgot-link { text-align: right; margin: -0.5rem 0 1rem; font-size: 0.85rem; }
.auth-2fa-code-input { font-size: 1.5rem; letter-spacing: 0.3em; text-align: center; }
/* The client area had no checkbox anywhere before the sign-in challenge
   needed one, so a bare `label > input[type=checkbox]` inherited the
   block layout every other label on these forms uses and stacked the
   box above its own text. */
.auth-trust-check {
    display: flex; align-items: center; gap: 0.5rem;
    margin: 0.75rem 0 1rem; font-size: 0.85rem; font-weight: 400;
    color: var(--color-body); cursor: pointer;
}
.auth-trust-check input[type="checkbox"] { width: auto; margin: 0; flex: 0 0 auto; }
.auth-recovery-details { margin-top: 1.25rem; font-size: 0.9rem; }
.auth-recovery-details summary { cursor: pointer; color: var(--color-body); }
.auth-recovery-details form { margin-top: 0.75rem; }

/* Real Nebulous toolbar icons (Contact/View Cart/Client Area) — no
   dedicated CSS spacing rule exists in vendor/css/layout.css for
   these (unlike the mega-menu icon slot), so real Wizard Panel must
   rely on plain inline icon+space markup; matched here the same way. */
.toolbarmain-text i, .toolbarmain-menu i { margin-right: 4px; }
.account-text-muted { color: var(--color-body); font-size: 0.85rem; }
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1.25rem; }
.product-card { display: block; padding: 1.25rem; background: var(--color-box-background); border: 1px solid var(--color-box-background-border); border-radius: var(--border-radius-default); color: var(--color-body); text-decoration: none; }
.product-card:hover { border-color: var(--color-primary-background); }
.product-card h2 { font-size: 1.05rem; margin: 0 0 0.5rem; }
.stock-warning { color: var(--color-red); }
.stock-note, .note { color: var(--color-body); font-size: 0.9rem; }
.notice { color: var(--color-primary-background); font-size: 0.9rem; }
p.error { color: var(--color-red); }
p.success { color: var(--color-green); }
.module-badge { display: inline-block; font-size: 0.75rem; padding: 0.15rem 0.55rem; border-radius: 999px; background: var(--color-tertiary-background); border: 1px solid var(--color-border); color: var(--color-body); }
.module-badge-ok { background: #dcfce7; border-color: #bbf7d0; color: #15803d; }
.module-badge-warn { background: #fef3c7; border-color: #fde68a; color: #92400e; }

/* ---- Client area home (account/dashboard.php) — built against a
   real screenshot of Wayne's own live WHMCS Client Area (Nebulous
   theme): dark navy panel headers, orange stat numbers, narrow
   sidebar. Uses Nebulous's real --color-heading/--color-primary-
   background tokens rather than literal hex, same discipline as the
   rest of this glue file. */

.clientdash { display: grid; grid-template-columns: 260px 1fr; gap: 1.5rem; align-items: start; margin-top: 2rem; }
.clientdash-sidebar, .clientdash-main { display: flex; flex-direction: column; gap: 1.25rem; }

.clientdash-card { background: #fff; border: 1px solid var(--color-border); border-radius: var(--border-radius-default); overflow: hidden; }
/* !important throughout this card-header block is deliberate — the
   vendor's own base `h1, h2, h3... { color: var(--color-heading);
   text-align: center; }` rule in layout.css otherwise wins despite
   lower specificity than it should (same "beat the vendor base style"
   posture already used site-wide, e.g. .color1's own !important
   declarations). Found live 2026-08-18: without it, both the card
   title and the pill link inside it silently rendered navy-on-grey,
   reading as "dimmed out". */
.clientdash-card h3 {
    margin: 0 !important; background: var(--color-secondary-background); color: #fff !important; padding: 0.65rem 1rem;
    font-size: 0.95rem; display: flex; align-items: center; justify-content: space-between; text-align: left !important;
    min-height: 2.75rem; box-sizing: border-box;
}
.clientdash-card-link { background: rgba(255, 255, 255, 0.2); color: #fff !important; padding: 0.25rem 0.6rem; border-radius: 4px; font-size: 0.8rem; text-decoration: none; }
.clientdash-card-link:hover { background: rgba(255, 255, 255, 0.32); }
.clientdash-card-body { padding: 1rem; }
.clientdash-block-btn { display: block; text-align: center; margin-top: 0.75rem; text-decoration: none; }

.clientdash-shortcuts { list-style: none; margin: 0; padding: 0; }
.clientdash-shortcuts li { border-top: 1px solid var(--color-border); }
.clientdash-shortcuts li:first-child { border-top: none; }
/* !important on background/color/border: vendor's own global
   `body.template-nebulous button { background: var(--color-primary-background); ... }`
   (nebulous.css's own generic submit-button styling) outranks this
   selector on raw specificity (0,1,2 vs 0,1,1) despite being the more
   generic rule — same "beat a broader vendor rule" posture used
   elsewhere in this file (.clientdash-card h3, .acct-hero-content h1).
   Without it the Logout <button> rendered as a solid orange block
   instead of a plain list row. */
.clientdash-shortcuts a, .clientdash-shortcuts button {
    display: flex !important; align-items: center; gap: 0.65rem; width: 100%; padding: 0.65rem 1rem; text-decoration: none; text-align: left;
    color: var(--color-heading) !important; background: none !important; border: none !important; font: inherit; cursor: pointer;
}
.clientdash-shortcuts a:hover, .clientdash-shortcuts button:hover { background: #f4f4f4 !important; }
.clientdash-shortcuts button.clientdash-shortcut-logout { color: var(--color-primary-background) !important; font-weight: 600; }
.clientdash-shortcut-icon { flex-shrink: 0; }

/* Leading icon + trailing (decorative, non-collapsing) chevron on a
   card header, matching the real WHMCS reference's panel-heading
   look — only added to .clientdash-card h3 elements that opt in with
   .clientdash-card-title, so existing plain-text headers are unaffected. */
.clientdash-card-title { display: flex; align-items: center; gap: 0.5rem; }
.clientdash-card-chevron { flex-shrink: 0; color: rgba(255, 255, 255, 0.7); }

.clientdash-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; }
.clientdash-stat {
    display: block; background: #fff; border: 1px solid var(--color-border); border-radius: var(--border-radius-default);
    padding: 1rem; text-align: left; text-decoration: none; color: inherit; transition: box-shadow 0.15s, border-color 0.15s;
}
a.clientdash-stat:hover { border-color: var(--color-primary-background); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); }
.clientdash-stat-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
.clientdash-stat-icon { color: #8a8a8a; flex-shrink: 0; }
.clientdash-stat-number { display: block; font-size: 2rem; font-weight: 700; color: var(--color-primary-background); line-height: 1; }
.clientdash-stat-label {
    display: block; margin-top: 0.5rem; padding-top: 0.4rem; border-top: 2px solid var(--color-primary-background);
    font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-body);
}

.clientdash-promo { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; }
.clientdash-promo-title { font-weight: 700; margin: 0 0 0.35rem; }
.clientdash-view-more { text-align: right; margin: 0.5rem 0 0; }

.clientdash-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
.clientdash-list { list-style: none; margin: 0; padding: 0; }
.clientdash-list li { padding: 0.5rem 0; border-top: 1px solid var(--color-border); }
.clientdash-list li:first-child { border-top: none; }

.clientdash-domain-form { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.clientdash-domain-form input { flex: 1; min-width: 140px; padding: 0.5em 0.75em; border: 1px solid var(--color-border); border-radius: var(--border-radius-default); }

@media (max-width: 900px) {
    .clientdash { grid-template-columns: 1fr; }
    .clientdash-row { grid-template-columns: 1fr; }
}

.clientdash-shortcuts a.active { background: #f4f4f4; font-weight: 700; }

/* ---- Account-area page-title band ---- Wayne's real WHMCS Client
   Area (globalwebconcepts.net) puts every account page under a
   full-bleed image band with a centered orange bold title + breadcrumb
   trail (WHMCS core's own .subbanner/.subbanner-heading/.breadcrumbmain
   markup). This app isn't running real WHMCS so that vendor CSS
   doesn't exist here — .acct-hero reproduces the same look using the
   real Nebulous background.jpg already bundled in vendor/images/, and
   joins the existing .category-hero-banner full-bleed breakout so it
   escapes .site-main.nebulous-main's own max-width/padding. */
.nebulous-slider,
.category-hero-banner,
.acct-hero {
    width: 100vw;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
}

/* Rendered as a sibling of <main> it is already the full width of the
   page, so the escape-the-container trick above must be undone — left
   alone it would shove the banner half a viewport sideways. This is the
   client-area case; invoice-confirmation.php still renders inside
   <main> and still needs the trick. */
.acct-hero.acct-hero-plain {
    width: auto;
    left: auto;
    right: auto;
    margin-left: 0;
    margin-right: 0;
}

.acct-hero {
    background-color: #2b2b2b;
    background-image: url('/templates/nebulous/vendor/images/background.jpg');
    background-size: cover;
    background-position: center;
    padding: 3rem 1.5rem;
}

.acct-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
}

.acct-hero-content { position: relative; z-index: 1; max-width: var(--container-maxwidth); margin: 0 auto; text-align: center; }
/* !important: vendor layout.css's own `h1, h2, h3... { color:
   var(--color-heading); text-align: center; }` base rule otherwise
   wins here despite lower specificity — the same "beat the vendor
   base style" fix already needed for .clientdash-card h3 (see nebulous.css
   history 2026-08-18); without it this rendered as near-illegible navy
   text on the dark photo background. */
/* Matches whether the banner renders an h1 (invoice-confirmation.php)
   or a div (the client-area shell, which keeps its only h1 for the page
   head) — the styling is the same either way. */
.acct-hero-content h1,
.acct-hero-content .acct-hero-title {
    color: var(--color-primary-background) !important; text-shadow: rgba(0, 0, 0, 0.4) 1px 1px;
    font-size: 2.5rem !important; margin: 0 !important; text-align: center !important;
    /* A div inherits none of the theme heading typography an h1 got for
       free, so state it — otherwise the banner renders noticeably
       lighter in the client area than on invoice-confirmation.php. */
    font-family: var(--font-heading, inherit);
    font-weight: 700;
    line-height: 1.2;
}
.acct-hero-breadcrumb { margin: 0.75rem 0 0; color: #fff; font-size: 0.9rem; }
.acct-hero-breadcrumb a { color: #fff; padding: 0 5px; }
.acct-hero-breadcrumb a:hover { text-decoration: underline; }

/* ---- Account-area data table (services/domains/invoices/tickets
   lists) — matches the dark DataTable header row in the same real
   reference pages. Scoped to .clientdash-card-body so it never touches
   .cart-table's own look used across cart/checkout. ---- */
.clientdash-card-body table.acct-table { width: 100%; border-collapse: collapse; }
.clientdash-card-body table.acct-table th {
    background: var(--color-secondary-background); color: #fff; text-align: left;
    padding: 0.65rem 1rem; font-size: 0.85rem; font-weight: 700;
}
.clientdash-card-body table.acct-table td { padding: 0.65rem 1rem; border-bottom: 1px solid var(--color-border); }
.clientdash-card-body table.acct-table tbody tr:hover { background: #f7f7f7; }
.clientdash-card-body table.acct-table tbody tr:last-child td { border-bottom: none; }
.clientdash-card-body table.acct-table td.acct-table-actions { display: flex; align-items: center; gap: 0.75rem; white-space: nowrap; }
.clientdash-card-body table.acct-table td.acct-table-actions form { margin: 0; }

/* ---- Cart progress bar / items-in-cart reminder ---- */

/* Flat light-gray strip with a thin bottom rule, "STEP N" bold next to
   a lighter step name, and only the ACTIVE step gets a shrink-wrapped
   charcoal box around just its own text (not the whole column) —
   Wayne's exact reference screenshot, 2026-08-19.

   This is now the ONE step-bar look for every page regardless of
   which cart template is active (Wayne's explicit call, 2026-08-19 —
   "I want all pages to align with this style") — !important on every
   property a cart-template stylesheet also sets, since those load
   AFTER this file and each carries a more specific
   `body.cart-theme-{key} .cart-steps...` selector that would
   otherwise win (e.g. Nexus's own solid-black ROUNDED pill,
   `border-radius: 999px`, was overriding this on the live site before
   !important was added here — caught live, not guessed). The 9
   cart-template CSS files' own `.cart-steps` blocks are left in place
   on disk, just fully superseded for this one component; nothing else
   about those templates (cards, colors elsewhere) is touched. */
.cart-steps {
    display: flex; list-style: none; margin: 0 0 2rem; padding: 0;
    background: var(--color-box-background) !important;
    border: none !important;
    border-bottom: 1px solid var(--color-box-background-border) !important;
    border-radius: 0 !important;
    gap: 0 !important;
    overflow: visible !important;
}
.cart-steps li {
    flex: 1;
    display: flex; align-items: center; justify-content: center;
    padding: 0.6rem 0.5rem !important;
    background: transparent !important;
    color: var(--color-body) !important;
    font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
    border-right: none !important;
    border-radius: 0 !important;
}
.cart-step-inner { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.7rem 1.5rem; border-radius: 4px; }
.cart-steps li.complete { background: transparent !important; color: var(--color-body) !important; }
.cart-step-number { font-size: 0.85rem; }
.cart-step-label { font-size: 0.78rem; opacity: 0.6; margin-left: 0.15rem; }
.cart-steps li.active { background: transparent !important; color: var(--color-body) !important; }
/* This one bar is shared by all nine cart templates by Wayne's
   2026-08-19 call, so it is the single place the checkout shows a
   state colour at all — every template's own accent is superseded
   here.

   IT IS NO LONGER THE BRAND ORANGE. The paragraph that used to sit
   here said "the active step wears the brand, not charcoal" and "dark
   label on the orange"; both stopped being true on 2026-09-03 when the
   fill went green, and a comment asserting the opposite of the code is
   how the next person puts the orange back. */
/* GREEN, matching the checkout button. Wayne 2026-09-03: "change the
   colour of the orange step counter to green."

   THE TEXT COLOUR HAD TO CHANGE WITH IT, which is the part that is
   easy to miss. The old pairing was #2c2d30 on the theme orange — dark
   ink on a light fill. The same dark ink on this green gives about
   2.1:1 and would have been unreadable; white on it gives 5.38:1.
   Swapping only the background would have produced a button that
   looked right in a screenshot and failed for anyone actually reading
   it.

   #197a43 is the same shade as the checkout button, chosen over the
   site's own #1f8a4c because that one measures 4.38:1 against white
   and this text is under the large-text threshold. The completed-step
   tick beside it already uses a green, so the component had the
   vocabulary for this before it was asked for. */
/* ONLY THE CURRENT STEP CARRIES THE FILL.
 *
   Filling the completed ones too was tried and reverted the same
   minute. This bar exists to answer "where am I", and three identical
   green blocks make the eye hunt for the current step — active and
   complete would then differ by a tick a few pixels wide. Done, here
   and to-come read better as three distinct states than as one fill
   spanning three items, which is also why Stripe and Shopify draw
   their checkouts this way.

   Completed steps keep the treatment they had: no fill, and a green
   tick that stays legible because there is no green behind it.

   PALE, NOT SOLID. Wayne 2026-09-03: "lighter green, like light pale
   green." A solid green was tried first and is the wrong weight here —
   this is a position indicator, not a call to action, and it sat
   heavier on the page than the button it appears above.

   THE INK FLIPS WITH THE FILL, which is the part that is easy to miss.
   A solid green needs white text; a pale green needs dark, or it
   becomes white-on-near-white. Both directions were measured rather
   than eyeballed:

       #2c2d30 (the old orange-era ink) on #d5ecdf   ~10:1   fine
       #ffffff (the solid-green ink)    on #d5ecdf    1.4:1   invisible
       #14532d (chosen)                 on #d5ecdf    7.33:1  comfortable

   #d5ecdf was already in this stylesheet, so the palette is not
   growing to satisfy one component. */
.cart-steps li.active .cart-step-inner { background: #d5ecdf !important; color: #14532d !important; }

/* The label is dimmed to 0.6 by default, which is fine as grey on the
   page but takes the dark ink to roughly 3.4:1 against this fill. */
/* 0.85, not the 0.6 the upcoming steps use: the label sits on a fill
   here and needs to hold its own against it. An `opacity: 0.9` rule
   was added just above this on 2026-09-03 and was dead on arrival —
   same selector, same specificity, one line earlier, so this one won
   and the new value never applied. Found by an audit of the block, not
   by looking at the page, because the two values are visually a
   hair apart. */
.cart-steps li.active .cart-step-label { opacity: 0.85; }
.cart-step-check { font-weight: 700; color: #15803d !important; margin-left: 0.15rem; }
@media (max-width: 640px) {
    .cart-steps { flex-direction: column; }
    .cart-steps li { border-right: none !important; border-bottom: 1px solid var(--color-box-background-border) !important; }
    .cart-steps li:last-child { border-bottom: none !important; }
}
/* ---- AI chat widget (chat-widget.js) — app.css's own #hca-chat-widget
   rules never load here (Nebulous owns the whole document once active,
   see StorefrontTemplate.php), so without this the toggle button
   rendered as a bare unstyled <button> at the end of the document
   flow — no fixed position, no icon, picking up plain body-level
   button styling — which is why it appeared as a stray orange text
   button below the footer instead of a floating corner bubble (found
   live 2026-08-16). Fixed bottom-right, round icon button, matching
   this template's own --color-* tokens. */
#hca-chat-widget { position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 1000; }
.chat-widget-toggle {
    display: flex; align-items: center; justify-content: center;
    width: 3.5rem; height: 3.5rem; padding: 0;
    background: var(--color-primary-background); color: var(--color-primary-foreground);
    border: none; border-radius: 50%; cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
.chat-widget-toggle:hover { background: var(--color-primary-active-background); }
.chat-widget-panel {
    position: absolute; right: 0; bottom: calc(100% + 0.75rem);
    width: 22rem; max-width: calc(100vw - 3rem);
    height: 28rem; max-height: calc(100vh - 8rem);
    background: var(--color-box-background); border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    display: flex; flex-direction: column; overflow: hidden;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}
.chat-widget-panel[hidden] { display: none; }
.chat-widget-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0.85rem 1rem; background: var(--color-box-head-background);
    color: var(--color-box-head-foreground); border-bottom: 1px solid var(--color-box-background-border);
    font-weight: 600;
}
.chat-widget-close { background: none; border: none; color: inherit; font-size: 1.25rem; line-height: 1; cursor: pointer; padding: 0.25rem; opacity: 0.8; }
.chat-widget-close:hover { opacity: 1; }
.chat-widget-messages { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.6rem; }
.chat-widget-message { max-width: 85%; padding: 0.55rem 0.8rem; border-radius: 0.6rem; font-size: 0.9rem; line-height: 1.4; white-space: pre-wrap; }
.chat-widget-message-user { align-self: flex-end; background: var(--color-primary-background); color: var(--color-primary-foreground); }
.chat-widget-message-assistant { align-self: flex-start; background: var(--background-body); color: var(--color-body); border: 1px solid var(--color-box-background-border); }
.chat-widget-message-system { align-self: center; background: transparent; color: var(--color-body); font-size: 0.8rem; text-align: center; }
.chat-widget-form { display: flex; gap: 0.5rem; padding: 0.75rem; border-top: 1px solid var(--color-box-background-border); background: var(--background-body); }
.chat-widget-form input[type="text"] { flex: 1; background: var(--color-box-background); border: 1px solid var(--color-border); border-radius: 0.4rem; color: var(--color-body); padding: 0.5rem 0.7rem; font-size: 0.9rem; }
.chat-widget-form button { background: var(--color-primary-background); color: var(--color-primary-foreground); border: none; border-radius: 0.4rem; padding: 0.5rem 1rem; font-weight: 600; cursor: pointer; }
.chat-widget-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }

/* ---- Dark color scheme toggle (uses Nebulous's own bundled .colortoggle-dark rules in layout.css) ---- */

/* Ai Site Whiz's Pages tab — nav highlight badge next to a category
   link in the mega-menu (see templates/nebulous/header.php). Uses
   real colors, not app.css's --hca-* tokens, since this template
   never loads app.css (see partials/header.php's dispatch comment). */
.nav-highlight-badge {
    display: inline-block;
    font-size: 0.65rem;
    text-transform: uppercase;
    font-weight: 700;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    color: #fff;
    margin-left: 0.35rem;
    vertical-align: middle;
}
.nav-highlight-hot { background: #dc1010; }
.nav-highlight-new { background: #45a523; }
.nav-highlight-popular { background: var(--color-primary-background, #f09546); }

/* Ai Site Whiz's Header tab — Main Menu "Sticky menu" toggle. No
   sticky mechanism existed in the bundled vendor CSS, so this is a
   real addition (see nebulous.js for the scroll listener that toggles
   this class), not just wiring something that already existed. */
.navmain-container[data-sticky-menu="1"].is-sticky-menu {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 999;
    background: var(--background-body, #fff);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
}

/* Ai Site Whiz's General tab — "Enable scroll animations" toggle.
   Animations already play unconditionally via animate.css classes
   baked into partials/hero-slides.php; this is the real off-switch,
   rendered as a body class from templates/nebulous/header.php. */
body.whiz-animations-off .animate__animated {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
}

/* Header dropdown mini-cart (mini-cart.js) — same position:absolute
   overlay pattern as .configure-ns-dropdown/.configure-ns-panel, so
   opening it never pushes the header/toolbar down. Opens on hover,
   matching the toolbar's own existing "My Account" submenu convention
   (.toolbarmain-menu ul li:hover ul in the vendor CSS) rather than a
   click-toggle — mini-cart.js only lazy-loads the panel's content. */
.mini-cart { position: relative; display: inline-flex; }
.mini-cart-panel {
    display: none;
    /* Centred on the cart link, not pinned to its right edge.
       It was `right: 0`, which right-ALIGNED the 320px panel against a
       ~116px link, putting the panel's centre 102px to the left of the
       trigger's — measured live, and reported as "off center". It was
       never told to centre; nothing drifted. */
    position: absolute; top: 100%; left: 50%; transform: translateX(-50%); z-index: 50;
    /* 320 -> 360. The card-on-ground treatment spends horizontal room
       on the ground's padding plus each card's own padding and border,
       which squeezed the product name against the price. Widening pays
       that back rather than letting names truncate earlier than before. */
    width: 360px; max-width: calc(100vw - 2rem); padding: 1rem 1.25rem;
    margin-top: 0.5rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    box-shadow: var(--shadow);
    text-align: left; color: var(--color-heading);
}
.mini-cart:hover .mini-cart-panel,
.mini-cart:focus-within .mini-cart-panel,
.mini-cart.is-open .mini-cart-panel { display: block; }
/* On a narrow screen the cart sits near the right edge, so a centred
   320px panel would hang off it. Right-alignment is the correct
   behaviour there — the very thing that was wrong on desktop. */
@media (max-width: 640px) {
    .mini-cart-panel { left: auto; right: 0; transform: none; }
}
/* Bridges the gap between the link and the panel above it so the
   panel doesn't close while the cursor crosses that gap. */
.mini-cart::after {
    content: ''; position: absolute; top: 100%; right: 0; left: 0; height: 0.5rem;
}
.mini-cart-loading, .mini-cart-empty { margin: 0; padding: 0.95rem 1.05rem; font-size: 0.85rem; }

/* ---------------------------------------------------------------
   Mini-cart panel — "Ink Receipt", built 2026-08-28.

   The cap is a light grey band with dark grey text and the count in
   accent orange — the same light-chrome language the admin uses. It
   was briefly the toolbar's own #222, which read as the bar
   continuing downward; the light cap trades that connection for a
   panel that stays bright against the hero and never competes with
   the orange button below it.

   The panel's own padding moved off .mini-cart-panel and onto
   .mini-cart-body, because the cap and the totals block both need to
   run full-bleed to their edges and could not while the parent held
   the padding.
   --------------------------------------------------------------- */
.mini-cart-panel { padding: 0 !important; overflow: hidden; }

.mini-cart-cap {
    display: flex; justify-content: space-between; align-items: center;
    background: #f1f3f5; color: #4a5769;
    border-bottom: 1px solid var(--color-box-background-border);
    padding: 0.5rem 1.05rem;
    font-size: 0.68rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.08em;
}
.mini-cart-cap b { color: var(--color-primary-background); font-weight: 700; }

/* The admin's card-on-ground composition, scaled down: a light ground
   with hairline-bordered cards sitting on it, rather than rows split by
   a divider. That pattern is most of why the admin panels read as
   finished, and here it costs one background and one border. */
.mini-cart-body { padding: 0.6rem 0.75rem 0.75rem; background: #f7f8fa; }

.mini-cart-items { list-style: none; margin: 0; padding: 0; max-height: 300px; overflow-y: auto; }
.mini-cart-item {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.5rem 0.55rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 8px;
    transition: box-shadow .14s ease;
}
.mini-cart-item + .mini-cart-item { margin-top: 0.4rem; }
.mini-cart-item:hover { box-shadow: 0 1px 3px rgba(16, 24, 40, 0.09); }

/* A category glyph, not a product photograph. Hosting plans have no
   image worth 40px, and the old thumbnail slot reserved that space on
   every row for a placeholder icon most of the time. */
.mini-cart-item-glyph {
    flex: none; width: 30px; height: 30px; border-radius: 7px;
    background: var(--color-box-background-divider); color: var(--color-heading);
    display: inline-flex; align-items: center; justify-content: center;
}
.mini-cart-item-glyph svg { width: 16px; height: 16px; }

.mini-cart-item-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.mini-cart-item-name {
    font-size: 0.79rem; font-weight: 600; line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mini-cart-term {
    align-self: flex-start;
    font-size: 0.62rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
    padding: 0.05rem 0.32rem; border-radius: 3px;
    background: var(--color-box-background-divider); color: var(--color-body);
}
/* Figures line up as a column, so two prices can be compared without
   reading them. */
.mini-cart-item-price {
    font-size: 0.79rem; font-weight: 600; white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.mini-cart-item-remove {
    flex: none !important; width: 20px !important; height: 20px !important;
    padding: 0 !important; border: none !important; border-radius: 50% !important;
    background: transparent !important; color: var(--color-body) !important;
    font-size: 1rem; line-height: 1 !important; cursor: pointer; opacity: 0.5;
    display: inline-flex !important; align-items: center; justify-content: center;
}
.mini-cart-item-remove:hover { background: #dc2626 !important; color: #fff !important; opacity: 1; }

/* The totals block runs full-bleed on its own tinted ground — the
   half of the panel that is a receipt rather than a list. */
.mini-cart-totals {
    /* Flush to the panel edge and on the panel's own surface, so it
       reads as the panel's footer rather than a third card. */
    margin: 0.6rem -0.75rem -0.75rem;
    padding: 0.65rem 0.95rem 0.85rem;
    background: var(--color-box-background);
    border-top: 1px solid var(--color-box-background-border);
}
.mini-cart-sum { display: flex; justify-content: space-between; align-items: baseline; font-variant-numeric: tabular-nums; }
.mini-cart-sum-due { font-size: 0.92rem; font-weight: 700; }
.mini-cart-renews { margin: 0.1rem 0 0; font-size: 0.7rem; color: var(--color-body); opacity: 0.8; }

.mini-cart-cta {
    display: block; text-align: center; margin-top: 0.7rem;
    padding: 0.6rem 1rem !important; border-radius: 8px;
    background: var(--color-primary-background); color: #fff !important;
    font-size: 0.8rem; font-weight: 700; letter-spacing: 0.02em; text-decoration: none;
}
.mini-cart-cta:hover { background: #d97b28; }
.mini-cart-view {
    display: block; text-align: center; margin-top: 0.45rem;
    font-size: 0.72rem; color: var(--color-body); text-decoration: underline;
}

/* Empty state. The panel you see most, since a cart is empty until it
   is not — so it gets a real composition rather than one sentence. */
.mini-cart-blank { text-align: center; padding: 1.4rem 1.05rem 1.2rem; }
.mini-cart-blank-glyph { display: inline-flex; color: var(--color-heading); opacity: 0.35; }
.mini-cart-blank-glyph svg { width: 30px; height: 30px; }
.mini-cart-blank-title { margin: 0.45rem 0 0; font-size: 0.85rem; font-weight: 600; }
.mini-cart-blank-sub { margin: 0.15rem 0 0; font-size: 0.74rem; color: var(--color-body); }

/* The panel lives inside the toolbar's own `.toolbarmain-menu ul li`
   structure (it IS one of those <li>s), and the vendor's
   `.toolbarmain .toolbarmain-menu ul li a { padding: 5px 10px; }` has
   higher specificity than any plain class here — so every <a> inside
   the panel needs the override, not just the old .mini-cart-actions
   ones. Same "vendor-base-style-wins" bug documented elsewhere in this
   file (e.g. .clientdash-card h3). */
.mini-cart-panel .mini-cart-view { padding: 0 !important; }

/* Dark mode partner. The cap is already near-black, so only the body
   surfaces move; the tinted totals block has to go DARKER than the
   panel here, where in light mode it goes darker-than-white by being
   grey — same relationship, opposite direction. */
body.colortoggle-dark .mini-cart-cap { background: #262626; color: #c8ced6; border-bottom-color: #383838; }
/* The ground has to go DARKER than the cards here, where in light mode
   it goes darker than white by being grey — same relationship, opposite
   direction. Get this backwards and the cards vanish into the ground. */
body.colortoggle-dark .mini-cart-body { background: #222222; }
body.colortoggle-dark .mini-cart-item { background: #2f2f2f; border-color: #3d3d3d; }
body.colortoggle-dark .mini-cart-item:hover { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); }
body.colortoggle-dark .mini-cart-item-glyph { background: #3a3a3a; }
body.colortoggle-dark .mini-cart-term { background: #3a3a3a; }
body.colortoggle-dark .mini-cart-totals { background: #2f2f2f; border-top-color: #3d3d3d; }

/* Invoice view (public/invoice-confirmation.php) — the real WHMCS
   viewinvoice.php-style layout Wayne asked for: logo/status banner,
   dates, Invoiced To/Pay To, a dark-header items table, and a Ledger.
   Lives inside the same .clientdash-card-body every other account
   page already uses, so .acct-table's own styling applies unchanged. */
.invoice-view-topbar { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.invoice-view-logo { max-height: 48px; max-width: 220px; }
.invoice-view-wordmark { font-family: var(--font-heading); font-size: 1.3rem; font-weight: 700; color: var(--color-heading); }
.invoice-view-status {
    display: inline-block; padding: 0.4rem 0.9rem; border-radius: var(--border-radius-default);
    font-weight: 700; font-size: 0.85rem; letter-spacing: 0.03em; white-space: nowrap;
}
.invoice-view-status-unpaid { background: rgba(220, 38, 38, 0.12); color: #dc2626; }
.invoice-view-status-payment_failed { background: rgba(220, 38, 38, 0.12); color: #dc2626; }
.invoice-view-status-paid { background: rgba(22, 163, 74, 0.12); color: #16a34a; }
.invoice-view-status-void { background: var(--color-tertiary-background); color: var(--color-body); }
/* Size and weight are stated because this is a <div> now, not an <h1>
   — the shell's page head carries the page's only h1, and a div
   inherits none of the theme heading typography. 2em/bold is what the
   theme gave an h1. */
.invoice-view-heading {
    font-family: var(--font-heading);
    color: var(--color-heading);
    margin: 0 0 0.5rem;
    font-size: 2em;
    font-weight: bold;
    line-height: 1.2;
}
.invoice-view-meta { display: flex; flex-wrap: wrap; gap: 0.4rem 1.5rem; margin: 0 0 1.25rem; color: var(--color-body); }
.invoice-view-meta p { margin: 0; }
.invoice-view-due-late strong { color: #dc2626; }
.invoice-view-payment-box { margin: 0 0 1.5rem; }
.invoice-view-payment-note { margin: 0 0 1.5rem; color: var(--color-body); }
.invoice-view-addresses {
    display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;
    padding: 1rem 0; margin-bottom: 1.5rem; border-top: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
}
.invoice-view-addresses h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-body); margin: 0 0 0.5rem; }
.invoice-view-addresses p { margin: 0; line-height: 1.6; }
.invoice-view-items, .invoice-view-ledger { margin-bottom: 1.5rem; }
.invoice-view-items h3, .invoice-view-ledger h3 { font-family: var(--font-heading); color: var(--color-heading); margin: 0 0 0.5rem; font-size: 1.1rem; }
/* A light header row, matching the email template and the PDF. This was
   a dark navy band (#222b3d), a colour that appears nowhere else in the
   identity — not in the client area, not in the email — and it is also
   the part of an invoice a customer is most likely to print. */
.invoice-view-items table thead tr, .invoice-view-ledger table thead tr {
    background: #f5f6f8;
    color: #243141;
}
.invoice-view-items table thead th, .invoice-view-ledger table thead th {
    color: #243141;
    border-bottom: 2px solid #e6e8ec;
}
.invoice-view-amount-col { text-align: right; }
.invoice-view-total-label { text-align: right; font-weight: 600; }
.invoice-view-items tfoot td, .invoice-view-ledger tfoot td { border-top: 2px solid var(--color-border); font-weight: 600; }
.invoice-view-actions { display: flex; gap: 0.75rem; }

/* ---------------------------------------------------------------
   Printing

   Printing an invoice from the website produced the sidebar and about
   a third of the invoice. Two separate causes, both worth naming:

   1. This block only hid `.clientdash-sidebar`, the OLD client area's
      class. The area was rebuilt on `.ca-shell` and the rules were
      never updated, so the current sidebar printed and the invoice was
      squeezed into a 250px-offset grid column.

   2. `.ca-card` carries `overflow: hidden` — it needs it on screen, so
      the accent rule clips to the rounded corner. An element with
      hidden overflow CANNOT break across printed pages: everything past
      the first page boundary is clipped away rather than continuing.
      That is the missing two-thirds, and it would have silently
      truncated any long invoice.
   --------------------------------------------------------------- */

@media print {
    /* The theme's REAL chrome classes. `.site-header` was in this list
       and matches nothing the Nebulous header renders — that header is
       `.headermain` plus `.toolbarmain-container`, and the nav is
       `.navmain-container`, so all three printed above every invoice.
       The floating widgets matter as much: a chat bubble or a dark-mode
       toggle is position:fixed, so it lands on top of the document. */
    .site-header, .utility-bar, .sidebar-social, .acct-hero, .clientdash-sidebar,
    .invoice-view-actions, .invoice-view-payment-box, .site-footer,
    .impersonation-banner, .mini-cart,
    .headermain, .toolbarmain-container, .navmain-container,
    .nebulous-color-toggle, #hca-chat-widget, .chat-widget-toggle,
    .ca-shell-nav, .ca-page-actions, .ca-tabs, .ca-btn, .ca-btn-row,
    .invoice-view-back { display: none !important; }

    /* One column, full width: the grid exists to place a sidebar that
       is no longer there. */
    .ca-shell {
        display: block;
        max-width: none;
        margin: 0;
        gap: 0;
    }

    .clientdash { display: block; margin-top: 0; }
    .clientdash-card { border: none; }

    body { background: #fff; }

    /* Paper margins here rather than the browser's default, which is
       wider than an invoice needs and was costing most of the room that
       pushed this onto a second sheet. */
    @page { margin: 12mm; }

    .ca-card {
        overflow: visible;          /* the fix for the truncation */
        box-shadow: none;
        border: 1px solid #e6e8ec;
        border-radius: 0;
        padding: 1rem 1.1rem 1.2rem;
        page-break-inside: auto;
        break-inside: auto;
    }

    /* The shell's page head repeats the invoice number that the document
       itself carries two lines below. On screen that is orientation; on
       paper it is the same words twice and 40px of height. */
    .ca-page-head { display: none !important; }

    /* Tightened for paper. None of this changes the screen. */
    .invoice-view-topbar { margin-bottom: 0.5rem; }
    .invoice-view-heading { margin-bottom: 0.35rem; font-size: 1.3rem; }
    .invoice-view-meta { margin-bottom: 0.75rem; gap: 0.2rem 1.25rem; }
    .invoice-view-addresses { margin-bottom: 0.9rem; gap: 1.25rem; }
    .invoice-view-items, .invoice-view-ledger { margin-bottom: 0.9rem; }
    .invoice-view-items h3, .invoice-view-ledger h3 { margin-bottom: 0.3rem; font-size: 1rem; }
    .invoice-view-payment-note { margin-bottom: 0.75rem; }
    .ca-table th, .ca-table td { padding-top: 0.35rem; padding-bottom: 0.35rem; }

    /* Keep the accent rule and the table header's tint. Browsers drop
       backgrounds when printing unless asked; these two are the whole
       visual identity of the document, and both are light enough to
       cost almost no ink. */
    .ca-card::before,
    .invoice-view-items table thead tr,
    .invoice-view-ledger table thead tr {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* Only the ROW is protected from splitting. An earlier version also
       put `page-break-inside: avoid` on td/th and restated the thead and
       tfoot display groups; the result was that a one-item invoice moved
       its whole table to page two and left page one half empty. Telling
       the engine to keep more together than necessary is how a document
       that fits on one page stops fitting. */
    tr { page-break-inside: avoid; break-inside: avoid; }

    .invoice-view-items table,
    .invoice-view-ledger table {
        page-break-inside: auto;
        break-inside: auto;
    }

    /* The heading belongs with the table it introduces. */
    .invoice-view-items h3,
    .invoice-view-ledger h3 {
        page-break-after: avoid;
        break-after: avoid;
    }
}

/* ---------------------------------------------------------------
   Product landing page (product.php, rich layout)

   Only applies to a product that has image-bearing feature blocks —
   see product.php's $richLayout. Every other product page is
   untouched by everything below.
   --------------------------------------------------------------- */

/* The hero no longer carries the purchase form, so it can be shorter
   and lighter than .category-hero-banner's default. */
.product-hero { padding: 3rem 1.5rem; }

.product-hero-content {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 2rem;
    align-items: center;
}

.product-hero-text { max-width: 40rem; }

/* The heading box was already flush left with everything under it — the
   vendor sheet centres the TEXT inside it, so the words sat indented
   while the eyebrow and lead below started at the container edge (Wayne
   2026-08-25: "didn't align to the left of the banner even with the text
   below"). Measured: container, eyebrow, h1 and lead all at x=388, but
   h1 text-align computed to center across a 595px box.

   0,2,1 here beats the vendor's 0,1,2, same specificity arithmetic as
   .product-hero .product-hero-lead above. */
.product-hero .product-hero-text h1,
.product-hero .product-hero-text p { text-align: left; }

.product-hero-eyebrow {
    margin: 0 0 0.5rem;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #f09546;
}

.product-hero-text h1 {
    margin: 0 0 0.75rem;
    color: #fff;
    text-wrap: balance;
}

/* Two classes, not one. Same specificity fight as .pricing-card-head h3
   above: the vendor sheet's "body.template-nebulous p" is 0,1,2 and beat
   a lone .product-hero-lead at 0,1,0, so the margin silently computed to
   0 and the chips sat flush under the sentence (Wayne 2026-08-25: "needs
   a little padding between text and badges"). .product-hero
   .product-hero-lead is 0,2,0 and wins. */
.product-hero .product-hero-lead {
    margin: 0 0 2rem;
    font-size: 1.125rem;
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.88);
}
.product-hero .product-hero-eyebrow { margin: 0 0 0.6rem; }

/* Proof chips. These are the product's own short description bullets,
   which the plan cards already render, so they stay short by necessity
   rather than by discipline.

   A grid rather than a wrapping flex row (Wayne 2026-08-25: "make them
   all the same length and height"). Flex sized each chip to its own text,
   so six chips of differing word counts made a ragged two-row block with
   a gap at the end. Equal fractional columns give every chip the same
   width whatever it says, and the flex centring inside each one plus a
   min-height keeps a chip that wraps to two lines the same height as the
   rest of its row. */
.product-hero-chips {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.55rem;
    /* Narrower than the text column above it so the right-hand chips clear
       Site Whiz, who stands at the foot of the visual column. Measured
       2026-08-25: at the text column's full width the last chip ran 64px
       into his head. */
    max-width: 32rem;
    margin: 0 0 1.75rem;
    padding: 0;
    list-style: none;
}
.product-hero-chips li {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.35rem;
    padding: 0.35rem 0.6rem;
    /* 0.78rem, down from 0.85 — Wayne 2026-08-25, "make the badge text
       just a little smaller so it doesn't hit Site Whiz's head". */
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.25;
    text-align: center;
    color: rgba(255, 255, 255, 0.92);
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}

.product-hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.product-hero-visual { position: relative; }

/* Site Whiz stands on the floor of the banner, not against the
   screenshot (Wayne 2026-08-25: "can we put him at the bottom of the
   banner"). He first sat overlapping the dashboard, obscuring the very
   thing the screenshot is there to show.

   The negative bottom is exactly .product-hero's own 3rem padding, so
   his feet land on the banner's bottom edge rather than floating above
   it, and the artwork's hard waist cut stays off-frame — the same
   reason bin/composite-site-whiz.py anchors him past the crop.

   He is an element rather than part of the background image because
   .category-hero-banner is background-size: cover, so anything baked
   into the JPG shifts and crops with the viewport. Hidden on narrow
   screens, where the screenshot has no room to spare. */
.product-hero-mascot {
    display: none;
    position: absolute;
    /* Beside the SCREENSHOT, which is what this column holds on any
       product without a spec matrix. He stands clear of its left edge
       here — moving him in for the spec card put him 136px ON TOP of
       the screenshot on every product that still shows one, which is
       what this default now protects. */
    left: -7rem;
    bottom: -3rem;
    width: 11rem;
    height: auto;
    pointer-events: none;
    filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.5));
}

.product-pricing-panel {
    /* 56rem, not 46: the rows-plus-summary picker needs a real left
       column. At 46rem the choices column came out 386px and the longest
       row wrapped its blurb, making that row 92px against 61px for its
       neighbours. */
    max-width: 56rem;
    margin: 0 auto 2.5rem;
    padding: 1.75rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
}

.product-compare {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    margin-bottom: 2.5rem;
    align-items: stretch;
}
.product-compare-card {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 1.75rem 1.6rem 1.5rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    /* Room for the accent stripe below, which is drawn on ::before rather
       than as a border so it can be full-bleed inside the rounded corner. */
    overflow: hidden;
}
.product-compare-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 3px;
    background: var(--color-box-background-divider);
}
/* The amber stripe and border are the "you are here" marker, not a hover
   state — but with no hover of its own the card read as one stuck on
   (Wayne 2026-08-25: "the box hover doesn't work, it stays highlighted
   only on the first box"). So the current card keeps its colour and hover
   is expressed as MOVEMENT instead: -6px and a shadow, matching
   .pricing-card:hover above, which is this site's existing card idiom.
   Deliberately no colour change on hover, or hovering the sibling would
   make it look like the current one. */
.product-compare-card {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.product-compare-card:hover {
    transform: translateY(-6px);
    box-shadow: var(--shadow-hover, 0 10px 30px rgba(36, 49, 65, 0.14));
}
.product-compare-card--current { border-color: #f09546; box-shadow: var(--shadow); }
.product-compare-card--current::before { background: #f09546; }
.product-compare-card--current:hover { box-shadow: 0 10px 30px rgba(240, 149, 70, 0.28); }

@media (prefers-reduced-motion: reduce) {
    .product-compare-card { transition: none; }
    .product-compare-card:hover { transform: none; }
}
/* Sits in the card footer, opposite the price, exactly where the sibling
   card puts its button — so the two footers read as a matched pair. */
.product-compare-badge {
    align-self: center;
    margin-bottom: 0;
    padding: 0.45rem 0.9rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #fff;
    background: #f09546;
    border-radius: 999px;
}
.product-compare-card h3 { margin: 0 0 0.4rem; }
.product-compare-lead { margin: 0 0 0.75rem; font-size: 0.94rem; opacity: 0.85; }

/* "60 features across 10 areas" — the contrast this section exists to
   draw, stated rather than left to be counted off the lists. */
.product-compare-count {
    margin: 0 0 1.1rem;
    padding-bottom: 0.9rem;
    border-bottom: 1px solid var(--color-box-background-divider);
    font-size: 0.78rem; font-weight: 700;
    letter-spacing: 0.08em; text-transform: uppercase;
    color: #f09546;
}

/* Two columns of feature groups per card. */
.product-compare-groups {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.1rem 1.4rem;
    margin-bottom: 1.25rem;
}
.product-compare-group h4 {
    display: flex; align-items: center; gap: 0.4rem;
    margin: 0 0 0.35rem;
    font-family: var(--font-heading);
    font-size: 0.92rem; font-weight: 700;
    color: var(--color-heading);
}
.product-compare-group-icon { font-size: 0.95rem; line-height: 1; }
.product-compare-group ul { margin: 0; padding: 0; list-style: none; }
.product-compare-group li {
    position: relative;
    padding: 0.14rem 0 0.14rem 0.95rem;
    font-size: 0.82rem; line-height: 1.4;
    opacity: 0.82;
}
.product-compare-group li::before {
    content: "";
    position: absolute;
    left: 0; top: 0.62rem;
    width: 5px; height: 5px;
    border-radius: 50%;
    background: #f09546;
    opacity: 0.65;
}
.product-compare-more { font-style: italic; opacity: 0.6 !important; }
.product-compare-more::before { display: none; }

.product-compare-fallback { margin: 0 0 1rem; padding-left: 1.1rem; }
.product-compare-fallback li { padding: 0.2rem 0; font-size: 0.92rem; }

/* Footer pinned to the bottom, so the price and button line up across
   cards of different heights. */
.product-compare-foot {
    display: flex; flex-wrap: wrap; align-items: center;
    justify-content: space-between; gap: 0.75rem;
    margin-top: auto;
    padding-top: 1.1rem;
    border-top: 1px solid var(--color-box-background-divider);
}
.product-compare-price { margin: 0; font-weight: 700; font-size: 1.15rem; color: var(--color-heading); }
.product-compare-here {
    font-size: 0.72rem; font-weight: 700;
    letter-spacing: 0.08em; text-transform: uppercase;
    opacity: 0.55;
}
.product-compare-card .button1 { align-self: center; }

/* A quiet panel with one orange edge, not a slab of orange.

   It was a full-width block of --color-primary-background with white
   text. On a page that already has orange card headers, orange price
   figures and an orange primary button, a saturated orange band the
   width of the content competes with all three — the accent stops
   marking anything because everything is the accent. Wayne: "the orange
   banner is a little too much."

   The band now sits on the same light ground as every other card, with
   a 4px orange edge and ordinary heading/body colours. The only
   saturated orange left in it is the button, which is the thing the
   band exists to point at. */
.product-cta-band {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 2.5rem;
    padding: 1.75rem 2rem;
    background: var(--color-box-background);
    border: 1px solid var(--card-edge);
    border-left: 4px solid var(--color-primary-background);
    border-radius: var(--border-radius-default);
    box-shadow: var(--card-lift);
}
.product-cta-band-text h2 { margin: 0 0 0.35rem; color: var(--color-heading); }
.product-cta-band-text p { margin: 0; color: var(--color-body); }

@media (min-width: 781px) {
    .product-hero-content { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 3rem; }
    .product-hero-mascot { display: block; }
    .product-compare { grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
    /* Three across on desktop, so six chips form two even rows of three
       rather than three rows of two. */
    .product-hero-chips { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---------------------------------------------------------------
   Billing-cycle picker — rows plus a summary panel (Option C)

   Wayne 2026-08-25 chose this from three mock-ups after "they are not
   right ... I want to make them look very professional and well laid
   out". The card grid it replaces had four layout faults — a "Save 17%"
   pill that wrapped and made its card taller than the others, an "Add to
   cart" button too narrow for its own label, the per-order note squeezed
   into a column beside that button, and a loose radio dot floating in
   each card duplicating the card's own selected state.

   Rows fix all four by construction: a row cannot be made taller than
   its neighbours by a pill, and the prices right-align into a column so
   the three figures are genuinely comparable.

   Only products with more than one variant render this — today exactly
   the two Software licences. The original .billing-cycle-option card
   styling above is deliberately left intact for the narrow purchase card
   inside a hero banner, which a future multi-variant product could still
   land in.
   --------------------------------------------------------------- */
/* width/align-self because .product-detail-form is itself a flex column
   that does not stretch its children: without these the grid shrank to
   its content and sat 680px wide inside an 840px panel, which is what
   made the rows narrow enough to wrap. */
.purchase-layout {
    display: grid;
    gap: 1.25rem;
    margin-bottom: 1.25rem;
    width: 100%;
    align-self: stretch;
}

.billing-cycle-picker--rows {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.6rem;
    margin-bottom: 1rem;
}

/* Overrides the card layout above for the row variant.

   display is restated, not inherited from .billing-cycle-option above:
   a vendor rule turns a <label> inside .product-detail-form into a
   block, which beat the base rule's own display:flex — so setting only
   flex-direction here was inert and the row's parts stacked vertically
   at 104px tall instead of sitting on one line. Measured before the fix:
   computed display "block", children at three different offsets. */
.billing-cycle-picker--rows .billing-cycle-option {
    display: flex;
    flex-direction: row;
    /* Explicit, so a row carrying a "Save 17%" pill is exactly as tall as
       one carrying a plain blurb. Left to content they came out 60/61/62. */
    min-height: 3.9rem;
    align-items: center;
    justify-content: flex-start;
    gap: 0.9rem;
    min-width: 0;
    padding: 0.9rem 1.1rem;
    text-align: left;
}
.billing-cycle-picker--rows .billing-cycle-option:hover {
    transform: none;
    background: var(--color-tertiary-background);
}
.billing-cycle-picker--rows .billing-cycle-option:has(input:checked) {
    background: var(--color-tertiary-background);
}
/* The dot is drawn, not native: the native control was absolutely
   positioned into the card corner, away from the label it belonged to. */
.billing-cycle-picker--rows .billing-cycle-option input[type="radio"] {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.billing-cycle-dot {
    flex: 0 0 auto;
    width: 1.15rem; height: 1.15rem;
    border: 2px solid var(--color-box-background-border);
    border-radius: 50%;
    display: grid; place-items: center;
}
.billing-cycle-option:has(input:checked) .billing-cycle-dot { border-color: var(--color-primary-background); }
.billing-cycle-option:has(input:checked) .billing-cycle-dot::after {
    content: "";
    width: 0.55rem; height: 0.55rem;
    border-radius: 50%;
    background: var(--color-primary-background);
}
.billing-cycle-option:has(input:focus-visible) {
    outline: 3px solid var(--color-primary-background);
    outline-offset: 2px;
}

.billing-cycle-main {
    flex: 1 1 auto; min-width: 0;
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
}
.billing-cycle-blurb { font-size: 0.82rem; color: var(--color-body); opacity: 0.75; }
.billing-cycle-picker--rows .billing-cycle-save { margin-top: 0; white-space: nowrap; }

.billing-cycle-figure { flex: 0 0 auto; text-align: right; white-space: nowrap; }
.billing-cycle-picker--rows .billing-cycle-price { font-size: 1.3rem; }
.billing-cycle-term { font-size: 0.8rem; color: var(--color-body); opacity: 0.75; margin-left: 0.15rem; }

.purchase-summary {
    background: var(--color-tertiary-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1.35rem 1.25rem;
}
.purchase-summary h4 {
    margin: 0 0 0.75rem;
    font-family: var(--font-heading);
    font-size: 0.74rem; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    opacity: 0.7;
}
.purchase-summary-total {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 2.15rem; font-weight: 700; line-height: 1;
    color: var(--color-heading);
}
.purchase-summary-sub { margin: 0.3rem 0 1rem; font-size: 0.84rem; opacity: 0.75; }
.purchase-summary-list { list-style: none; margin: 0 0 1.15rem; padding: 0; display: grid; gap: 0.4rem; }
.purchase-summary-list li {
    display: grid; grid-template-columns: 1rem 1fr; gap: 0.5rem;
    font-size: 0.85rem;
}
.purchase-summary-tick { color: #2f855a; font-weight: 700; }
.purchase-summary-cta { display: flex; width: 100%; justify-content: center; white-space: nowrap; }
.purchase-summary .note { margin: 0.85rem 0 0; font-size: 0.82rem; }

@media (min-width: 46rem) {
    .purchase-layout { grid-template-columns: minmax(0, 1fr) 17rem; align-items: start; }
}

/* The picker also renders inside the narrow purchase card in a hero
   banner. There is no room for a second column, so the summary drops
   under the rows rather than being squeezed beside them. */
.category-hero-banner-purchase .purchase-layout { grid-template-columns: minmax(0, 1fr); }

/* Fills the block under the picker rows, which is shorter than the
   summary column beside it. */
.purchase-guidance { margin-top: 0.25rem; }
.purchase-guidance h4 {
    margin: 0 0 0.6rem;
    font-family: var(--font-heading);
    font-size: 0.74rem; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    opacity: 0.7;
}
.purchase-guidance dl { margin: 0; display: grid; gap: 0.5rem; }
.purchase-guidance dt {
    font-size: 0.78rem; font-weight: 700;
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--color-heading);
}
.purchase-guidance dd {
    margin: 0.1rem 0 0;
    font-size: 0.86rem; line-height: 1.5;
    opacity: 0.8;
}
.purchase-guidance .note { margin: 0.9rem 0 0; font-size: 0.8rem; }

/* A photograph in the hero visual, rather than a UI capture: no browser
   chrome, so the image needs its own top edge rounded and a little more
   presence to stop it reading as a bare rectangle dropped on the banner. */
.category-hero-banner-screenshot--photo { padding: 0; overflow: hidden; }
.category-hero-banner-screenshot--photo img {
    display: block;
    border-radius: var(--border-radius-default);
}

/* ---------------------------------------------------------------
   Ai Site Whiz spotlight (product.php, rich layout)

   Its own band rather than a fourth alternating pillar row: the mascot
   is a character on a transparent background rather than a diagram, and
   the section reads as an aside about the product.
   --------------------------------------------------------------- */
.site-whiz-spotlight {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
    align-items: center;
    margin-bottom: 2.5rem;
    padding: 2rem 1.75rem;
    background: var(--color-tertiary-background);
    border: 1px solid var(--color-box-background-border);
    border-left: 3px solid #f09546;
    border-radius: var(--border-radius-default);
}
.site-whiz-spotlight-figure { text-align: center; }
.site-whiz-spotlight-figure img {
    width: 100%;
    /* 22rem, up from 16 — Wayne 2026-08-25 picked the original high-res
       artwork over a regenerated full-body version, so the box should
       give it room rather than shrink it to a thumbnail. The source is
       1392x1172, so there is plenty of resolution behind this. */
    max-width: 22rem;
    height: auto;
    /* The mascot is a PNG cut-out, so a shadow under it reads as the
       character standing on the panel rather than being pasted onto it. */
    filter: drop-shadow(0 12px 22px rgba(36, 49, 65, 0.18));
}
.site-whiz-spotlight-body h3 { margin: 0 0 0.3rem; }
.site-whiz-spotlight-lead {
    margin: 0 0 1rem;
    font-size: 1.05rem; font-weight: 600;
    color: var(--color-heading);
}
.site-whiz-spotlight-body p { margin: 0 0 1rem; }
.site-whiz-spotlight-list {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.35rem 1.25rem;
    margin: 0; padding: 0; list-style: none;
}
.site-whiz-spotlight-list li {
    position: relative;
    padding-left: 1.5rem;
    font-size: 0.9rem;
}
.site-whiz-spotlight-list li::before {
    content: "\2713";
    position: absolute; left: 0; top: 0;
    font-weight: 700;
    color: #f09546;
}

@media (min-width: 781px) {
    .site-whiz-spotlight { grid-template-columns: 22rem minmax(0, 1fr); gap: 2.5rem; padding: 2.5rem 2rem; }
    .site-whiz-spotlight-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------------------------------------------------------------
   Animated star dust around Site Whiz's stylus

   Wayne 2026-08-25: "maybe even animated silver magic sparkles."
   Silver rather than the brand amber — these sit right beside his own
   cyan pen, and amber fought both it and the artwork behind it.

   Only the spotlight gets these, because only there is the mascot a
   live <img>. He is composited INTO the homepage and category
   illustrations, so those carry drawn star dust in the pixels instead.

   Transform and opacity only, so each sparkle animates on the compositor
   without triggering layout or paint on the rest of the section.
   --------------------------------------------------------------- */
.site-whiz-spotlight-figure { position: relative; display: inline-block; overflow: visible; }

.whiz-sparkles {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* A real four-pointed sparkle glyph via clip-path, not two crossed bars.
   The bars read as a plus sign; the concave waist between the points is
   what makes it look like a struck star (Wayne: "fix them to look more
   like magic stars sparkling").

   Silver: a white core falling to #bac8d6 at the tips, with a cool glow
   behind and a thin outline so it holds an edge on any background. */
.whiz-sparkle {
    position: absolute;
    width: var(--s, 18px);
    height: var(--s, 18px);
    margin: calc(var(--s, 18px) / -2) 0 0 calc(var(--s, 18px) / -2);
    /* A GOLD ramp, not silver (Wayne: "would gold work better?" — it
       does, and for a reason worth writing down: silver's whole range
       sits close to a white page, so on the illustrations it had almost
       no contrast to work with no matter how the shape was tuned. Gold
       is saturated enough to hold its own on white and still reads as
       metal on the dark banner. It is also the site's own accent.)

       Highlight, shadow, specular, shadow — alternating bands are what
       makes it read as polished metal rather than a flat amber shape. */
    background:
        linear-gradient(135deg,
            #fffdf5 0%, #ffe9ae 14%, #f2b545 30%, #c07f1e 42%,
            #fff6dc 54%, #ffd071 66%, #d98f22 82%, #94600f 100%);
    /* A tight waist (46/54 rather than 40/60) is what separates a
       four-pointed sparkle from a plus sign — Wayne, on the previous
       version: "they look like crosses or airplanes". The narrower the
       band between the points, the more it reads as light rather than
       a shape. */
    /* 42/58, not 46/54. At the thinner waist the points are barely a
       pixel or two across, and neither the metallic ramp nor the outline
       has any surface to show on — which is why it still washed out on
       white. This is fatter but still unmistakably a sparkle rather than
       the plus sign the crossed-bars version made. */
    clip-path: polygon(
        50% 0%, 58% 42%, 100% 50%, 58% 58%,
        50% 100%, 42% 58%, 0% 50%, 42% 42%);
    opacity: 0;
    transform-origin: 50% 50%;
    animation: whiz-twinkle 2.9s ease-in-out infinite;
    animation-delay: var(--d, 0s);
    /* Two 1px shadows stack into an outline that holds the shape on a
       white ground; the third is the warm glow. */
    filter: drop-shadow(0 0 1px rgba(120, 76, 12, 0.95))
            drop-shadow(0 0 1px rgba(120, 76, 12, 0.7))
            drop-shadow(0 0 10px rgba(240, 176, 74, 0.7));
}

/* Size and timing only — POSITION is set per context below, because the
   spotlight shows the mascot alone while the illustrations show him
   beside a browser window, so the stylus lands somewhere different in
   each. An earlier revision of these rules dropped the spotlight's own
   left/top, which left them with no position at all and stacked the
   whole set beside his head (Wayne: "problem with stars above his
   head"). */
.whiz-sparkle--1 { --s: 30px; --d: 0s; }
.whiz-sparkle--2 { --s: 19px; --d: 0.7s; }
.whiz-sparkle--3 { --s: 14px; --d: 1.4s; }
.whiz-sparkle--4 { --s: 22px; --d: 2.1s; }
.whiz-sparkle--5 { --s: 16px; --d: 1.1s; }
.whiz-sparkle--6 { --s: 25px; --d: 2.6s; }

/* Spotlight: the image IS the mascot, so his stylus tip sits far right
   and high — 97.9% / 6.9% of the artwork, confined to the far right when
   detecting it because a wider scan finds the blue outline on top of his
   HEAD instead, which is what put an earlier set of these in the wrong
   place twice.

   Clustered ON the tip and allowed to run past 100%: the sparkle should
   look like it is coming off the end of the pen, and the pen ends at the
   very edge of the picture. */
.site-whiz-spotlight .whiz-sparkle--1 { left: 98%; top: 4%; }
.site-whiz-spotlight .whiz-sparkle--2 { left: 90%; top: 9%; }
.site-whiz-spotlight .whiz-sparkle--3 { left: 105%; top: 10%; }
.site-whiz-spotlight .whiz-sparkle--4 { left: 94%; top: 14%; }
.site-whiz-spotlight .whiz-sparkle--5 { left: 101%; top: 2%; }
.site-whiz-spotlight .whiz-sparkle--6 { left: 86%; top: 6%; }
.site-whiz-spotlight .whiz-mote--1 { left: 96%; top: 12%; }
.site-whiz-spotlight .whiz-mote--2 { left: 88%; top: 4%; }
.site-whiz-spotlight .whiz-mote--3 { left: 104%; top: 6%; }
.site-whiz-spotlight .whiz-mote--4 { left: 92%; top: 2%; }
.site-whiz-spotlight .whiz-mote--5 { left: 100%; top: 15%; }
.site-whiz-spotlight .whiz-mote--6 { left: 84%; top: 11%; }
.site-whiz-spotlight .whiz-mote--7 { left: 99%; top: 17%; }

/* The fine motes: smaller, slower, and drifting upward as they fade. */
.whiz-mote {
    position: absolute;
    width: 5px; height: 5px;
    margin: -2.5px 0 0 -2.5px;
    border-radius: 50%;
    background: linear-gradient(135deg, #fffaeb 0%, #ffd071 45%, #c08420 100%);
    opacity: 0;
    animation: whiz-drift 4.6s linear infinite;
    animation-delay: var(--d, 0s);
    filter: drop-shadow(0 0 1px rgba(120, 76, 12, 0.9)) drop-shadow(0 0 5px rgba(240, 176, 74, 0.65));
}
.whiz-mote--1 { --d: 0.3s; }
.whiz-mote--2 { --d: 1.2s; }
.whiz-mote--3 { --d: 2.1s; }
.whiz-mote--4 { --d: 3.0s; }
.whiz-mote--5 { --d: 3.8s; }
.whiz-mote--6 { --d: 1.7s; }
.whiz-mote--7 { --d: 2.6s; }

@keyframes whiz-twinkle {
    0%, 100% { opacity: 0;    transform: scale(0.15) rotate(-25deg); }
    18%      { opacity: 0.85; transform: scale(0.85) rotate(10deg); }
    34%      { opacity: 1;    transform: scale(1.15) rotate(35deg); }
    52%      { opacity: 0.7;  transform: scale(0.9)  rotate(60deg); }
    72%      { opacity: 0.3;  transform: scale(0.55) rotate(85deg); }
}

@keyframes whiz-drift {
    0%   { opacity: 0;   transform: translate(0, 0) scale(0.6); }
    25%  { opacity: 0.9; }
    100% { opacity: 0;   transform: translate(14px, -46px) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .whiz-sparkle, .whiz-mote { animation: none; opacity: 0.9; }
    .whiz-sparkle { transform: none; }
}

/* Twinkle overlay for illustrations that have Site Whiz composited in.
   The figure wrapper is a flex item in .nebulous-alt-block, so it has to
   carry the same sizing the bare <img> had or the row collapses. */
.nebulous-alt-block-figure {
    position: relative;
    flex: 1;
    min-width: 0;
    align-self: center;
    line-height: 0;
}
.nebulous-alt-block-figure img {
    max-width: 100%;
    max-height: 420px;
    width: auto;
    object-fit: contain;
}

/* The overlay is sized to the IMAGE, not to the flex item around it.
   Measured live: the illustration renders 420x420 inside a 561-wide
   figure, so percentages against the figure landed the sparkles ~14% of
   the width off — the tip sits at 55.8% of the image but only 41.8% of
   the box. aspect-ratio pins the layer to the square image instead, so
   the compositor's own reported fraction can be used directly. */
.whiz-sparkles--illustration {
    right: auto;
    width: auto;
    aspect-ratio: 1 / 1;
}

/* Centred ON the stylus tip, not weighted to one side of it.
   The tip is at 56% / 20% of the finished illustration — verified by
   marking the point on the artwork and looking, after two different
   automated readings disagreed (one found his cyan eyes, the other
   over-shot to 63.5% because the compositor reports placement in the
   background's pixels before the output is resized down to 900).
   An earlier set leaned left of it on purpose and read as detached from
   the pen (Wayne: "the stars on index.php are still too far to the
   left"). */
.whiz-sparkles--illustration .whiz-sparkle--1 { left: 57%; top: 14%; }
.whiz-sparkles--illustration .whiz-sparkle--2 { left: 50%; top: 19%; }
.whiz-sparkles--illustration .whiz-sparkle--3 { left: 64%; top: 17%; }
.whiz-sparkles--illustration .whiz-sparkle--4 { left: 54%; top: 25%; }
.whiz-sparkles--illustration .whiz-sparkle--5 { left: 62%; top: 24%; }
.whiz-sparkles--illustration .whiz-sparkle--6 { left: 48%; top: 12%; }
.whiz-sparkles--illustration .whiz-mote--1 { left: 59%; top: 21%; }
.whiz-sparkles--illustration .whiz-mote--2 { left: 52%; top: 15%; }
.whiz-sparkles--illustration .whiz-mote--3 { left: 66%; top: 21%; }
.whiz-sparkles--illustration .whiz-mote--4 { left: 51%; top: 27%; }
.whiz-sparkles--illustration .whiz-mote--5 { left: 60%; top: 28%; }
.whiz-sparkles--illustration .whiz-mote--6 { left: 46%; top: 22%; }
.whiz-sparkles--illustration .whiz-mote--7 { left: 63%; top: 11%; }

/* Static-page banner (Company, Why Choose Us, Testimonials, Affiliate).
   Shorter than a product hero: it carries a heading and a line, with no
   purchase box or screenshot to make room for. */
.static-page-hero { padding: 2.75rem 1.5rem; margin-bottom: 2.5rem; }
.static-page-hero .category-hero-banner-headline h1 { margin: 0 0 0.5rem; }
/* Held to a readable measure. The lead was one line when this rule
   was written; a full paragraph run across the container's whole width
   is a wall of text, and the banner is the first thing anyone reads. */
.static-page-hero .category-hero-banner-headline p {
    margin: 0;
    font-size: 1.05rem;
    line-height: 1.6;
    max-width: 68ch;
}

/* Contact rows on the company page. */
.company-contact {
    max-width: 44rem;
    margin: 0 auto 2.5rem;
    padding: 1.5rem 1.75rem;
    background: var(--color-tertiary-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
}
.company-contact-row {
    display: grid;
    grid-template-columns: 1.6rem 9rem minmax(0, 1fr);
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--color-box-background-divider);
}
.company-contact-row:last-of-type { border-bottom: none; }
.company-contact-row i { color: #f09546; }
.company-contact-label { font-weight: 700; font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; opacity: 0.7; }
.company-contact-value { word-break: break-word; }
.company-contact-cta { margin: 1.25rem 0 0; }

@media (max-width: 780px) {
    .company-contact-row { grid-template-columns: 1.6rem minmax(0, 1fr); }
    .company-contact-label { grid-column: 2; }
    .company-contact-value { grid-column: 2; }
}


/* ===================================================================
   RESPONSIVE LAYER  (added 2026-08-25)
   ===================================================================

   Every mobile and tablet rule lives HERE, in one ordered block,
   largest breakpoint first — not scattered next to the component each
   one patches.

   That scattering is not a style preference, it is the bug that
   produced half of this section. The desktop nav-collapse rules at
   max-width:1030px/900px (see ~line 238) were written to control which
   nav item drops first as a DESKTOP window narrows. They have no lower
   bound, so they were still firing on a 412px phone and beating the
   vendor's own mobile-menu rules. Nobody editing the Addons mega-menu
   section was thinking about handsets. Keeping the responsive rules
   contiguous makes "what applies at 412px?" answerable by reading one
   place.

   The vendor sheet (vendor/css/layout.css) already ships a complete,
   themed mobile menu — 447 rules keyed off .navmain-mobile. It was
   never broken; it was never switched on, because nebulous.js toggled
   a class (`nav-open`) that no stylesheet defines. Most of what
   follows is getting out of that menu's way rather than replacing it.
   See nebulous.js for the other half of that fix.

   NOT wrapped in @layer, deliberately. Cascade layers sit BELOW
   unlayered styles, and both layout.css and the rest of this file are
   unlayered — so layering this block would make it lose every
   specificity tie it currently wins, the opposite of what an override
   section needs. It relies on source order plus matching specificity,
   and on !important only where it has to out-shout a vendor
   !important (see 2b).

   Anything responsive added later belongs in this block, in
   breakpoint order — not next to the component it affects.
   =================================================================== */

/* -------------------------------------------------------------------
   1. Stop hiding real overflow
   -------------------------------------------------------------------
   nebulous.css:34 puts overflow-x:hidden on body to absorb the few-px
   gap between 100vw and the visible width when a DESKTOP scrollbar is
   showing. Because html sets no overflow of its own, that value
   propagates to the viewport — so the page can never scroll sideways
   on any device. Android Chrome uses overlay scrollbars, so 100vw
   already equals the visible width there and the rule buys nothing;
   all it does is silently CLIP anything too wide. "$4.99/mo runs off
   screen" was never running off a scrollable page — it was being cut.

   scrollbar-gutter does the job the comment actually describes, at
   the source, without blinding us to genuine overflow.               */

html { scrollbar-gutter: stable; }

/* -------------------------------------------------------------------
   1a. Domain checker — undo a desktop-only column split
   -------------------------------------------------------------------
   THIRD instance of the same bug pattern as the nav links below and
   .nebulous-alt-block further down: a rule written to solve a DESKTOP
   problem, given no lower bound, still firing on a phone.

   The vendor already stacks this section correctly at max-width:1000px
   (layout.css:7426) — flex-direction:column, both columns to 100%.
   But nebulous.css:1659 sets `.domainchecker .domainchecker-columns-left
   { width: 36% }` for a real desktop reason (the search box needed the
   extra room; see the note there). Identical specificity (0,0,2,0),
   and ours loads later, so ours wins — at every width.

   The result: the section stacks, but the heading column stays 36%
   wide. Measured at 412px, that is 120px of usable text width, which
   renders "A Great Domain Means Instant Credibility" at roughly one
   word per line. Reported as "the text is still not aligned to the
   far left" — it was not an alignment problem at all, it was a column
   131px narrower than its own container.

   Bounded to the same 1000px the vendor stacks at, so the desktop
   split it was written for is untouched. */
@media (max-width: 1000px) {
    body.template-nebulous .domainchecker .domainchecker-columns-left,
    body.template-nebulous .domainchecker .domainchecker-columns-right {
        width: 100%;
    }
    /* The vendor's stacked rule also re-centres the left column
       (layout.css:7434). Ranged left, per Wayne 2026-08-25 — same
       reasoning as the hero copy. */
    body.template-nebulous .domainchecker .domainchecker-columns-left,
    body.template-nebulous .domainchecker .domainchecker-heading,
    body.template-nebulous .domainchecker .domainchecker-subheading {
        text-align: left;
    }
}

@media (max-width: 900px) {
    body.template-nebulous { overflow-x: visible; }
}

/* -------------------------------------------------------------------
   1b. Tablets — 756px to 900px
   -------------------------------------------------------------------
   This band keeps the desktop nav (measured at 768px it fits: 678px of
   items in 753px of bar), so it needs none of section 2's menu work.
   It does need the social rail dealt with, and that is a separate bug
   from the phone one even though it has the same cause.

   nebulous.css:222-223 drops .site-main's left padding from 4rem to
   1.5rem at 780px AND tries to hide the rail. Only the first half
   lands — the hide loses to layout.css's `display:flex !important`.
   So between 756 and 780 the rail is still pinned at left:0, 50px
   wide, over content that no longer reserves a gutter for it. An iPad
   mini in portrait is exactly 768px: dead centre of the broken band.

   Hiding it up to 900px rather than 780px is deliberate. The rail is
   a hover affordance — its tooltips and hover states are unreachable
   on any touch device, and every device in this band is one.         */
/* -------------------------------------------------------------------
   1c. Collapsed nav — every width where the bar cannot hold the links
   -------------------------------------------------------------------
   The vendor switches to its mobile menu at 755px. Above that the
   DESKTOP bar is shown but cannot fit the full nav, so the theme and
   this file between them simply HIDE the tail: measured at 768px only
   Home, Domains, Web Hosting, Web Design and AI rendered, and at
   1024px both Addons and Support were gone. Those links were not
   merely awkward to reach, they were unreachable by any means —
   reported on a real iPad.

   So the hamburger now covers everything up to 1030px, which is the
   widest point at which anything was being dropped (a landscape iPad
   included). From 1031px up the bar holds all nine items and behaves
   exactly as before.

   A side effect worth knowing: the "hide the least essential item
   first" rules near line 254 (link7/8/9 at 1030px and 900px) are now
   dead code. They only ever applied at or below 1030px, and within
   that range the collapsed-menu selectors here outrank them. Left in
   place rather than deleted so the desktop ordering they document is
   still on record if the breakpoint ever moves back down.
   ------------------------------------------------------------------- */
@media (max-width: 1030px) {
    /* ---- Collapsed nav for tablets as well as phones ----
       The vendor switches to its mobile menu at 755px. Between 756 and
       900 the DESKTOP bar is shown but cannot fit ten items, so the
       overflow rules simply hide the tail — on an iPad only Home,
       Domains, Web Hosting, Web Design and AI rendered, and Software,
       Addons, Company and Support were unreachable by any means. That
       is worse than a collapsed menu: the links are not merely hard to
       reach, they are gone.

       These few declarations are the vendor's own reveal from its
       755px block (layout.css:8039 and 8069), repeated here so the
       hamburger appears across the whole touch range. Copied rather
       than re-invented so 756–900 renders identically to below 755,
       down to the Font Awesome burger glyph and its per-header-design
       colour tokens. */
    /* The bar's own height. Below 755px the vendor sets this from its
       --navmain-mobile-headerdesign{1,2}-height token; above 755 it
       does not, so once the desktop <li>s were hidden the bar had no
       content left and collapsed to 16px — the hamburger is absolutely
       positioned at 50px tall, so it hung outside a bar that was no
       longer there. Measured 16px at both 768px and 1024px. */
    body.template-nebulous .navmain-container,
    body.template-nebulous .navmain {
        min-height: var(--navmain-mobile-headerdesign1-height, 50px);
    }
    body.template-nebulous .headermain-design2 .navmain-container,
    body.template-nebulous .headermain-design2 .navmain {
        min-height: var(--navmain-mobile-headerdesign2-height, 50px);
    }
    body.template-nebulous .navmain { position: relative; }

    body.template-nebulous .navmain .navmain-mobile-home {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 50px;
        text-decoration: none;
    }
    /* The home icon needs its MOBILE colour token, not just a display
       value. Without these it inherits --navmain-headerdesign1-home-
       foreground, the DESKTOP token, which is --color-black — a black
       house on the dark bar, reported as "tiny and greyed out" next to
       a crisp white hamburger. The mobile token is white. Same
       oversight as the burger glyph: revealing an element is not the
       same as styling it. (layout.css:8047) */
    body.template-nebulous .headermain.headermain-design1 .navmain .navmain-mobile-home {
        height: var(--navmain-mobile-headerdesign1-height);
        color: var(--navmain-mobile-headerdesign1-home-foreground);
        font-size: 1.25em;
    }
    body.template-nebulous .headermain.headermain-design2 .navmain .navmain-mobile-home {
        height: var(--navmain-mobile-headerdesign2-height);
        color: var(--navmain-mobile-headerdesign2-home-foreground);
        font-size: 1.25em;
    }
    body.template-nebulous .headermain.headermain-design1 .navmain .navmain-mobile-home:hover {
        color: var(--navmain-mobile-headerdesign1-home-hover-foreground);
    }
    body.template-nebulous .headermain.headermain-design2 .navmain .navmain-mobile-home:hover {
        color: var(--navmain-mobile-headerdesign2-home-hover-foreground);
    }
    body.template-nebulous .navmain .navmain-mobile-icon {
        float: right;
        display: flex;
        justify-content: center;
        align-items: center;
        /* NOT "Font Awesome 6 Pro", which is what the vendor rule this
           was copied from says. The bundled webfont is Pro 5.10.1 and
           its family is literally "Font Awesome 5 Pro" — see the long
           note at line ~600, which already fixes this for the icon.
           Copying the vendor declaration verbatim re-broke it: this
           rule is later and more specific, so it beat that fix and the
           burger rendered as a tofu box. Left explicit rather than
           omitted so nobody re-copies the vendor value again. */
        font-family: "Font Awesome 5 Pro";
        font-weight: 900;
        font-style: normal;
        font-variant: normal;
        text-rendering: auto;
        position: absolute;
        top: 0;
        right: 0;
        cursor: pointer;
        width: 50px;
        text-decoration: none;
        font-size: 1.4em;
    }
    body.template-nebulous .headermain.headermain-design1 .navmain .navmain-mobile-icon::before {
        height: var(--navmain-mobile-headerdesign1-height);
        line-height: var(--navmain-mobile-headerdesign1-height);
        content: var(--navmain-mobile-headerdesign1-icon-burger);
        color: var(--navmain-mobile-headerdesign1-icon-foreground);
    }
    body.template-nebulous .headermain.headermain-design2 .navmain .navmain-mobile-icon::before {
        height: var(--navmain-mobile-headerdesign2-height);
        line-height: var(--navmain-mobile-headerdesign2-height);
        content: var(--navmain-mobile-headerdesign2-icon-burger);
        color: var(--navmain-mobile-headerdesign2-icon-foreground);
    }
    body.template-nebulous .headermain.headermain-design1 .navmain .navmain-mobile-icon-open::before {
        content: var(--navmain-mobile-headerdesign1-icon-burger-active);
    }
    body.template-nebulous .headermain.headermain-design2 .navmain .navmain-mobile-icon-open::before {
        content: var(--navmain-mobile-headerdesign2-icon-burger-active);
    }
    /* Desktop bar and its hover mega-menus stay out of the way until
       the hamburger puts .navmain-mobile on. */
    body.template-nebulous .navmain:not(.navmain-mobile) > ul > li { display: none; }
    body.template-nebulous .navmain:not(.navmain-mobile) .navmain-subcontainer { display: none; }

    /* The collapsed bar already carries its own home icon
       (.navmain-mobile-home), so the home <li> inside the panel is a
       second Home pointing at the same page. The vendor drops it in its
       own 755px block ("Remove the desktop home icon"); repeated here
       so 756–1030px matches instead of showing the duplicate. */
    body.template-nebulous .navmain > ul > li.navmain-home { display: none !important; }

    /* ---- The collapsed menu panel ----
       Lives in this 900px block, not the 755px one below: the
       hamburger is now revealed across the whole touch range, so
       the panel it opens has to be styled across the same range.
       At 768px the icon appeared but tapping it did nothing.
       .navmain-mobile is the vendor's own class — every rule these
       lean on already exists in layout.css. This only supplies the
       panel chrome our header markup needs (the vendor demo's header
       is a different shape) and a visible open/closed state.         */
    body.template-nebulous .navmain-container { position: relative; }

    /*
     * THE PANEL IS A CARD, NOT A DARK SLAB (Wayne, 2026-09-07: "fix the
     * mobile main dropdown to match the client area and our style").
     *
     * It used to inherit the desktop bar's navy and centre every row,
     * which read as a different product from the account menu directly
     * below it on the same screen. Same surface tokens as `.ca-nav`
     * now: white card, rounded, one hairline between rows, left-aligned
     * labels, orange only for the accent. All through tokens, so dark
     * mode still inverts.
     */
    body.template-nebulous .navmain.navmain-mobile > ul {
        display: block;
        position: absolute;
        left: 0;
        right: 0;
        top: 100%;
        z-index: 500;
        max-height: min(70dvh, 34rem);
        overflow-y: auto;
        overscroll-behavior: contain;
        background: var(--color-box-background, #ffffff);
        border: 1px solid var(--color-box-background-border, #d2d2d2);
        border-radius: 12px;
        margin-top: .5rem;
        box-shadow: 0 18px 40px -18px rgba(16, 24, 40, .45);
    }

    body.template-nebulous .navmain.navmain-mobile > ul > li {
        display: block;
        position: relative;
        float: none;
        width: 100%;
        background: transparent;
        border-top: 1px solid var(--color-border, #e6e8ec);
    }

    /* The card's own top edge already draws that line. */
    body.template-nebulous .navmain.navmain-mobile > ul > li:first-child {
        border-top: none;
    }

    /* Two of the nav's ten items — Software and Addons — are pure
       dropdown parents with no landing page of their own, so
       header.php:362 renders them as <span class="navmain-dropdown-label">
       rather than <a>. On desktop that is invisible: the mega-menu
       opens on hover and the label is never clicked. On a phone it
       meant those two rows got none of the row styling below and were
       not tappable at all — they read as greyed-out dead text next to
       eight working links. Both element types get the same row.      */
    body.template-nebulous .navmain.navmain-mobile > ul > li > a,
    body.template-nebulous .navmain.navmain-mobile > ul > li > .navmain-dropdown-label {
        display: block;
        width: 100%;
        /* Right padding clears the 52px chevron, which is absolutely
           positioned over this row rather than laid out beside it. */
        padding: .95rem 3.4rem .95rem 1.1rem;
        line-height: 1.3;
        /* 44px is the platform minimum touch target on both Android
           and iOS; .95rem padding on a 1.3 line-height clears it. */
        min-height: 44px;
        box-sizing: border-box;
    }

    /*
     * THE ROW'S COLOUR, AT A WEIGHT THAT SURVIVES THE VENDOR.
     *
     * layout.css paints mobile nav links through its own dedicated
     * token (`.headermain-design1 .navmain ul li a`, (0,2,3)) — written
     * for the navy panel that is no longer here, so left alone it puts
     * near-white text on our white card. The two design variants and
     * the hover and open states are all listed explicitly: a row that
     * is merely being touched must not turn into a different design.
     *
     * (0,5,4) with the body class, against the vendor's (0,2,3). This
     * file carries four separate comments from times that arithmetic
     * looked right and was not, so it was read back out of the CSSOM.
     */
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li > a,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li > a,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li > .navmain-dropdown-label,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li > .navmain-dropdown-label,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li:hover > a,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li:hover > a,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li:hover > .navmain-dropdown-label,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li:hover > .navmain-dropdown-label {
        text-align: left;
        color: var(--color-heading, #243141);
        background: transparent;
        font-size: .95rem;
        font-weight: 600;
        text-shadow: none;
    }

    /* Open gets a tint, the same way the account menu's button does —
       enough to say "this one is expanded", not a second colour scheme. */
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li.navmain-dropdown-mobile-open > a,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li.navmain-dropdown-mobile-open > a,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li.navmain-dropdown-mobile-open > .navmain-dropdown-label,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li.navmain-dropdown-mobile-open > .navmain-dropdown-label {
        color: var(--color-heading, #243141);
        background: var(--color-box-background-divider, #f4f6f9);
        text-shadow: none;
    }

    /*
     * THE PAGE YOU ARE ON — and the vendor's own mark for it, removed.
     *
     * layout.css paints the current item with
     * `linear-gradient(#525252, #525252)`: a dark slab designed for the
     * navy bar, which on the white card is one grey block among seven
     * white rows. Wayne caught it on the deployed build.
     *
     * TWO REASONS THE FIRST ATTEMPT MISSED IT, both worth keeping:
     *
     * 1. It is a background-IMAGE, not a background-color. `background:
     *    transparent` on the row did not remove it, and
     *    getComputedStyle().backgroundColor reported "rgba(0, 0, 0, 0)"
     *    while the thing was plainly visible on screen — the right
     *    property read on the wrong channel says everything is fine.
     * 2. `.headermain.headermain-design1 .navmain-mobile ul
     *    li.navmain-active` is (0,4,2); the row rule here was (0,3,3)
     *    and lost. Naming the header variant is what buys the weight.
     *
     * Hover is listed too: the same vendor rule shape paints it, and it
     * would have surfaced the next time anyone used this on a mouse.
     */
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li:hover,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li:hover,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li.navmain-active,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li.navmain-active {
        background: transparent;
        background-image: none;
    }

    /* Marked the way the account menu marks it: the accent as a rule
       down the edge and in the text, over a tint rather than a fill. */
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li.navmain-active > a,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li.navmain-active > a,
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile > ul > li.navmain-active > .navmain-dropdown-label,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile > ul > li.navmain-active > .navmain-dropdown-label {
        color: var(--color-primary-background, #f09546);
        background: var(--color-box-background-divider, #eef0f2);
        box-shadow: inset 3px 0 0 var(--color-primary-background, #f09546);
    }
    /* A separate, pre-existing bug this uncovered — live on the site
       today, independent of everything else in this file.

       The vendor recolours nav links for mobile with a DEDICATED token:
       layout.css:6953 sets `.headermain-design1 .navmain ul li a` to
       --navmain-MOBILE-headerdesign1-button-foreground, distinct from
       the desktop token used at layout.css:870. Our .navmain-dropdown-label
       is a Global Web Concepts addition (nebulous.css:616, added with the
       Software/Addons mega-menus) — the vendor has never heard of it,
       so it got the desktop rule and no mobile counterpart, then fell
       back to inheriting #222 from body.

       Measured: rgb(34,34,34) on the rgb(36,49,65) panel — a contrast
       ratio of 1.21:1, against a WCAG AA minimum of 4.5:1. "Software"
       and "Addons" were not dim on a phone, they were invisible.

       The panel is a white card now, so the fix is no longer the
       vendor's light-on-dark token — that would recreate the same
       failure with the colours the other way round. Both variants take
       the same heading colour every other row takes, set above; these
       rules stay only to make sure the vendor's own token cannot leak
       back in through a selector nobody has read.                     */
    body.template-nebulous .headermain-design1 .navmain.navmain-mobile .navmain-dropdown-label,
    body.template-nebulous .headermain-design2 .navmain.navmain-mobile .navmain-dropdown-label {
        color: var(--color-heading, #243141);
        text-shadow: none;
    }
    body.template-nebulous .navmain.navmain-mobile > ul > li > .navmain-dropdown-label {
        cursor: pointer;          /* vendor sets `default` — it IS a control here */
        /* Left, with everything else. This used to be centred to match
           the eight <a> rows, which inherited centring from the
           vendor's desktop bar; the card layout aligns them all left
           instead, so following them means changing this too. */
        text-align: left;
    }
    /* The label carries its own `›` pseudo-element as a desktop
       affordance (nebulous.css:632). The injected chevron on the right
       says the same thing, so on a phone it is just a stray glyph
       hanging off the front of two of the ten rows. */
    body.template-nebulous .navmain.navmain-mobile > ul > li > .navmain-dropdown-label::before {
        display: none;
    }

    /* Sub-menus stay closed until their own chevron is tapped. The
       vendor's hover reveal is unreachable on a touch screen, which
       is why the mega-menus reached nothing on a phone.              */
    body.template-nebulous .navmain.navmain-mobile .navmain-subcontainer {
        display: none;
        position: static;
        width: 100%;
        min-width: 0;
        box-shadow: none;
        /* A tint of the card rather than a hole punched in it — the
           panel is white now, and a 18%-black wash on white is a grey
           box, not a nested section. */
        background: var(--color-box-background-divider, #f4f6f9);
        border-top: 1px solid var(--color-border, #e6e8ec);
        padding: .25rem 0;
    }
    body.template-nebulous .navmain.navmain-mobile .navmain-subcontainer-mobile-open {
        display: block;
    }
    body.template-nebulous .navmain.navmain-mobile .navmain-columns,
    body.template-nebulous .navmain.navmain-mobile .navmain-columns-col {
        display: block;
        width: 100%;
        float: none;
    }

    /* ---- SUB-MENU ENTRIES: A LIST, NOT A GRID OF CARDS -------------
       Each entry is a desktop mega-menu tile — an icon in a chip, a
       heading and a sentence of description — laid out two to a row.
       On a 500px screen that is a 190px column, so "SSL Certificates"
       broke across two lines above six lines of prose, and eight of
       them filled four screens.

       They become the same icon + label row the account menu uses. The
       vendor's own rules for these run to (0,5,0) and use `max-width`
       as well as `width`, so BOTH have to be answered — width alone
       left every row still 190px wide, looking for all the world like
       the override had not applied at all. Read back from the CSSOM,
       not calculated.
       ---------------------------------------------------------------- */
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-links {
        display: block;
        width: 100%;
        margin: 0;
        padding: 0;
    }
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link,
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link.navmain-icon {
        display: block;
        width: 100%;
        max-width: none;
        min-width: 0;
        flex: 0 0 auto;
        float: none;
        margin: 0;
        padding: 0;
        background: transparent;
        border: none;
        border-radius: 0;
        box-shadow: none;
        min-height: 0;
    }
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link > a,
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link.navmain-icon > a {
        display: flex;
        /* The vendor stacks the tile's contents (`flex-direction:
           column`), which is what put each icon on its own line above
           its label even after this rule made the anchor a flex box. */
        flex-direction: row;
        flex-wrap: nowrap;
        align-items: center;
        justify-content: flex-start;
        gap: .7rem;
        width: 100%;
        max-width: none;
        height: auto;
        min-height: 44px;
        padding: .7rem 1.1rem .7rem 1.6rem;
        background: transparent;
        color: var(--color-heading, #243141);
        border: none;
        box-shadow: none;
        font-size: .9rem;
        font-weight: 500;
        text-align: left;
        text-shadow: none;
    }
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link > a > i,
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link.navmain-icon > a > i {
        position: static;
        top: auto;
        left: auto;
        right: auto;
        flex: 0 0 auto;
        width: 1.3rem;
        height: auto;
        margin: 0;
        padding: 0;
        font-size: .95rem;
        line-height: 1;
        text-align: center;
        color: var(--color-primary-background, #f09546);
        background: transparent;
        border: none;
        border-radius: 0;
        box-shadow: none;
    }
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link .navmain-link-heading,
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link.navmain-icon .navmain-link-heading {
        flex: 1 1 auto;
        min-width: 0;
        width: auto;
        margin: 0;
        padding: 0;
        font-size: .9rem;
        font-weight: 500;
        line-height: 1.3;
        color: inherit;
        text-align: left;
        text-transform: none;
        text-shadow: none;
    }
    /* The sentence under each entry is a desktop affordance. Ten of
       them turn this panel into a page you have to scroll rather than a
       menu you can see. The label and the icon carry the meaning. */
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link .navmain-link-text,
    body.template-nebulous .navmain.navmain-mobile .navmain-dropdown-megamenu .navmain-subcontainer .navmain-col-heading {
        display: none;
    }
    /* The right-hand promo column of each mega-menu is decorative and
       costs a full screen of scrolling on a handset. */
    body.template-nebulous .navmain.navmain-mobile .navmain-columns-col2 { display: none; }

    body.template-nebulous .navmain-mobile-child {
        position: absolute;
        right: 0;
        top: 0;
        width: 52px;
        height: 52px;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        /* Was #fff, for the navy panel. On the white card that is a
           chevron you cannot see. */
        color: var(--color-body, #6b7280);
    }
    body.template-nebulous .navmain-mobile-child::before {
        content: "\f078";               /* fa-chevron-down, FA5 Pro */
        font-family: "Font Awesome 5 Pro", "Font Awesome 5 Free";
        font-weight: 900;
        font-size: .7rem;
        transition: transform .18s ease;
    }
    body.template-nebulous .navmain-dropdown-mobile-open > .navmain-mobile-child::before {
        transform: rotate(180deg);
    }


}

@media (max-width: 900px) {
    body.template-nebulous .social-sidewidget { display: none !important; }
    /* The left-padding override that used to sit here is gone: it existed
       to stop 4rem of fixed padding eating a small screen, and main is
       now a percentage container, which needs no breakpoint of its own.
       Leaving it in re-introduced the asymmetry — measured at 745px,
       main sat at 61 left against 37 right while the header beside it
       was even. */
    body.template-nebulous .bx-prev { left: 10px; }
    body.template-nebulous .bx-next { right: 10px; }

    /* Partner/technology logos on one row (Wayne, 2026-08-25).
       They rendered at their own natural sizes — cPanel alone is
       215x56 — inside a `justify-content: space-between` flex row, so
       six logos wrapped onto three rows 293px tall: more vertical
       space than the copy above them.

       Each logo now takes an equal share of the row and scales inside
       it. `flex: 1 1 0` with `min-width: 0` is what makes the shares
       equal regardless of each image's own width — without min-width a
       flex item refuses to shrink below its intrinsic size, so the
       wide ones (cPanel, Stripe) would still push the rest out.
       object-fit keeps each logo's aspect ratio inside its box, so
       nothing is stretched to fill its share.

       Scoped to 900px, not the 755px phone block: at 768px this
       wrapped to two rows 144px tall. Desktop keeps the logos at their
       natural sizes, where there is room for them. */
    body.template-nebulous .nebulous-partners {
        flex-wrap: nowrap;
        justify-content: space-between;
        align-items: center;
        gap: .55rem;
    }
    body.template-nebulous .nebulous-partners img {
        flex: 1 1 0;
        min-width: 0;
        width: auto;
        height: auto;
        max-height: 1.5rem;
        object-fit: contain;
    }

    /* ---- Mini cart panel ----
       The panel is 320px wide and anchored `right: 0` to its own list
       item. On a phone that item sits around x=120, so the panel's left
       edge computed to roughly -84px: it opened off the left of the
       screen and was clipped.

       Centring the panel on the cart link was tried first and still
       left it 5–23px off screen, because the link is not centred in
       the bar either. The fix is to stop anchoring to the link at all:
       make the toolbar the containing block and pin the panel to both
       of its edges. The toolbar spans the viewport, so the panel now
       cannot leave it at any width, whatever the link's position. */
    body.template-nebulous .mini-cart { position: static; }
    body.template-nebulous .toolbarmain { position: relative; }
    body.template-nebulous .mini-cart-panel {
        left: 0.75rem;
        right: 0.75rem;
        width: auto;
        max-width: none;
        transform: none;
    }
}

/* -------------------------------------------------------------------
   2. Phone layout — below the vendor's own 755px nav breakpoint
   ------------------------------------------------------------------- */

@media (max-width: 755px) {

    /* 2a. The leaked desktop nav item.
       nebulous.css:238-247 re-asserts display:list-item on link7/link8
       at max-width:1030/900 to control WHICH item drops first as the
       desktop nav narrows. Those are max-width queries with no floor,
       so at 412px they are still in force — and .navmain .navmain-link7
       (0,0,2,0) outranks the vendor's mobile hide .navmain ul li
       (0,0,1,2). Result: a hamburger, a home icon, and one orphaned
       Addons mega-menu sitting in the bar. That is the "duplicate
       chrome". Undo it below the nav breakpoint and let the vendor's
       own collapsed menu own this width.                             */
    body.template-nebulous .navmain:not(.navmain-mobile) > ul > li { display: none; }

    /* 2b. The fixed social rail.
       nebulous.css:223 already tries `.social-sidewidget{display:none}`
       at 780px, but layout.css:6096 says `display:flex !important`.
       Same specificity, vendor has !important — source order is
       irrelevant, so the rail stayed pinned at left:0 z-index:9999
       straight over the copy. Meanwhile that same media block stripped
       the 4rem padding-left that was reserving its gutter, so the
       content slid UNDER it. One losing rule, both halves of the bug.
       Needs matching specificity AND our own !important; there is no
       cleverer escape from a vendor !important.                      */
    body.template-nebulous .social-sidewidget { display: none !important; }

    /* The slider arrows were nudged to 55px purely to clear that rail
       (nebulous.css:349-350). With it gone they are just dead inset. */
    body.template-nebulous .bx-prev { left: 10px; }
    body.template-nebulous .bx-next { right: 10px; }

    /* 2d. The utility bar.
       layout.css:503 sets display:flex with no flex-wrap, and its
       children float. At 412px the currency select + View Cart +
       Client Area + Register + theme toggle simply do not fit, and
       the body overflow mask above meant the overflow was invisibly
       cut rather than reachable. Wrapping is the honest fix — the row
       has no reading order that a horizontal scroll would preserve. */
    body.template-nebulous .toolbarmain {
        flex-wrap: wrap;
        gap: .3rem .4rem;
        justify-content: center;
        padding: .45rem .5rem;      /* was 8px 16px — 16px of side padding
                                       is 32px the four controls can't use */
    }
    body.template-nebulous .toolbarmain-text,
    body.template-nebulous .toolbarmain-menu { float: none; min-width: 0; }
    body.template-nebulous .toolbarmain-menu ul {
        display: flex;
        /* Still wrap rather than nowrap: below ~330px these genuinely
           cannot fit, and a second row is better than a clipped one.
           The sizing below is what stops it wrapping in practice. */
        flex-wrap: wrap;
        gap: .3rem .4rem;
        justify-content: center;
    }
    body.template-nebulous .toolbarmain-menu ul li { float: none; }

    /* One row for USD / View Cart / Client Area / Register.
       Measured at 412px: the four controls wanted 95 + 116 + 124 + 80
       = 415px plus 43px of gaps = 458px, inside 365px of usable width.
       Register was pushed onto a second row. Reclaiming ~93px comes
       from three places rather than squeezing any one of them hard —
       the side padding above, the gaps above, and the type and link
       padding here. */
    body.template-nebulous .toolbarmain,
    body.template-nebulous .toolbarmain a,
    body.template-nebulous .toolbarmain-menu ul li { font-size: .8rem; }
    body.template-nebulous .toolbarmain-menu ul li > a { padding: .3rem .45rem; }
    body.template-nebulous .toolbarmain .currency-selector-li select {
        font-size: .75rem;
        padding: .15rem .3rem;
        max-width: 4.6rem;
    }
    /* The cart and padlock glyphs sit tight to their labels once the
       row is this dense. */
    body.template-nebulous .toolbarmain-menu ul li > a i,
    body.template-nebulous .toolbarmain-menu ul li > a svg { margin-right: .25rem; }

    /* 2e. Hero.
       48px type at a 397px viewport is what pushed the headline off
       screen. clamp() replaces the 781px breakpoint pair outright and
       — unlike a breakpoint — degrades continuously, so there is no
       untested width left between here and 350px. balance stops the
       one-word orphan line.                                          */
    body.template-nebulous .banner .banner-heading {
        font-size: clamp(1.75rem, 8.5vw, 2.5rem);
        line-height: 1.15;
        text-wrap: balance;
    }
    body.template-nebulous .banner .banner-text,
    body.template-nebulous .banner p { font-size: 1rem; line-height: 1.55; }

    /* banner-price is absolutely positioned at right:0 (layout.css:2421)
       and is never re-sized below 600px, so it hangs off the corner. */
    body.template-nebulous .banner .banner-price {
        position: static;
        display: inline-flex;
        margin: 0 0 .9rem;
    }

    /* Hero height.
       The hero was rendering 1184px tall — nearly 1.3 screens before a
       visitor reaches anything.

       A `max-height: 78dvh` cap was tried first and REVERTED: capping
       the box does not shorten the content, it CLIPS it. Measured at
       412px, the hero's content needs 1298px, so a 714px cap cut off
       three of the four call-to-action buttons — including "Search
       Domains" and "View Hosting Plans", the primary CTAs on each
       slide. A tall hero is a minor annoyance; an invisible Buy button
       is a lost sale. Never cap a box whose content you have not
       shortened.

       So the height comes down by removing real vertical space rather
       than hiding the overflow. The desktop rhythm (30px under the
       heading, 30px under the lead, 50px under the whole text column)
       is generous for a wide layout and simply too loose stacked on a
       handset. */
    body.template-nebulous li.banner .banner-heading { margin-bottom: .9rem; }
    body.template-nebulous li.banner .banner-text { margin-bottom: .9rem; }
    body.template-nebulous li.banner .banner-twocolumn-col1 { margin-bottom: 1.25rem; }
    body.template-nebulous li.banner .banner-headline-panel { padding: 1rem 1.1rem; }
    /* The feature list is 6 rows at 36px each — the single biggest
       block in the panel. Tightening the row, not dropping any of it. */
    body.template-nebulous li.banner .banner-headline-panel ul li { padding-block: .18rem; }

    /* Left-align the hero copy on mobile (Wayne, 2026-08-25).
       The theme centres it, which works across a wide desktop measure
       but not in a ~280px column: centred ragged text gives every line
       two uneven edges, so nothing anchors the eye on the way down.
       Ranged-left gives a hard left edge to read against. */
    body.template-nebulous li.banner .banner-heading,
    body.template-nebulous li.banner .banner-text,
    body.template-nebulous li.banner .banner-headline-panel { text-align: left; }
    /* The feature list needs saying separately: layout.css's own
       mobile block centres it explicitly at `.banner .list ul li`, so
       it does not inherit the panel's alignment. Leaving it centred
       under a left-ranged heading is the worst of both — two competing
       axes in one panel. */
    body.template-nebulous li.banner .list ul li { text-align: left; }

    body.template-nebulous li.banner .background { background-position: center; }

    /* 2f. Domain search.
       nebulous.css:1727 clears the floats on -form and -transfer at
       600px but omits .domainsearch-www, which keeps float:left with a
       fixed 85px width and a 65px height. Two 100%-wide block siblings
       then wrap around it — the line-wrapping box. Dropping the whole
       float trio for flex fixes it at the mechanism rather than
       patching a third float.                                        */
    body.template-nebulous .domainchecker .domainsearch-form,
    body.template-nebulous .domainchecker .domainsearch-transfer,
    body.template-nebulous .domainchecker .domainsearch-www {
        float: none;
        width: 100%;
        box-sizing: border-box;
    }
    body.template-nebulous .domainchecker .domainsearch-www {
        height: auto;
        padding: .5rem;
        border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
    }
    body.template-nebulous .domainchecker .domainsearch-form {
        display: flex;
        flex-wrap: wrap;
        gap: .5rem;
    }
    body.template-nebulous .domainchecker .domainsearch-form input[type="text"] {
        flex: 1 1 100%;
        width: auto;
        min-width: 0;              /* flex items default to min-width:auto
                                      and refuse to shrink below their
                                      intrinsic width — the classic cause
                                      of an input bursting its row. */
    }
    body.template-nebulous .domainchecker .domainsearch-form input[type="submit"] {
        flex: 1 1 100%;
        width: auto;
        min-height: 44px;
    }

    /* The TLD price list is hidden outright below 500px by the vendor
       (layout.css: `.domainchecker .domainpricing { display: none }`),
       presumably because its row of chips cannot fit horizontally.
       The effect on this site is that five REAL configured prices
       (.ai $79.99/yr, .com $12.99/yr, and so on) disappear on every
       phone — directly beneath a subheading that promises "from just
       $12.99/yr" and immediately above the search box they qualify.
       That is live commercial content, not vendor demo decoration.

       Shown again, wrapped into rows instead of one non-wrapping line,
       which is what the hide was working around. Revert this one block
       if the prices are better left off small screens. */
    body.template-nebulous .domainchecker .domainpricing {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-start;
        gap: .4rem .75rem;
        margin-top: .9rem;
    }
    body.template-nebulous .domainchecker .domainpricing > * {
        margin-right: 0;
        white-space: nowrap;
    }

    /* 2g. The two floating buttons overlapped each other.
       Measured live at 412px: back-to-top occupied x351-381/y819-849
       and the chat launcher x317-373/y835-891 — a real 22x14px
       collision, both tappable, neither reliably. Stack them. */
    body.template-nebulous .cd-top { bottom: 6.25rem; right: 1.35rem; }

    /* 2h. Page gutters. Vertical only now — the horizontal 1rem here was
       a third gutter competing with the container's own, which is what
       made main sit at 39 while the header sat at 16 on a phone. */
    body.template-nebulous .site-main.nebulous-main { padding-top: 1.5rem; padding-bottom: 1.5rem; }
    /* The first-child banner pull above assumes 2.5rem of top padding;
       this breakpoint uses 1.5rem, so match it or the banner is yanked
       1rem too far and clips under the toolbar. */
    body.template-nebulous .site-main > .category-hero-banner:first-child,
    body.template-nebulous .site-main > .nebulous-slider:first-child,
    body.template-nebulous .site-main > .bx-wrapper:first-child {
        margin-top: -1.5rem;
    }
    body.template-nebulous .contentcontainer { width: 100%; padding-inline: 1rem; box-sizing: border-box; }

    /* 2i. Two real overflows that only became visible once the body
       mask in section 1 came off — they were always there, just
       silently clipped, which is why nobody could measure them.

       The footer's trust-badge row (nebulous.css:1808) is a flex
       container with no wrap, so the second badge is pushed straight
       past the viewport edge — measured at 412px, the RapidSSL badge
       ran to x=489 against a 397px viewport. It accounted for the
       whole 92px of remaining page overflow on its own. */
    body.template-nebulous .nebulous-legal-badges {
        flex-wrap: wrap;
        justify-content: center;
        row-gap: .5rem;
    }
    body.template-nebulous .nebulous-legal-badges img { max-width: 45%; height: auto; }

    /* Partner logos: see the .nebulous-partners rule in the 900px
       block above — a 768px tablet wrapped them onto two rows 144px
       tall when this lived here, so it is scoped to the whole
       touch-device range rather than to phones. */

    /* The corner ribbon (nebulous.css:1102) is positioned at
       right:-2.2rem and rotated 45deg — a design that assumes the card
       clips it. .pricing-card never set overflow, so the ribbon hangs
       into the page. Harmless at desktop widths where there is room
       to spare; at 412px it is 10px of real horizontal overflow. */
    /* (A phone-only `.pricing-card { overflow: hidden }` used to sit
       here. Removed as redundant: the base rule at line ~1108 already
       sets it at every width, which is what lets the restored corner
       ribbon clip to the card.) */

    /* 2j. Alternating image/text blocks stack instead of staying a row.
       .nebulous-alt-block (line ~1495) is display:flex with a 2.5rem
       gap and no wrap at any width, so on a handset the image and the
       copy fight over one screen's worth of space. It survives 412px
       only by squeezing both; at 320px it overflows by 9px outright.
       `.reverse` has to be neutralised too — a reversed COLUMN would
       put each image below its own caption, silently scrambling the
       reading order of every one of these sections. */
    body.template-nebulous .nebulous-alt-block,
    body.template-nebulous .nebulous-alt-block.reverse {
        flex-direction: column;
        gap: 1.25rem;
        align-items: stretch;
    }
    body.template-nebulous .nebulous-alt-block > * { min-width: 0; width: 100%; }
    body.template-nebulous .nebulous-alt-block img { max-height: 240px; }

    /* Its checklist needed no rule here in the end — see the
       `columns: 15rem 2` note on .nebulous-alt-block-checklist itself.
       Switching from a fixed column COUNT to a column WIDTH fixes it
       at every width at once, including the 756–900px tablet band that
       a rule in this block would have missed. Noted rather than left
       silent, so the next person looking for the phone fix finds it. */

    /* Anything genuinely wide scrolls inside its own box rather
       than widening the page. */
    body.template-nebulous table,
    body.template-nebulous pre { display: block; max-width: 100%; overflow-x: auto; }
    /* max-width only — deliberately NOT `height: auto`.
       This rule originally carried `height: auto` as a defensive pair,
       and it caused the card mini-banner overlap reported three times:
       `body.template-nebulous img` is (0,0,1,1), exactly the same
       specificity as `.pricing-card-image img` (line ~1194) which sets
       `height: 100%` so object-fit can crop the banner into its 88px
       box. Equal specificity, and this file's responsive tail loads
       last, so `height: auto` won — the image reverted to its own
       816x288 aspect, rendered 123px tall inside a 104px wrapper, and
       its top edge sat ~10px OVER the grey card header.

       It hid well: an <img> that has not finished loading reports
       naturalWidth 0 and collapses to ~2px, so a probe that measures
       before the bytes arrive sees a perfectly flush card. Measure
       card images only after `img.complete && img.naturalWidth`.

       max-width:100% alone does the overflow job this rule exists for;
       an image that wants auto height already has it by default. */
    body.template-nebulous img,
    body.template-nebulous video { max-width: 100%; }
}

/* -------------------------------------------------------------------
   3. Narrow handsets
   ------------------------------------------------------------------- */

@media (max-width: 420px) {
    body.template-nebulous .toolbarmain-text { width: 100%; text-align: center; }
    body.template-nebulous .product-hero-chips { grid-template-columns: 1fr; }

    /* The four toolbar controls need 346px on an iPhone SE and had
       344px — two pixels short, so Register still dropped to a second
       row while a Pixel held one. Rather than shrink the type again
       (already .8rem), take the last few pixels from the spacing,
       which nobody reads. */
    body.template-nebulous .toolbarmain { padding-inline: .35rem; gap: .3rem; }
    body.template-nebulous .toolbarmain-menu ul { gap: .3rem; }
    body.template-nebulous .toolbarmain-menu ul li > a { padding-inline: .35rem; }
}

/* -------------------------------------------------------------------
   4. Motion
   ------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .navmain-mobile-child::before { transition: none; }
}


/* ===================================================================
   SOCIAL ICON SETS  (added 2026-08-25)
   ===================================================================

   One block serving every placement zone and every style variant, fed
   by includes/SocialLinks.php. The brand colour arrives per-link as a
   --brand custom property on the element itself, which is what lets a
   single rule paint 33 networks without 33 selectors.

   Marks come from the bundled Font Awesome 5.10.1 PRO Brands webfont
   (.fab), from an inline <svg> for the three networks that release
   predates (X, TikTok, Threads), or from an admin-uploaded <img>.
   All three are sized identically here so a mixed row stays even.
   =================================================================== */

.social-set { display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: center; }

.social-set-item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: var(--social-size, 2.3rem);
    height: var(--social-size, 2.3rem);
    line-height: 1;
    text-decoration: none;
    transition: transform .15s ease, filter .15s ease, background-color .15s ease;
}
/* The three mark types, normalised to one optical size. */
.social-set-item i { font-size: calc(var(--social-size, 2.3rem) * 0.46); }
.social-set-item svg { width: calc(var(--social-size, 2.3rem) * 0.5); height: auto; display: block; }
.social-set-item img {
    width: calc(var(--social-size, 2.3rem) * 0.56);
    height: calc(var(--social-size, 2.3rem) * 0.56);
    object-fit: contain;
    display: block;
}
.social-set-item .social-fallback {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: calc(var(--social-size, 2.3rem) * 0.44);
}

/* ---- sizes ---- */
.social-set-sm { --social-size: 1.9rem; }
.social-set-md { --social-size: 2.3rem; }
.social-set-lg { --social-size: 2.9rem; }

/* ---- variant: brand tile (the default) ---- */
.social-set-tile .social-set-item {
    background: var(--brand, #555);
    color: #fff;
    border-radius: 4px;
}
/* ---- variant: brand circle ---- */
.social-set-circle .social-set-item {
    background: var(--brand, #555);
    color: #fff;
    border-radius: 50%;
}
/* ---- variant: outline ---- */
.social-set-outline .social-set-item {
    background: transparent;
    color: currentColor;
    border: 1px solid currentColor;
    border-radius: 50%;
    opacity: 0.8;
}
.social-set-outline .social-set-item:hover { color: var(--brand, #555); border-color: var(--brand, #555); opacity: 1; }
/* ---- variant: bare glyph ---- */
.social-set-bare .social-set-item {
    background: transparent;
    color: var(--brand, #555);
    width: auto; height: auto;
}
.social-set-bare .social-set-item i { font-size: calc(var(--social-size, 2.3rem) * 0.62); }
.social-set-bare .social-set-item svg { width: calc(var(--social-size, 2.3rem) * 0.66); }

/* ---- monochrome override: ignore brand colours entirely ---- */
.social-set-mono.social-set-tile .social-set-item,
.social-set-mono.social-set-circle .social-set-item { background: var(--color-primary-background); color: #fff; }
.social-set-mono.social-set-bare .social-set-item { color: var(--color-primary-background); }

.social-set-item:hover { transform: translateY(-2px); filter: brightness(1.12); }
.social-set-item:focus-visible { outline: 2px solid var(--color-primary-background); outline-offset: 2px; }

/* Snapchat's brand yellow needs dark ink to stay legible; it is the
   one network in the list whose colour is lighter than white text. */
.social-set-tile .social-set-snapchat,
.social-set-circle .social-set-snapchat { color: #1a1a1a; }

/* Zone placement: the bottom bar is a centred flex row, so push the
   icons to its right-hand end as asked. */
.site-footer-bottom .social-zone-footer_bottom { margin-left: auto; }

@media (max-width: 900px) {
    /* The bottom bar wraps and centres on a handset, so the auto
       margin would strand the icons rather than right-align them. */
    .site-footer-bottom .social-zone-footer_bottom { margin-left: 0; justify-content: center; width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .social-set-item { transition: none; }
    .social-set-item:hover { transform: none; }
}

/* Two fixed buttons live permanently in the bottom-right corner — the
   chat launcher (56px) and back-to-top. Measured at 1440px+, the chat
   launcher sat directly on top of the last social tile: both were
   clickable, neither reliably.

   :has() scopes the reserved gutter to bars that actually carry icons,
   so a storefront with the zone switched off keeps its full width. It
   is baseline-supported in every browser since 2023, and this file
   already leans on that era of CSS (clamp, dvh, aspect-ratio). */
.site-footer-bottom:has(.social-zone-footer_bottom) { padding-right: 6rem; }

@media (max-width: 900px) {
    /* The bar centres and wraps on a handset, so a right gutter would
       just shift it off-centre. Reserve height instead, because the
       launcher still covers that corner. */
    .site-footer-bottom:has(.social-zone-footer_bottom) {
        padding-right: 1rem;
        padding-bottom: 4.5rem;
    }
}

/* ---- Footer badge/logo groups: placeable, alignable, sizable ----
   (2026-08-25) Each group carries its zone as a class and its
   alignment + height cap as custom properties set inline by
   footer.php, so one rule serves all three zones and both groups
   rather than a selector per combination.

   The existing .nebulous-partners / .nebulous-legal-badges rules still
   provide each group's own gap and rhythm; these only add what is now
   admin-controlled. */
.footer-badge-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: var(--badge-align, center);
}
.footer-badge-group img {
    max-height: var(--badge-height, 24px);
    width: auto;
    height: auto;
    max-width: 100%;
}
/* In the About column the group is one item in a stacked column, so it
   needs its own breathing room; in the other two zones the parent
   already supplies it. */
.footer-badge-zone-about { margin: 1rem 0 1.25rem; gap: 0.6rem; }

@media (max-width: 900px) {
    /* Alignment is a desktop nicety — every footer band centres on a
       handset, so honouring "left" there would just look broken. */
    .footer-badge-group { justify-content: center; }
}

/* The footer's generic link rule, `.footer-column a { display: block }`
   (0,0,1,1), outranks `.social-set-item` (0,0,1,0) — so inside a footer
   column the tiles stopped being flex containers and every brand glyph
   pinned itself to the top of its tile: measured 0px above, 25px below
   on a 46px tile. It also inherited that rule's 0.5rem bottom margin.

   Qualifying the class with the element takes this to (0,0,2,1), which
   wins outright rather than relying on source order. Needed because the
   About column is now the default placement, so this is the common
   case, not an edge one. */
.social-set a.social-set-item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    padding: 0;
    border-bottom: 0;
}

/* ---- Comparison table: pin the price row height ----
   (2026-08-25) product-tables1 aligns its rows purely by every column
   carrying the same blocks at the same heights, and the vendor sheet
   fixes a height for every block in that stack — head 60px, head2
   50px, text 77px, each feature item 47px, buttons 50px — EXCEPT
   .product-tables1-price, which it leaves to its content.

   That works while every column has a price. The label column does
   not: its price slot is deliberately empty, so it collapsed to 0 and
   pushed all four of its feature groups 66px out of step with the
   plans beside it. Measured on a real 3-plan category before this rule.

   65px is the vendor's own value for .product-tables1-price-free — the
   "FREE" variant of this exact row — so it is the height the theme
   already considers correct for the price band, not a number invented
   here. */
.product-tables1 .product-tables1-box .product-tables1-price {
    height: 65px;
    box-sizing: border-box;
}

/* ── product-tables2: contain the plan ribbon ──────────────────────
   Added 2026-08-25 with the compact-table plan layout. The vendor's
   ribbon rule is an absolutely-positioned ::after at right:-71px, so
   it needs an ancestor that is positioned AND clipped or it hangs
   across the plan name instead of folding into the corner. The vendor
   CSS never establishes one, because its own reference markup wrapped
   the ribbon in a container this theme does not ship. */
.product-tables2.product-tables2-plans td.tableplan-heading {
    position: relative;
    overflow: hidden;
}

.product-tables2.product-tables2-plans td.tableplan-heading .tableplan-highlight {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* ── product-slider1: the plan labels ──────────────────────────────
   Added 2026-08-25 with the slider plan layout, then restyled as
   badges (Wayne: "put the links in a styled badge that highlights when
   hovered over").

   Two vendor quirks have to be worked around first:

   - `.product-slider1-slider-productname strong` is `position:absolute`
     with no offsets, so each label renders in place but contributes
     ZERO height. The names strip collapses and the feature row below
     slides up into it. Putting the badge on the BUTTON rather than the
     strong puts a real box back in the flow.
   - the vendor caps the label at `max-width:125px`, narrower than most
     plan names, so "Shared XXX-treme" wrapped after the hyphen. Wayne
     asked for all text inline, so the cap is removed and the text is
     nowrap. */
.product-slider1 .product-slider1-slider-productnames {
    align-items: stretch;
    gap: 0.5rem;
    padding-top: 0.75rem;
}

.product-slider1 .product-slider1-slider-productname {
    appearance: none;
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    margin: 0;
    cursor: pointer;
    font: inherit;
    color: inherit;

    /* The badge itself. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--color-box-background-divider, rgba(0, 0, 0, 0.12));
    border-radius: 999px;
    background: var(--color-box-background, #fff);
    transition: background-color .15s ease, border-color .15s ease,
                color .15s ease, transform .15s ease;
}

.product-slider1 .product-slider1-slider-productname strong {
    /* Undo the vendor's absolute positioning: inside a real badge the
       text should sit in the badge, not float over whatever follows. */
    position: static;
    max-width: none;
    white-space: nowrap;
    font-size: 1em;
    pointer-events: none;
}

.product-slider1 .product-slider1-slider-productname:hover,
.product-slider1 .product-slider1-slider-productname:focus-visible {
    background: var(--color-primary, #e8830c);
    border-color: var(--color-primary, #e8830c);
    color: #fff;
    transform: translateY(-1px);
}

.product-slider1 .product-slider1-slider-productname:focus-visible {
    outline: 2px solid var(--color-primary, #e8830c);
    outline-offset: 2px;
}

/* The plan currently on the slider reads as selected, not merely
   hovered — filled, and no longer underlined now that it is a badge. */
.product-slider1 .product-slider1-slider-productname[aria-current="true"] {
    background: var(--color-primary, #e8830c);
    border-color: var(--color-primary, #e8830c);
    color: #fff;
}

.product-slider1 .product-slider1-slider-productname[aria-current="true"] strong {
    text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
    .product-slider1 .product-slider1-slider-productname {
        transition: none;
    }
    .product-slider1 .product-slider1-slider-productname:hover {
        transform: none;
    }
}

/* The slider's CTA row. The vendor sets
   `.product-slider1-productoptions { display:flex;
   justify-content:space-between; margin:80px 0 0 0 }` because its own
   reference renders one card per billing cycle. Every plan here has a
   single monthly variant, so space-between would pin that lone card
   against the left edge with a void beside it — which is exactly how
   it looked. Centre it, give it a sensible width, and close the 80px
   gap, since the feature strip sits directly above. */
/* NOTE the .product-slider1-productcontainer in these selectors. The
   vendor's own rule is four classes deep
   (.product-slider1 .product-slider1-productcontainer
    .product-slider1-product .product-slider1-productoptions); a
   three-class override loses to it. Confirmed by reading the computed
   style rather than assuming — gap and flex-basis applied while
   justify-content and margin-top silently did not. */
.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-productoptions {
    justify-content: center;
    gap: 1.25rem;
    flex-wrap: wrap;
    margin-top: 1.75rem;
}

.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-productoptions .product-slider1-cta {
    flex: 0 1 22rem;
    max-width: 100%;
}

/* The slider card's spec list. The vendor has no list style inside
   .product-slider1-cta because its own reference puts the specs in the
   wide strip above the card; these rows live in the card instead (see
   partials/plan-slider.php for why), so they need their own rules. */
.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-cta .product-slider1-ctafeatures {
    list-style: none;
    margin: 0;
    padding: 0 1.25rem;
    text-align: left;
}

.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-cta .product-slider1-ctafeatures li {
    display: flex;
    align-items: baseline;
    gap: 0.55rem;
    padding: 0.45rem 0;
    border-bottom: 1px solid var(--color-box-background-divider, rgba(0, 0, 0, 0.08));
}

.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-cta .product-slider1-ctafeatures li:last-child {
    border-bottom: 0;
}

/* A tick drawn in CSS rather than an <i>, so the list does not depend
   on the Font Awesome build the storefront happens to ship — this
   theme bundles FA 5 Pro while its own CSS names FA 6 classes, a
   mismatch that has produced empty icons here before. */
.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-cta .product-slider1-ctafeatures li::before {
    content: "";
    flex: 0 0 auto;
    width: 0.42rem;
    height: 0.75rem;
    margin-top: 0.1rem;
    border: solid var(--color-primary, #e8830c);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* The price sat flush against the last spec row once the list moved
   into the card; the vendor's own spacing assumed nothing above it. */
.product-slider1 .product-slider1-productcontainer .product-slider1-product .product-slider1-cta .product-slider1-ctafeatures + .product-slider1-ctaprice {
    margin-top: 1rem;
}

/* ── Knowledgebase index ───────────────────────────────────────────
   Added 2026-08-26. The index was a bare <h2> plus <ul> per category,
   and the theme centres section headings sitewide — so every category
   title floated centred above a narrow left-aligned list. Two columns
   of proper cards instead, with the heading over its own list. */
/* Multi-column, not a grid.
   
   These cards are deliberately different heights — a category with one
   article is shorter than one with three — and a row grid handles that
   badly whichever way you set it: stretch them and short cards fill
   with dead space, don't stretch them and the rows go ragged. Either
   way an odd count leaves the last card alone on its own row.
   
   Columns flow the cards down and balance the column heights, so there
   are no ragged rows and no orphan. break-inside keeps a card whole
   instead of splitting it across a column break. */
/* ---------------------------------------------------------------
   Knowledgebase — index and article

   Rebuilt 2026-08-26 to match the card system the client area and
   Downloads use. It was a list of links under bare headings, which was
   correct and looked unfinished beside everything else.

   Not the Downloads treatment: those rows are FILES, so they earn a PDF
   icon, a size and a Download button. An article is a page you read, so
   a category card holds article rows and shows how many there are.

   Colours resolve through theme tokens, so light and dark both work
   from one definition.
   --------------------------------------------------------------- */

.kb-page {
    max-width: 1120px;
    margin: 0 auto 3rem;
}

.kb-card {
    position: relative;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 12px;
    padding: 1.35rem 1.5rem 1.5rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}

/* ---- index ---- */

.kb-index {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
    gap: 1.25rem;
    /* Cards vary in height with their article count; align to the top
       so a one-article card does not stretch to match a three. */
    align-items: start;
    margin-bottom: 1.5rem;
}

.kb-cat-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding-bottom: 0.9rem;
    margin-bottom: 0.35rem;
    border-bottom: 1px solid var(--color-border);
}

.kb-cat-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    border-radius: 10px;
    color: #fff;
    font-size: 0.95rem;
}

.kb-cat-text {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    min-width: 0;
}

.kb-cat-name {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--color-heading);
}

.kb-cat-count {
    font-size: 0.78rem;
    color: var(--color-body);
}

.kb-cat-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.kb-cat-list li + li { border-top: 1px solid var(--color-border); }

.kb-cat-list a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.7rem 0.2rem;
    font-size: 0.92rem;
    color: var(--color-heading);
    text-decoration: none;
    transition: color 0.14s ease, padding-left 0.14s ease;
}

.kb-cat-list a i {
    flex: 0 0 auto;
    font-size: 0.72rem;
    color: var(--color-border);
    transition: color 0.14s ease, transform 0.14s ease;
}

.kb-cat-list a:hover,
.kb-cat-list a:focus-visible {
    color: var(--color-primary-background);
    padding-left: 0.45rem;
    text-decoration: none;
}

.kb-cat-list a:hover i,
.kb-cat-list a:focus-visible i {
    color: var(--color-primary-background);
    transform: translateX(2px);
}

/* ---- article ---- */

.kb-back { margin: 0 0 1.1rem; }

.kb-eyebrow {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.4rem;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-body);
}

.kb-article h1 {
    margin: 0 0 1.1rem;
    font-size: 1.75rem;
    text-wrap: balance;
    /* !important: the vendor base rule centres every heading, which put
       a centred title over left-aligned body copy — the same clash the
       old .kb-index block carried a note about. An article is a
       document, so its title starts where its text starts. */
    text-align: left !important;
}

.kb-article-body {
    color: var(--color-body);
    line-height: 1.65;
    /* Long-form reading: hold the measure near 70 characters rather
       than letting a paragraph run the full card width. */
    max-width: 70ch;
}

.kb-article-body p { margin: 0 0 1rem; }
.kb-article-body p:last-child { margin-bottom: 0; }

/* ---- the "still stuck" card ---- */

.kb-next {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.25rem;
    flex-wrap: wrap;
    margin-top: 1.5rem;
    background:
        linear-gradient(135deg, rgba(240, 149, 70, 0.1) 0%, rgba(240, 149, 70, 0.02) 100%),
        var(--color-box-background);
}

.kb-next p { margin: 0; color: var(--color-body); font-size: 0.93rem; }
.kb-next strong { color: var(--color-heading); }

/* ---- empty ---- */

.kb-empty { text-align: center; padding: 2.5rem 1.5rem; color: var(--color-body); }
.kb-empty i { display: block; font-size: 1.9rem; margin-bottom: 0.7rem; color: var(--color-primary-background); opacity: 0.45; }
.kb-empty p { margin: 0; }

/* ---- buttons ----
   Scoped through .kb-page so they beat nebulous.css's own
   body.template-nebulous button rule (0,1,2), which otherwise
   overrides padding, radius and colour — the same trap hit twice
   already today. */

.kb-page a.kb-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.6rem 1.15rem;
    border: none;
    border-radius: 6px;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1.2;
    text-decoration: none;
    transition: filter 0.15s ease;
}

.kb-page a.kb-btn:hover { filter: brightness(1.07); text-decoration: none; }

.kb-page a.kb-btn-quiet {
    background: transparent;
    border: 1px solid var(--color-border);
    color: var(--color-body);
}

.kb-page a.kb-btn-quiet:hover {
    border-color: var(--color-primary-background);
    color: var(--color-primary-background);
    background: transparent;
    filter: none;
}

@media (prefers-reduced-motion: reduce) {
    .kb-cat-list a,
    .kb-cat-list a i,
    .kb-cat-list a:hover,
    .kb-cat-list a:hover i {
        transition: none;
        transform: none;
        padding-left: 0.2rem;
    }
}

/* ── product-tables1: let a plan name use two lines ────────────────
   Added 2026-08-26, after the VPS comparison table went live reading
   "VPS" where it should have said "VPS Standard".

   The vendor centres the heading with the old line-height trick —
   `height:60px; line-height:60px; overflow:hidden` — which only works
   for a single line. A two-word plan name wraps, the second line lands
   below the 60px box, and overflow:hidden eats it. Measured on the
   live page: scrollHeight 120 against clientHeight 59, so exactly half
   the name was being clipped.

   Flex centring instead, so one line still sits in the middle and two
   lines both fit. Same specificity as the vendor rule (three classes),
   and later in the cascade, so no !important is needed. */
.product-tables1 .product-tables1-box .product-tables1-head,
.product-tables1 .product-tables1-box .product-tables1-head2 {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1.2;
    padding: 0 0.5rem;
    text-align: center;
}

/* ── Downloads: framed cards ───────────────────────────────────────
   Added 2026-08-26. The page was a centred <h2> over a left-aligned
   bullet list — the same mismatch the knowledgebase index had, and it
   read as unfinished rather than designed.

   The whole card is the link, so the hit area is the card and not just
   the few words of the title. */
.download-group {
    margin: 2rem 0 2.5rem;
}

.download-group h2 {
    text-align: left;
    margin: 0 0 1rem;
    padding: 0;
    font-size: 1.2rem;
}

.download-cards {
    display: grid;
    /* 26rem rather than 21: at 21 the grid fits three across on a
       normal screen and each card has to stack chip, two-line
       title, description and button into ~275px. Two roomier
       cards read better than three cramped ones, and it still
       goes to three on a wide monitor. */
    grid-template-columns: repeat(auto-fit, minmax(26rem, 1fr));
    gap: 1rem;
}

.download-cards .download-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.1rem 1.25rem;
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-box-background-divider, rgba(0, 0, 0, 0.1));
    border-radius: var(--border-radius-default, 6px);
    text-decoration: none;
    color: inherit;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

/* An orange rail down the left edge, echoing the one on the hero's
   text box. Tying the cards to a device the site already uses reads as
   deliberate, where simply adding more orange to six cards at once
   would compete with the hero and the Get Started bar and leave the
   page without a focal point. */
.download-cards .download-card {
    border-left: 4px solid var(--color-primary, #e8830c);
}

.download-cards .download-card:hover,
.download-cards .download-card:focus-visible {
    border-color: var(--color-primary, #e8830c);
    /* A warm tint rather than a grey shadow, so the lift feels like the
       rest of the site rather than a generic card hover. */
    background: linear-gradient(0deg,
        rgba(232, 131, 12, 0.05) 0%, rgba(232, 131, 12, 0.02) 100%),
        var(--color-box-background, #fff);
    box-shadow: 0 6px 18px rgba(232, 131, 12, 0.16);
    transform: translateY(-2px);
}

/* The file-type chip. Drawn rather than an icon font, so it does not
   depend on which Font Awesome build the storefront happens to ship —
   this theme bundles FA 5 while its own CSS names FA 6 classes. */
.download-cards .download-card-type {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3.5rem;
    border-radius: 4px;
    background: var(--color-primary, #e8830c);
    color: #fff;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
}

/* The drawn file icon. Sized to the same box the lettered chip used,
   so a card with an icon and a card without line up.

   The icon's orange is baked into the SVG rather than themed from
   here: an SVG loaded through an img tag is an isolated document and
   cannot read this page's custom properties. Inline the SVG if it ever
   needs to follow the theme. */
.download-cards .download-card-icon {
    flex: 0 0 auto;
    width: 2.9rem;
    height: auto;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.14));
    transition: transform .15s ease;
}

.download-cards .download-card:hover .download-card-icon,
.download-cards .download-card:focus-visible .download-card-icon {
    transform: translateY(-2px) rotate(-3deg);
}

@media (prefers-reduced-motion: reduce) {
    .download-cards .download-card-icon {
        transition: none;
    }
    .download-cards .download-card:hover .download-card-icon {
        transform: none;
    }
}

.download-cards .download-card-body {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
    flex: 1 1 auto;
}

.download-cards .download-card-body strong {
    font-size: 1rem;
    line-height: 1.3;
}

.download-cards .download-card-desc {
    font-size: 0.87rem;
    line-height: 1.35;
    opacity: 0.75;
}

.download-cards .download-card-meta {
    font-size: 0.75rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    opacity: 0.55;
}

.download-cards .download-card-cta {
    flex: 0 0 auto;
    align-self: center;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--color-primary, #e8830c);
    border-radius: 999px;
    color: var(--color-primary, #e8830c);
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}

.download-cards .download-card:hover .download-card-cta,
.download-cards .download-card:focus-visible .download-card-cta {
    background: var(--color-primary, #e8830c);
    color: #fff;
}

.download-cards .download-card:hover .download-card-body strong,
.download-cards .download-card:focus-visible .download-card-body strong {
    color: var(--color-primary, #e8830c);
}

@media (max-width: 30rem) {
    .download-cards .download-card {
        flex-wrap: wrap;
    }
    .download-cards .download-card-cta {
        margin-left: 4rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .download-cards .download-card {
        transition: none;
    }
    .download-cards .download-card:hover {
        transform: none;
    }
}


/* ---------------------------------------------------------------
   Dark/light toggle — floating, bottom-left, labelled

   SELECTORS USE THE ID DELIBERATELY. nebulous.css's own
   `body.template-nebulous button` rule (specificity 0,1,2) paints every
   bare <button> with --color-primary-background, the orange accent, at
   0.6rem 1.25rem padding. A plain `.nebulous-color-toggle` class
   (0,1,0) loses to it outright, which is what left this looking like a
   large orange block after the first attempt at styling it.

   Measured, not guessed: computed background was rgb(240,149,70) —
   exactly --color-primary-background — with the vendor's padding and
   radius, while only the properties my higher-specificity dark rule
   declared were getting through.

   It used to sit in the toolbar with no styling of its own, so it
   inherited app.css's generic `button` rule (orange background, 0.5rem
   padding) and rendered as a large orange block taller than the row it
   was in. Moved out entirely 2026-08-26 rather than restyled in place.

   Bottom-LEFT is not arbitrary: .cd-top (bottom: 66px; right: 16px)
   and #hca-chat-widget (bottom/right: 1.5rem) both sit bottom-right,
   so the left corner is the only one free.

   z-index sits just under the chat widget's 1000 — this should never
   cover an open chat panel, though the two are in opposite corners
   anyway.
   --------------------------------------------------------------- */

#nebulous-color-toggle.nebulous-color-toggle {
    position: fixed;
    bottom: 1.5rem;
    left: 1.5rem;
    z-index: 999;

    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;

    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    background: #1f2430;
    color: #f3f4f6;

    font-family: inherit;
    font-size: 0.8125rem;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;

    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
    transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}

#nebulous-color-toggle.nebulous-color-toggle:hover {
    background: #2a3040;
    border-color: rgba(255, 255, 255, 0.32);
    transform: translateY(-1px);
}

#nebulous-color-toggle.nebulous-color-toggle:focus-visible {
    outline: 2px solid var(--hca-accent, #f0862a);
    outline-offset: 2px;
}

.nebulous-color-toggle-glyph {
    font-size: 1rem;
    line-height: 1;
}

/* In dark mode the dark pill would sink into the page, so it inverts
   to stay findable. */
body.colortoggle-dark #nebulous-color-toggle.nebulous-color-toggle {
    background: #f3f4f6;
    color: #1f2430;
    border-color: rgba(0, 0, 0, 0.18);
}

body.colortoggle-dark #nebulous-color-toggle.nebulous-color-toggle:hover {
    background: #ffffff;
    border-color: rgba(0, 0, 0, 0.3);
}

/* On a phone the labelled pill would crowd the bottom of the screen,
   so it drops to the glyph alone and keeps a comfortable tap target. */
@media (max-width: 600px) {
    #nebulous-color-toggle.nebulous-color-toggle {
        bottom: 1rem;
        left: 1rem;
        padding: 0.6rem;
        gap: 0;
    }
    .nebulous-color-toggle-label {
        /* Hidden visually, still read aloud by a screen reader. */
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

@media (prefers-reduced-motion: reduce) {
    #nebulous-color-toggle.nebulous-color-toggle,
    #nebulous-color-toggle.nebulous-color-toggle:hover {
        transition: none;
        transform: none;
    }
}


/* ---------------------------------------------------------------
   Dark mode: neutral near-black, not navy

   Nebulous's own dark palette sets --background-body and
   --background-banner to #0d0d1b, which is a blue-tinted near-black.
   Against the orange accent it read as navy rather than as a neutral
   dark, which Wayne asked to change 2026-08-26.

   Only these two variables carry the tint — every other colour in the
   vendor's .colortoggle-dark block is already neutral grey (#2f2f2f
   boxes, #444/#4f4f4f secondary), so overriding the pair is enough and
   nothing else needs re-toning to match.

   Overridden here rather than edited in vendor/css/layout.css: that
   file is the purchased theme, and a change there is lost the next
   time it is updated.
   --------------------------------------------------------------- */

body.colortoggle-dark {
    --background-body: #111111;
    --background-banner: #111111;

    /*
     * A REAL GAP IN THE THEME, not a preference.
     *
     * Nebulous's dark palette redefines --color-tertiary-FOREGROUND to
     * #fff but never redefines --color-tertiary-BACKGROUND, so it stays
     * #f5f5f5 from the light palette. Anything using the pair renders
     * white on white in dark mode.
     *
     * Wayne hit this on /category.php?slug=web-design 2026-08-26: the
     * "FEATURES" band on each plan card was invisible until he selected
     * the text. The same band reads "Overview" on categories that show
     * a tagline instead, and the Company page's "Open a support ticket"
     * button was measured at #f1f1f1 on #f5f5f5 — contrast ratio 1.04.
     *
     * Fixed at the variable rather than per-component, so every current
     * and future consumer of the pair is covered. The values follow the
     * vendor's own dark greys: #2f2f2f boxes, #383838 borders.
     */
    --color-tertiary-background: #2b2b2b;
    --color-tertiary-background-gradient-top: #2b2b2b;
    --color-tertiary-background-gradient-bottom: #2b2b2b;
    --color-tertiary-background-border: #383838;

    --color-tertiary-active-background: #343434;
    --color-tertiary-active-background-gradient-top: #343434;
    --color-tertiary-active-background-gradient-bottom: #343434;
    --color-tertiary-active-background-border: #383838;
}


/* ---------------------------------------------------------------
   Service manage panel (account/cpanel-manage.php)

   Built 2026-08-26. The page shipped as bare h1/p/form markup — it
   worked and looked unfinished, which is the wrong impression for the
   first thing a customer sees after buying hosting.

   Every colour here comes from the theme's own tokens rather than
   literals, so light and dark both work with no second palette to keep
   in sync: --color-box-background is #ffffff/#2f2f2f,
   --color-heading #243141/#ffffff, --color-body #696969/#a9aaac. The
   one deliberate constant is the orange accent, which the theme keeps
   in both modes.
   --------------------------------------------------------------- */

.ca {
    max-width: 1040px;
    margin: 0 auto 3rem;
}

/* ---- header band ---- */

.ca-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.5rem;
    flex-wrap: wrap;
    padding: 1.75rem 0 1.25rem;
    border-bottom: 2px solid var(--color-border);
    margin-bottom: 1.75rem;
}

.ca-back {
    display: inline-block;
    margin-bottom: 0.4rem;
    font-size: 0.85rem;
    text-decoration: none;
    color: var(--color-body);
}

.ca-back:hover { color: var(--color-primary-background); }

/* .ca-back was written for a link ABOVE content and carries
   margin-bottom. At the foot of the invoice and quote documents it
   needs the space on the other side, so the wrapper supplies it rather
   than the shared class growing a second personality. */
.invoice-view-back {
    margin: 1.25rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border, #e6e8ec);
}

.ca-head h1 {
    margin: 0;
    /* The service name can be long; let it wrap sensibly rather than
       forcing the status pill onto its own line. */
    text-wrap: balance;
}

.ca-domain {
    margin: 0.35rem 0 0;
    color: var(--color-body);
    font-size: 0.95rem;
}

.ca-domain i { color: var(--color-primary-background); margin-right: 0.35rem; }

.ca-status {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    background: rgba(34, 197, 94, 0.12);
    border: 1px solid rgba(34, 197, 94, 0.35);
    color: #16a34a;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
}

.ca-status-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #22c55e;
}

/* Dark mode needs a lighter green — #16a34a on #2f2f2f is muddy. */
body.colortoggle-dark .ca-status { color: #4ade80; }

/* ---- alerts ---- */

.ca-alert {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.85rem 1.1rem;
    border-radius: 6px;
    border-left: 4px solid;
    margin: 0 0 1.5rem;
    font-size: 0.95rem;
}

.ca-alert-error {
    background: rgba(220, 38, 38, 0.08);
    border-color: #dc2626;
    color: #b91c1c;
}

.ca-alert-success {
    background: rgba(34, 197, 94, 0.1);
    border-color: #22c55e;
    color: #15803d;
}

body.colortoggle-dark .ca-alert-error { color: #fca5a5; }
body.colortoggle-dark .ca-alert-success { color: #86efac; }

/* ---- cards ---- */

.ca-grid {
    display: grid;
    /* Actions carry two tiles and need the room; details is a list. */
    grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
    gap: 1.5rem;
    margin-bottom: 1.5rem;
}

.ca-card {
    position: relative;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 10px;
    padding: 1.6rem 1.75rem 1.75rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    overflow: hidden;
}

/* A short accent rule at the top edge of every card — the one
   decorative flourish, tying the panel to the orange accent without
   colouring anything functional. */
.ca-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 74px;
    height: 3px;
    background: var(--color-primary-background);
}

.ca-card-title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 0.4rem;
    font-size: 1.15rem;
    color: var(--color-heading);
}

.ca-card-title i {
    color: var(--color-primary-background);
    font-size: 0.95em;
}

.ca-card-lead {
    margin: 0 0 1.4rem;
    color: var(--color-body);
    font-size: 0.92rem;
    max-width: 58ch;
}

/* ---- the two SSO tiles ---- */

.ca-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 1.1rem;
}

.ca-action {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.45rem;
    padding: 1.35rem;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    background: var(--background-body);
    transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.ca-action:hover {
    border-color: var(--color-primary-background);
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
}

.ca-action-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 9px;
    margin-bottom: 0.35rem;
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-primary-background);
    font-size: 1.15rem;
}

.ca-action-title {
    font-weight: 700;
    font-size: 1.02rem;
    color: var(--color-heading);
}

.ca-action-text {
    font-size: 0.87rem;
    color: var(--color-body);
    line-height: 1.45;
    /* Push the button to the bottom so both tiles align even when one
       description wraps to an extra line. */
    flex: 1;
}

/* ---- buttons ----

   Scoped as `.ca button.ca-btn` rather than the bare class.
   nebulous.css's own `body.template-nebulous button` rule (0,1,2) beats
   a plain `.ca-btn` (0,1,0) and was overriding the padding,
   radius and border outright — the primary button only LOOKED right
   because the vendor colour happens to be the same orange, while the
   quiet variant came out solid orange instead of outlined. Measured,
   not guessed: computed padding was 9.6px 20px (the vendor's 0.6rem
   1.25rem) with border-width 0. Same trap as the dark/light toggle
   earlier the same day. */

.ca button.ca-btn {
    margin-top: 0.9rem;
    padding: 0.6rem 1.15rem;
    border: none;
    border-radius: 6px;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    transition: filter 0.15s ease;
}

.ca button.ca-btn:hover { filter: brightness(1.07); }

.ca button.ca-btn-quiet {
    background: transparent;
    border: 1px solid var(--color-primary-background);
    color: var(--color-primary-background);
}

.ca button.ca-btn-quiet:hover {
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    filter: none;
}

/* ---- details list ---- */

.ca-facts {
    margin: 0;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.55rem 1rem;
    font-size: 0.9rem;
}

.ca-facts dt {
    color: var(--color-body);
    white-space: nowrap;
}

.ca-facts dd {
    margin: 0;
    font-weight: 700;
    color: var(--color-heading);
    /* Usernames and hostnames are identifiers — read them as such, and
       let long ones wrap instead of widening the card. */
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.86rem;
    word-break: break-word;
}

/* ---- account usage ---- */

.ca-usage {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* minmax(0, …) not 1fr: a long figure would otherwise push a column
       wider than its share, the same squeeze that hit the domain
       results grid earlier. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: 0.9rem;
}

.ca-usage-item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "icon  label"
        "icon  value"
        "bar   bar"
        "max   max";
    align-items: center;
    gap: 0.15rem 0.7rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--color-border, rgba(128, 128, 128, 0.28));
    border-radius: 10px;
}

.ca-usage-icon {
    grid-area: icon;
    align-self: center;
    width: 2.1rem;
    height: 2.1rem;
    display: grid;
    place-items: center;
    border-radius: 8px;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    font-size: 0.9rem;
}

.ca-usage-label {
    grid-area: label;
    font-size: 0.82rem;
    color: var(--color-body);
}

.ca-usage-value {
    grid-area: value;
    font-weight: 700;
    font-size: 1.15rem;
    line-height: 1.2;
    color: var(--color-heading);
    /* Figures that sit in a grid should line up as figures. */
    font-variant-numeric: tabular-nums;
}

.ca-usage-bar {
    grid-area: bar;
    margin-top: 0.6rem;
    height: 6px;
    border-radius: 999px;
    background: var(--color-border, rgba(128, 128, 128, 0.28));
    overflow: hidden;
}

.ca-usage-fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--color-primary-background);
}

.ca-usage-max {
    grid-area: max;
    margin-top: 0.35rem;
    font-size: 0.76rem;
    color: var(--color-body);
}

/* "Unlimited" is an answer, not an absence — it gets the same weight as
   a real limit rather than reading like a missing value. */
.ca-usage-unlimited {
    font-weight: 600;
}

/* At the limit is the one state worth interrupting for: the next email
   account or database simply will not be created. */
.ca-usage-item.is-maxed {
    border-color: #c2410c;
}

.ca-usage-item.is-maxed .ca-usage-fill,
.ca-usage-item.is-maxed .ca-usage-icon {
    background: #c2410c;
}

.ca-usage-item.is-maxed .ca-usage-max {
    color: #c2410c;
    font-weight: 600;
}

.ca-usage-empty {
    margin: 0;
    font-size: 0.9rem;
    color: var(--color-body);
}

/* ---- /development: the opening statement above the explorer ---- */

/* THE LEDE SITS INSIDE THE CARDS, not beside them.
   A centred block left the page empty either side; a two-column split
   left it empty on one. Five columns with the words occupying the
   middle two puts content across the full width and still gives the
   heading the most prominent position on the row. */
.dev-lede {
    max-width: var(--container-maxwidth, 1250px);
    margin: 2.5rem auto 3rem;
    /* NO HORIZONTAL PADDING. This carried `0 1.25rem`, which put the
       whole card row 20px inside the container everything else lines up
       to — measured at three widths: 144 against 124, 78 against 58, 67
       against 47. It is already inside main, which now IS the shared
       container, so any inset here is a second gutter on top of the
       real one. */
    padding: 0;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    grid-auto-flow: row dense;
    gap: 0.85rem;
    align-items: start;
}

/* Explicitly placed; the cards then auto-fill every cell around it.
   `dense` is what lets a card backfill the first column of row one,
   which auto-placement would otherwise skip because the lede is
   earlier in the source. */
/* A box like the others. It sits in the same row as eight cards, so
   being the only thing on that row WITHOUT a surface made it read as
   floating rather than as the row's centrepiece. */
.dev-lede-main {
    grid-column: 2 / span 2;
    grid-row: 1;
    align-self: stretch;
    display: grid;
    align-content: center;
    padding: 1.6rem 1.5rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 10px;
    background: var(--color-box-background, #fff);
}

/* The theme centres h1 globally. Left here, the heading sat centred
   above an eyebrow and a paragraph that were not, which reads as a
   mistake rather than a choice. */
.dev-lede-main h1 { text-align: left; }

/* ---- the proof cluster beside the lede ---- */

/* Not a grid of its own — its children ARE the parent's grid items, so
   they can flow into the cells around the lede. The list element stays
   in the markup because eight related statements are a list, and a
   screen reader should be told how many there are. */
.dev-proof {
    list-style: none;
    margin: 0;
    padding: 0;
    display: contents;
}

/* SAME SURFACE AS THE SECTION AROUND THEM.
   These were --color-box-background (#ffffff) sitting on
   --background-body (#f8f8f8): four white tiles on a grey band, which
   read as a mismatch rather than as cards. They now share the page's
   own background and are defined by their border instead, so nothing
   on this row disagrees about what colour the surface is.

   `transparent` rather than naming the token: the lede sits on
   whatever the theme's body colour is, and in dark mode that is a
   different value again. Inheriting cannot drift; repeating the token
   can. */
/* LIGHTER THAN THE PAGE, and the same as the lede box beside them.
   These were transparent, which matched the grey body but left the
   whole row flat. Now every surface in the row is
   --color-box-background and the row reads as one set of cards on a
   page, rather than as outlines drawn on the background. */
.dev-proof-card {
    display: grid;
    gap: 0.29rem;
    align-content: start;
    height: 100%;
    padding: 1.1rem 1.15rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 10px;
    background: var(--color-box-background, #fff);
}

.dev-proof-icon { font-size: 1.32rem; line-height: 1; }

/* Spans the whole row. Laid out horizontally rather than stacked,
   because at full width a stacked card leaves a long empty stripe to
   the right of two short lines. */
.dev-proof-card.is-wide {
    grid-column: 1 / -1;
    grid-template-columns: auto minmax(0, 14rem) minmax(0, 1fr);
    align-items: center;
    gap: 0.25rem 1.1rem;
}

/* THE CARD COUNT HAS TO DIVIDE BY THE COLUMN COUNT, or the last row
   ends with a hole.

   At five columns the lede takes two of row one, leaving exactly eight
   card slots — so the ninth spans the full row beneath and everything
   is filled. At three columns the lede takes a row of its own and
   there are nine cards for nine cells, which only works if the ninth
   stops being wide. At two columns the wide card spans both and the
   remaining eight make four clean rows.

   Same nine cards, three different arrangements, no gaps in any. */
@media (max-width: 1100px) {
    .dev-proof-card.is-wide {
        grid-column: auto;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: start;
    }
}

@media (max-width: 780px) {
    .dev-proof-card.is-wide {
        grid-column: 1 / -1;
        grid-template-columns: auto minmax(0, 1fr);
    }
}

@media (max-width: 520px) {
    .dev-proof-card.is-wide { grid-template-columns: minmax(0, 1fr); }
}


.dev-proof-card strong {
    color: var(--color-heading, #111827);
    font-size: 1.06rem;
}

.dev-proof-card span:not(.dev-proof-icon) {
    color: var(--color-body, #6b7280);
    font-size: 0.94rem;
    line-height: 1.5;
}

/* Below the point where two columns of prose plus four cards stop
   fitting, the cluster goes under the words rather than beside them. */
/* Three columns, with the lede taking a full row of its own. Five
   columns of card at this width would be about 14rem each, which is
   too narrow for a sentence. */
@media (max-width: 1100px) {
    .dev-lede { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .dev-lede-main { grid-column: 1 / -1; grid-row: auto; padding-bottom: 0.5rem; }
}

@media (max-width: 780px) {
    .dev-lede { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 520px) {
    .dev-lede { grid-template-columns: minmax(0, 1fr); }
}

.dev-eyebrow {
    margin: 0 0 0.6rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    /* Fallback on every var(): an undefined custom property does not
       fall back to the previous rule, it invalidates the whole
       declaration and the text renders in the inherited colour. */
    color: var(--color-primary-background, #f09546);
}

.dev-lede h1 {
    margin: 0 0 0.7rem;
    line-height: 1.08;
    letter-spacing: -0.02em;
    color: var(--color-heading, #111827);
    text-wrap: balance;
}

.dev-lede-text {
    margin: 0 0 1.5rem;
    font-size: 1.06rem;
    color: var(--color-body, #4b5563);
}

/* A short line under the tagline, not a paragraph. Given weight
   rather than made quieter — at three or four words it is a statement
   of terms, and set small and grey it would read as a footnote. */
.dev-lede-body {
    margin: 0 0 1.4rem;
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--color-heading, #111827);
}

.dev-lede-cta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.7rem;
}

/* The line under a section band. Centred to sit under the heading it
   belongs to, and held to a readable measure rather than running the
   full container width — a single sentence stretched across 1250px is
   harder to read than the same sentence in a column. */
.dev-section-lead {
    max-width: 62ch;
    margin: -0.5rem auto 2rem;
    padding: 0 1.25rem;
    text-align: center;
    font-size: 1rem;
    line-height: 1.6;
    color: var(--color-body, #4b5563);
}

/* ---- /development: the split-pane service explorer ---- */

/* Switching panels is done in CSS, not JavaScript. storefront-ajax.js
   swaps #site-content with innerHTML and innerHTML never executes
   scripts, so a JS-driven explorer would be dead on every in-site
   navigation here and alive only on a hard refresh — a bug that works
   whenever you test it. Radios also give correct keyboard behaviour
   for free: arrow keys move within a radio group, which is exactly
   the interaction this wants. */

.dev-explorer {
    /* Clear of whatever follows — the Key Features band sat directly
       against the explorer's lower edge. */
    margin: 0 0 3.5rem;
    display: grid;
    /* Wide enough for the longest service name on one line.
       "Billing & Subscription Automation" is the constraint; at 17rem
       it wrapped, and a wrapped row in a list of thirteen makes the
       whole rail look ragged. The panel beside it gives up the same
       width and still has roughly 740px of reading column at a 1250px
       container, which is more than the prose needs. */
    grid-template-columns: minmax(0, 21.5rem) minmax(0, 1fr);
    border: 1px solid var(--color-box-background-border, #d7dbe0);
    border-radius: 12px;
    overflow: hidden;
    background: var(--color-box-background, #fff);
}

/* Visually hidden, not display:none — a display:none radio is removed
   from the tab order and the keyboard cannot reach the explorer at
   all. */
.dev-explorer .dev-radio {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ---- left index ---- */

.dev-index {
    border-right: 1px solid var(--color-box-background-border, #d7dbe0);
    padding: 0 0 0.5rem;
    /* WHITE, matching the card this rail sits INSIDE.
       
       Measured rather than assumed, and the first attempt was wrong
       because of it: I read --background-body (#f2f3f5, the page) as
       what sat behind this panel and lightened it a shade off that.
       The rail is not on the page at all — it is inside .dev-explorer,
       which is #ffffff. So a near-grey panel was sitting on a white
       card and read exactly as Wayne described: "looks like the card
       background is grey?"
       
       The border-right already divides the rail from the detail pane,
       so the panel does not need a fill of its own to be legible. */
    background: #fff;
}

.dev-index-head {
    margin: 0;
    padding: 0.95rem 1.1rem 0.7rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--color-body, #6b7280);
}

/* SPECIFICITY, not styling. `body.template-nebulous label` is
   (0,1,2) and sets display:block plus a 1rem bottom margin — right
   for a form field, wrong for a two-line nav row. It beat the plain
   class here, so the grid never applied: the spans fell back to
   inline and a wrapped service name started again under the number
   instead of staying in its own column, and every row carried a
   1rem gap it did not ask for.

   `.dev-index label.dev-index-item` is (0,2,1), which wins on class
   count regardless of source order. Same trap as the tool buttons
   earlier in this file. */
.dev-index label.dev-index-item {
    display: grid;
    margin-bottom: 0;
    grid-template-columns: 1.6rem 1.4rem minmax(0, 1fr);
    align-items: center;
    gap: 0.1rem;
    padding: 0.58rem 0.9rem;
    cursor: pointer;
    font-size: 0.87rem;
    color: var(--color-body, #4b5563);
    border-left: 3px solid transparent;
    transition: background 0.14s ease, color 0.14s ease;
}

.dev-index label.dev-index-item:hover {
    background: var(--color-box-background, #fff);
    color: var(--color-heading, #111827);
}

.dev-index-num {
    font-size: 0.7rem;
    opacity: 0.5;
    font-variant-numeric: tabular-nums;
}

.dev-index-icon { font-size: 0.95rem; line-height: 1; }

/* Long service names wrap inside the rail rather than widening it. */
.dev-index-label { min-width: 0; }

/* ---- right pane ---- */

/* Generous bottom padding. With the call to action removed, the last
   paragraph or checklist sat almost against the panel edge and ran
   straight into the section below it. */
.dev-pane { display: none; padding: 1.7rem 1.9rem 3rem; }

/* A gentle arrival. Not a `wow` class: WOW.js measures position to
   decide whether to reveal, and anything inside a display:none panel
   measures zero — so a panel opened after scrolling would stay
   permanently invisible. Plain CSS has no such dependency. */
@keyframes dev-pane-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

.dev-pane-head {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin: 0 0 1rem;
}

.dev-pane-icon { font-size: 1.5rem; line-height: 1; }

.dev-pane-head h3 {
    margin: 0;
    font-size: 1.35rem;
    color: var(--color-heading, #111827);
    letter-spacing: -0.015em;
}

.dev-pane-lead {
    color: var(--color-body, #4b5563);
    margin: 0 0 1.2rem;
    /* The lead is the one line everyone reads. Slightly larger than
       the body so the eye starts in the right place. */
    font-size: 1.05rem;
    line-height: 1.55;
}

/* ONE COLUMN UNLESS THERE IS ART.
   This reserved a 15rem art column on every panel whether or not an
   image existed, so eleven text-only panels wrapped their paragraphs
   at roughly two thirds width against an empty gutter. The column now
   appears only when a panel actually has a picture to put in it. */
.dev-pane-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.8rem;
    align-items: start;
}

.dev-pane-body.has-art {
    grid-template-columns: minmax(0, 1fr) minmax(0, 15rem);
}

/* No 62ch cap. That measure is right for a full-width article; inside
   a panel that is already narrowed by the index rail beside it, it
   wraps the text a second time and leaves a column of empty white to
   the right of every paragraph. The panel is the measure. */
.dev-pane-text p { margin: 0 0 1.1rem; color: var(--color-body, #4b5563); }

.dev-pane-art img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 10px;
}

.dev-check { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.42rem; }

.dev-check li {
    position: relative;
    padding-left: 1.55rem;
    font-size: 0.9rem;
    color: var(--color-body, #4b5563);
}

/* The tick is drawn from the storefront's Font Awesome 5 — the admin
   panel runs FA6, where this glyph has a different name, and a name
   from the wrong set renders nothing at all and does so silently. */
.dev-check li::before {
    content: "\f00c";
    font-family: "Font Awesome 5 Free", "Font Awesome 5 Pro";
    font-weight: 900;
    position: absolute;
    left: 0;
    top: 0.15em;
    font-size: 0.74rem;
    /* Green, matching the ticks on /web-app-development.php rather than
       inventing a second tick colour for the same glyph. Wayne
       2026-09-03: "Can we put the green check marks also."

       #177245 measures 5.95:1 on this panel's white and 5.44:1 on the
       cream card the landing page uses, so one value is correct in both
       places. The orange it replaces was 2.31:1 — a tick nobody with
       imperfect eyesight could reliably see. */
    color: #177245;
}

.dev-pane-tool { margin: 0 0 1.4rem; }


/* On a phone the two columns become one and the rail sits on top —
   a 17rem sidebar beside a 62ch measure does not fit a 375px screen. */
@media (max-width: 780px) {
    .dev-explorer { grid-template-columns: minmax(0, 1fr); }
    .dev-index { border-right: 0; border-bottom: 1px solid var(--color-box-background-border, #d7dbe0); }
    .dev-pane { padding: 1.3rem 1.2rem 1.5rem; }
    .dev-pane-body { grid-template-columns: minmax(0, 1fr); gap: 1.2rem; }
    .dev-pane-art { max-width: 15rem; }
}

/* ---- which panel is showing ---- */
/* One pair per index. Written out rather than looped because CSS has
   no loop; MAX is 20, comfortably above the eleven services plus the
   functional panels, and an explorer with more entries than this
   would be unusable long before it ran out of rules. */

/* THE SELECTED SERVICE IS TINTED, not just barred.
   
   Wayne: "the active state colour should be our light orange?" It was
   white — which worked while the rail behind it was grey, and stopped
   working the moment the rail went white too, leaving the orange left
   border doing the job alone.
   
   rgba rather than a mixed hex, so the tint sits over whatever is
   behind it and still reads when the theme's dark mode swaps the
   panel underneath. 0.14 is enough to see against white without
   turning the row into a button. */
#dev-svc-0:checked ~ .dev-index label.dev-index-item[for="dev-svc-0"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-0:checked ~ .dev-panes #dev-pane-0 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-1:checked ~ .dev-index label.dev-index-item[for="dev-svc-1"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-1:checked ~ .dev-panes #dev-pane-1 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-2:checked ~ .dev-index label.dev-index-item[for="dev-svc-2"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-2:checked ~ .dev-panes #dev-pane-2 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-3:checked ~ .dev-index label.dev-index-item[for="dev-svc-3"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-3:checked ~ .dev-panes #dev-pane-3 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-4:checked ~ .dev-index label.dev-index-item[for="dev-svc-4"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-4:checked ~ .dev-panes #dev-pane-4 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-5:checked ~ .dev-index label.dev-index-item[for="dev-svc-5"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-5:checked ~ .dev-panes #dev-pane-5 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-6:checked ~ .dev-index label.dev-index-item[for="dev-svc-6"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-6:checked ~ .dev-panes #dev-pane-6 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-7:checked ~ .dev-index label.dev-index-item[for="dev-svc-7"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-7:checked ~ .dev-panes #dev-pane-7 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-8:checked ~ .dev-index label.dev-index-item[for="dev-svc-8"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-8:checked ~ .dev-panes #dev-pane-8 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-9:checked ~ .dev-index label.dev-index-item[for="dev-svc-9"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-9:checked ~ .dev-panes #dev-pane-9 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-10:checked ~ .dev-index label.dev-index-item[for="dev-svc-10"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-10:checked ~ .dev-panes #dev-pane-10 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-11:checked ~ .dev-index label.dev-index-item[for="dev-svc-11"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-11:checked ~ .dev-panes #dev-pane-11 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-12:checked ~ .dev-index label.dev-index-item[for="dev-svc-12"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-12:checked ~ .dev-panes #dev-pane-12 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-13:checked ~ .dev-index label.dev-index-item[for="dev-svc-13"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-13:checked ~ .dev-panes #dev-pane-13 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-14:checked ~ .dev-index label.dev-index-item[for="dev-svc-14"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-14:checked ~ .dev-panes #dev-pane-14 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-15:checked ~ .dev-index label.dev-index-item[for="dev-svc-15"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-15:checked ~ .dev-panes #dev-pane-15 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-16:checked ~ .dev-index label.dev-index-item[for="dev-svc-16"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-16:checked ~ .dev-panes #dev-pane-16 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-17:checked ~ .dev-index label.dev-index-item[for="dev-svc-17"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-17:checked ~ .dev-panes #dev-pane-17 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-18:checked ~ .dev-index label.dev-index-item[for="dev-svc-18"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-18:checked ~ .dev-panes #dev-pane-18 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

#dev-svc-19:checked ~ .dev-index label.dev-index-item[for="dev-svc-19"] {
    background: rgba(240, 149, 70, 0.14);
    color: var(--color-heading, #111827);
    border-left-color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

#dev-svc-19:checked ~ .dev-panes #dev-pane-19 {
    display: block;
    animation: dev-pane-in 0.22s ease both;
}

/* Someone who has asked for less motion still gets the panel, just
   without the movement. */
@media (prefers-reduced-motion: reduce) {
    .dev-pane { animation: none !important; }
}


/* Depth inside a panel: the lead reads bigger, the detail sits under
   it, and what-is-included is set apart so it can be scanned without
   reading the prose above it. */
.dev-pane-includes {
    margin: 1.5rem 0 0;
    padding: 1.1rem 1.2rem;
    border-radius: 10px;
    background: var(--background-body, #f7f8fa);
    border: 1px solid var(--color-box-background-border, #e5e7eb);
}

.dev-pane-includes h4 {
    margin: 0 0 0.7rem;
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-body, #6b7280);
}

/* What a typical engagement looks like. Quieter than the body — it is
   an aside, not a claim. */
.dev-pane-note {
    margin: 1.2rem 0 0;
    padding-left: 0.9rem;
    border-left: 3px solid var(--color-primary-background, #f09546);
    font-size: 0.88rem;
    color: var(--color-body, #4b5563);
}

/* The summary-mode link out of a service panel to its full section on
   /web-app-development.php. Added 2026-09-03 with $serviceExplorerSummary:
   the home page stopped repeating all eleven service bodies, so each panel
   now ends with the way to the detail instead of carrying a copy of it.

   Styled as a link and not a button on purpose. There are eleven of these
   in one component, and eleven buttons would compete with the one CTA the
   section actually wants pressed. The arrow is in its own span so the
   underline stops at the words. */
.dev-pane-more {
    margin: 1.3rem 0 0;
}

/* ------------------------------------------------------------------
   Compact hosting category tiles — the "Choose your hosting" panel in
   the home page service explorer. See partials/hosting-category-tiles.php.

   Replaces the full pricing cards that used to render there: measured
   at the panel's real 843px they were 389x783, two per row, stacking
   ten categories to 4,047px. This grid does the same job in about 400.
   ------------------------------------------------------------------ */

/* ==================================================================
   SERVICE CARDS on /web-app-development.php (2026-09-03).

   All eleven services as compact cards with the detail folded into a
   <details>. This was briefly tiered — four full-width rows with art
   above seven cards — and Wayne's read was that the four "are very big
   and don't look good. The bottom ones do." The four-row rules are
   deleted rather than left switched off: unreachable CSS reads as a
   feature somebody turned off, and the next person spends time working
   out how to turn it back on.
   ================================================================== */

.dev-more {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    margin: 0 0 3rem;
}

/* Eleven is odd, so the last card would otherwise sit alone in a
   half-width column looking like a mistake. Spanning it is the same
   even-fit problem the hosting tiles had, solved the other way: there
   the count divided, here it does not. :nth-child(odd) on :last-child
   means this corrects itself if a twelfth service is ever added. */
.dev-more-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
}

.dev-more-card {
    display: flex;
    flex-direction: column;
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-box-background-border, #d7dbe0);
    border-radius: 12px;
    padding: 1.4rem 1.5rem 1.2rem;
    /* Same offset the flagship sections use, so an anchor landing here
       clears the sticky header identically. */
    scroll-margin-top: 90px;
}

.dev-more-head {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
}

/* h2 AND h3, because the heading level is a document-outline decision
   and the size is a design one — they must not be coupled. The cards
   moved from h3 to h2 when all eleven became cards, and this rule still
   said h3, so every heading silently fell back to the theme's default
   h2 and rendered oversized. Nothing errors when a selector stops
   matching; it just looks wrong. */
.dev-more-head h2,
.dev-more-head h3 {
    margin: 0;
    font-size: 1.12rem;
    line-height: 1.25;
    color: var(--color-heading-text, #243141);
}

.dev-more-icon { font-size: 1.05rem; }

/* The claim and timeline as an inline pair, not the white-on-white box
   the flagship strip uses — inside a card that is already white, a
   white strip on a hairline is invisible, which is what the audit
   found. */
.dev-more-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0.5rem 0 0;
}

.dev-more-lead {
    margin: 0.8rem 0 0;
    font-size: 0.95rem;
    line-height: 1.7;
    color: var(--color-body, #4b5563);
}

.dev-more-detail {
    margin-top: 0.9rem;
    border-top: 1px solid var(--color-box-background-border, #e6e8ec);
    padding-top: 0.7rem;
}

.dev-more-detail > summary {
    cursor: pointer;
    list-style: none;
    font-size: 0.86rem;
    /* 700 rather than 600, and the reason is contrast rather than
       emphasis. Wayne asked for the brand orange here; #e07b2a measures
       2.98:1 on white, where 0.86rem text wants 4.5:1. The weight does
       not fix that — nothing at this size can while staying this
       orange — but a heavier stroke lays down more ink per glyph and is
       the one lever available without changing the colour. Flagged
       rather than silently corrected: it is a brand decision. */
    font-weight: 700;
    color: #e07b2a;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.dev-more-detail > summary::-webkit-details-marker { display: none; }

/* A caret drawn in CSS rather than an icon font: this element exists
   in seven cards and needs no network request to say "expandable". */
.dev-more-detail > summary::after {
    content: "";
    width: 0.42rem;
    height: 0.42rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform 0.15s ease;
}

.dev-more-detail[open] > summary::after {
    transform: rotate(-135deg) translate(-2px, -2px);
}

.dev-more-detail > summary:hover,
.dev-more-detail > summary:focus-visible {
    color: var(--color-heading-text, #243141);
}

.dev-more-detail p {
    margin: 0.85rem 0 0;
    font-size: 0.9rem;
    line-height: 1.72;
    color: var(--color-body, #4b5563);
}

.dev-more-cta { margin: auto 0 0; padding-top: 1rem; }

/* .dev-more-card in front is load-bearing, not decoration.
   `body.template-nebulous a { color: inherit }` is (0,1,2) and a bare
   `.dev-more-cta a` is (0,1,1), so the theme won and this link rendered
   grey while "The detail" beside it — a <summary>, which that rule
   cannot match — came out orange. Two links in one card, two colours,
   from a rule nobody wrote here. Adding the ancestor class makes this
   (0,2,1): two classes beat one, and it stays overridable, which
   !important would not. */
.dev-more-card .dev-more-cta a {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.88rem;
    /* Same orange as "The detail" above. Two links in one card reading
       as two different colours is worse than either colour on its own. */
    font-weight: 700;
    color: #e07b2a;
    text-decoration: none;
    border-bottom: 1px solid currentColor;
    padding-bottom: 1px;
}

.dev-more-card .dev-more-cta a:hover,
.dev-more-card .dev-more-cta a:focus-visible { color: var(--color-heading-text, #243141); }

.dev-more-card .dev-more-cta a span { border-bottom: 0; transition: transform 0.15s ease; }
.dev-more-card .dev-more-cta a:hover span { transform: translateX(3px); }

@media (prefers-reduced-motion: reduce) {
    .dev-more-detail > summary::after,
    .dev-more-card .dev-more-cta a span { transition: none; }
    .dev-more-card .dev-more-cta a:hover span { transform: none; }
}

@media (max-width: 860px) {
    .dev-more { grid-template-columns: minmax(0, 1fr); }
}

/* ==================================================================
   THE BOARD — the live component in the /web-app-development.php banner.
   See partials/hero/board.php.

   LIGHT, IN THE SITE'S OWN PALETTE. It was built dark first, as an
   instrument panel, and that was wrong: it sits beside a cream headline
   card inside a light banner, and a black box there reads as a foreign
   object pasted onto the page rather than part of it. Wayne: "Needs to
   be light colour... match the site colours." Slate #243141 ink, orange
   #f09546 accents, cream #fdf3ea, and the same green #177245 the tick
   marks use.

   It replaces an <img> in an existing slot and Wayne's constraint was
   "exact same size and position", so it takes the SAME geometry the
   image had — absolutely filling .category-hero-banner-screenshot--bare,
   capped by the same band-height expression. Verified: 450x249 at the
   identical offset, banner height unchanged at 391px.
   ================================================================== */
/* ==================================================================
   THE BOARD MUST NOT BE SIZED BY THE PARAGRAPH BESIDE IT.

   Wayne 2026-09-03, from production: the X-ray board was clipped, with
   the domain field and the X-RAY IT button cut off along the bottom
   edge. It rendered correctly on this machine, which is the whole
   point of the bug.

   The board is position:absolute; inset:0 inside the screenshot slot,
   so it can never size its own parent - it is given whatever height
   the slot happens to be. The slot is a flex item, so its height is
   decided by the TEXT COLUMN beside it. Measured on both:

       hero lead 552 chars (local)       slot 249px   board fits
       hero lead 245 chars (production)  slot 178px   board clipped

   The board's content is fixed at 212px - a 26px head, four 38px rows
   with 2.24px gaps, and a 29px input bar - plus 10.24px of padding.
   Anything under about 222px cuts something off, and what gets cut is
   the bottom: the input and the button, the only two things on the
   board a visitor can use.

   Nothing was wrong with the CSS on production. The deployed file was
   byte-identical, checked twice after a cached copy briefly suggested
   otherwise. The lead copy is simply shorter there, and the board had
   been sized by looking at it here. A layout that depends on the
   length of an unrelated sentence is not a layout, and an admin
   editing that paragraph in Site Whiz would have broken this again
   with no way of knowing why.

   So the slot gets a floor. The board still fills it and still centres
   within it when there is more room; it can no longer be squeezed
   below its own contents. One custom property rather than a number
   buried in a rule, because it must be re-measured if a fifth row is
   ever added.
   ================================================================== */
.category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare {
    /* 212px of content + 10.24px padding, rounded up for font variance
       between platforms.
     *
     * DECLARED HERE, APPLIED FURTHER DOWN. An identical selector inside
     * the (min-width: 901px) block near the end of this file already
     * sets min-height on this element, and at equal specificity the
     * later rule wins on source order. A min-height here was simply
     * overwritten — measured, not assumed: the computed value stayed
     * 100% with this rule in place. The floor is applied where that
     * rule lives so there is one owner of the property rather than two
     * fighting over it. */
    --flapboard-content-height: 232px;
}

.category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare .flapboard {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* The same cap the <img> uses. Without it this overflowed upward
       into the header once already, which the rule above that records.
       The floor added above deliberately wins over the 100% term: a cap
       that can shrink below the content it is capping is exactly how
       the board came to be clipped on production. */
    max-height: min(
        max(100%, var(--flapboard-content-height, 232px)),
        calc(var(--page-banner-height, 391px) - 3rem)
    );
    margin-left: auto;
    /* Matches object-position: right center — the art sat on the grid's
       right edge, and so must this. */
    width: 100%;
    max-width: 30rem;
    padding: 0.32rem 0.75rem;
    border: 1px solid #e6d9c6;
    border-radius: 12px;
    background:
        radial-gradient(120% 90% at 100% 0%, rgba(240, 149, 70, 0.1), transparent 62%),
        linear-gradient(180deg, #ffffff 0%, #fffaf4 100%);
    box-shadow:
        0 14px 34px -24px rgba(36, 49, 65, 0.5),
        inset 0 1px 0 rgba(255, 255, 255, 0.9);
    overflow: hidden;
    isolation: isolate;
}

/* A faint ruled ground, so the panel reads as an instrument face rather
   than a plain card. A repeating gradient — no request, and it scales
   with the panel instead of tiling at a fixed size. */
.category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare .flapboard::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 2;
    background: repeating-linear-gradient(
        180deg,
        rgba(36, 49, 65, 0.022) 0 1px,
        transparent 1px 4px
    );
}

/* One pass of light travelling down the panel while it settles. */
.flapboard-sweep {
    position: absolute;
    left: 0;
    right: 0;
    top: -40%;
    height: 40%;
    pointer-events: none;
    z-index: 3;
    background: linear-gradient(180deg, transparent, rgba(240, 149, 70, 0.13), transparent);
    animation: flapboard-sweep 6s ease-in-out infinite;
}

@keyframes flapboard-sweep {
    0%, 64% { transform: translateY(0); opacity: 0; }
    68% { opacity: 1; }
    100% { transform: translateY(360%); opacity: 0; }
}

.flapboard-corner {
    position: absolute;
    width: 13px;
    height: 13px;
    pointer-events: none;
    z-index: 3;
    border: 0 solid rgba(240, 149, 70, 0.55);
}

.flapboard-corner--tl { top: 7px; left: 7px; border-top-width: 1px; border-left-width: 1px; }
.flapboard-corner--br { bottom: 7px; right: 7px; border-bottom-width: 1px; border-right-width: 1px; }

.flapboard-head {
    position: relative;
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.22rem;
}

/* A slow breath, not a blink. Green, matching the tick marks. */
.flapboard-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #177245;
    animation: flapboard-breathe 2.4s ease-in-out infinite;
}

@keyframes flapboard-breathe {
    0%, 100% { box-shadow: 0 0 0 0 rgba(23, 114, 69, 0.5); opacity: 1; }
    50% { box-shadow: 0 0 0 5px rgba(23, 114, 69, 0); opacity: 0.55; }
}

/* ---- the sound toggle ----
   .flapboard prefix is load-bearing: `body.template-nebulous button` is
   (0,1,2), so without it this rendered as the theme's orange gradient
   button — an orange card where an icon should be. Two classes beat one
   class and two elements. */
.flapboard .flapboard-sound {
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    margin-left: auto;
    padding: 0;
    border: 1px solid #ecdfcd;
    border-radius: 5px;
    background: #fff;
    box-shadow: none;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.flapboard .flapboard-sound:hover {
    border-color: #f09546;
    background: #fffaf4;
}

.flapboard .flapboard-sound:focus-visible {
    outline: 2px solid #f09546;
    outline-offset: 2px;
}

.flapboard-sound-icon {
    width: 0.95rem;
    height: 0.95rem;
    /* max-width: none is LOAD-BEARING. The theme's reset carries
       `img, picture, video, canvas, svg { max-width: 100% }`, and inside
       a `place-items: center` grid the item's containing block is sized
       by the item itself — so 100% is circular and resolves to 0. The
       icon measured 0x12: a cross drawn as a bare vertical stroke, with
       every width declaration on it looking perfectly correct.
       max-width: none breaks the loop. */
    max-width: none;
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.flapboard-sound-cone {
    /* Filled, so the speaker body reads as a solid object at 15px
       rather than as an outline that closes up. */
    fill: #8a939c;
    stroke: #8a939c;
}

.flapboard-sound-wave,
.flapboard-sound-slash {
    stroke: #8a939c;
}

/* OFF (the default): slash shown, waves hidden. */
.flapboard-sound-wave { display: none; }
.flapboard-sound-slash { display: inline; }

/* ON: waves shown, slash hidden, and the whole icon takes the live
   green the status dot uses — so the state is a SHAPE first and a
   colour second. */
.flapboard-sound[aria-pressed="true"] .flapboard-sound-wave { display: inline; }
.flapboard-sound[aria-pressed="true"] .flapboard-sound-slash { display: none; }

.flapboard-sound[aria-pressed="true"] .flapboard-sound-cone {
    fill: #177245;
    stroke: #177245;
}

.flapboard-sound[aria-pressed="true"] .flapboard-sound-wave {
    stroke: #177245;
}

.flapboard .flapboard-sound[aria-pressed="true"],
.flapboard .flapboard-sound[aria-pressed="true"]:hover {
    border-color: #bfe0cc;
    background: #f2faf5;
}

/* The far wave arrives a beat after the near one when switched on — a
   small confirmation that something happened. */
.flapboard-sound[aria-pressed="true"] .flapboard-sound-wave--near {
    animation: flapboard-wave 0.34s ease both;
}

.flapboard-sound[aria-pressed="true"] .flapboard-sound-wave--far {
    animation: flapboard-wave 0.34s ease 0.09s both;
}

@keyframes flapboard-wave {
    from { opacity: 0; transform: translateX(-2px); }
    to { opacity: 1; transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
    .flapboard-sound[aria-pressed="true"] .flapboard-sound-wave--near,
    .flapboard-sound[aria-pressed="true"] .flapboard-sound-wave--far {
        animation: none;
        opacity: 1;
        transform: none;
    }
}

.flapboard-title {
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 0.63rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    /* #a85a0a, not the brand orange: 5.08:1 on this white against
       2.31:1, and this is small uppercase text. */
    color: #a85a0a;
}

.flapboard-rows {
    position: relative;
    z-index: 4;
    display: flex;
    flex-direction: column;
    gap: 0.14rem;
    margin: 0;
}

.flapboard-row {
    position: relative;
    display: grid;
    grid-template-columns: 9px minmax(0, 1fr) auto;
    grid-template-areas:
        "led label value"
        "led meter meter";
    align-items: center;
    column-gap: 0.55rem;
    padding: 0.15rem 0.5rem;
    border: 1px solid #f0e4d3;
    border-radius: 6px;
    background: rgba(253, 243, 234, 0.55);
}

/* Staggered so the column of lights ripples rather than pulsing as one. */
.flapboard-led {
    grid-area: led;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #f09546;
    animation: flapboard-led 2.8s ease-in-out infinite;
    animation-delay: calc(var(--flap-row, 0) * 320ms);
}

@keyframes flapboard-led {
    0%, 100% { opacity: 0.3; box-shadow: none; }
    50% { opacity: 1; box-shadow: 0 0 6px rgba(240, 149, 70, 0.9); }
}

.flapboard-label {
    grid-area: label;
    min-width: 0;
    font-size: 0.6rem;
    line-height: 1.25;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #55606c;
}

.flapboard-note {
    display: block;
    margin: 0.02rem 0 0;
    font-size: 0.6rem;
    line-height: 1.25;
    letter-spacing: 0;
    text-transform: none;
    color: #6b7787;
}

.flapboard-value {
    grid-area: value;
    margin: 0;
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 0.92rem;
    font-weight: 700;
    /* Tabular figures so a digit landing cannot shift the row's width
       mid-flip — a split-flap that jitters sideways reads as broken
       rather than mechanical. */
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.01em;
    color: #243141;
    text-align: right;
    white-space: nowrap;
}

/* ---- the meters ----
   RED THROUGH TO GREEN, and the colour carries the same fact as the
   length. The gradient spans the WHOLE track, and the fill shows only
   its left portion — so a bar at 20% is red, one at 90% has reached the
   green. That works because all four meters point the same way: more DNS
   records, more days of certificate left, faster response, more on-page
   checks passed. Fuller is better in every one, so redder is worse in
   every one. A future meter that inverts that needs its own treatment.

   HOW THE GRADIENT STAYS ANCHORED TO THE TRACK. The fill is only as wide
   as its value, so a gradient painted inside it would compress into that
   width and every bar would end green. background-size scales it back up
   to the full track — 100% / the value — so the fill is a window onto a
   gradient that is always the same length.

   NO var() INSIDE THE KEYFRAME. The first version animated `left` to
   var(--flap-fill), and it resolved to zero: custom properties are not
   reliably readable from inside @keyframes, so every bar rendered empty
   while the property was set correctly on the element. Measured, not
   guessed — the inline style said 63% and the computed left said 0px.
   The animation is now a plain scaleX, which needs no variable at all. */
.flapboard-meter {
    grid-area: meter;
    position: relative;
    margin: 0.14rem 0 0;
    height: 3px;
    border-radius: 2px;
    background: #eee2d2;
    overflow: hidden;
}

.flapboard-meter-fill {
    display: block;
    width: var(--flap-fill, 0%);
    height: 100%;
    border-radius: 2px;
    background-image: linear-gradient(90deg, #c0392b 0%, #e8a33d 45%, #cbb43a 62%, #177245 100%);
    /* max() so a 0% bar cannot divide by zero. */
    /* 10000% / value, not 100% * 100 / value — the second form parsed as
       invalid and silently fell back to `auto`, which put the whole
       gradient inside the fill and made every bar end green regardless
       of its value. Caught by reading the computed background-size
       rather than trusting the declaration. */
    background-size: calc(10000% / max(1, var(--flap-pct, 100))) 100%;
    background-repeat: no-repeat;
    /*
     * NO ENTRANCE ANIMATION ON THIS ELEMENT, deliberately, and it took
     * two attempts to get here.
     *
     * It animated transform: scaleX(0 -> 1). With fill-mode `both` the
     * first frame is held whenever the animation is not running, and
     * without it the running animation still overrides the base value —
     * so in a throttled or background tab every bar sat at scaleX(0).
     * Measured twice: width computed 193px, transform
     * matrix(0,0,0,1,0,0), bars invisible while their values were
     * perfectly correct.
     *
     * Anything that animates the SIZE of this element can therefore hide
     * the number it exists to report, and "shows nothing" is the worst
     * possible failure mode for a data bar. The motion the board needs
     * is carried by the character flip, the scan sweep and the pulsing
     * indicators — none of which can misreport anything. The bars simply
     * render at their true width, immediately, always.
     */
}

/* Nothing measured, nothing claimed. */
.flapboard-meter--empty .flapboard-meter-fill {
    width: 0;
    animation: none;
}

.flapboard-note {
    display: block;
    margin: 0.02rem 0 0;
    font-size: 0.55rem;
    line-height: 1.25;
    letter-spacing: 0;
    text-transform: none;
    color: #8a939c;
}

.flapboard-value {
    grid-area: value;
    margin: 0;
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 0.92rem;
    font-weight: 700;
    /* Tabular figures so a digit landing cannot shift the row's width
       mid-flip — a split-flap that jitters sideways reads as broken
       rather than mechanical. */
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.01em;
    color: #243141;
    text-align: right;
    white-space: nowrap;
}

/* ---- the meters ----
   The fill width is a REAL RATIO from PlatformBoard, set as a custom
   property. The animation only travels TO that width, so the resting
   state is always the true number. */
/* ---- the meters ----
   RED THROUGH TO GREEN ACROSS THE TRACK, revealed by how full the bar
   is. The gradient lives on the track, and the "fill" element is really
   a MASK covering everything to the right of the value — so a bar at 20%
   shows only the red end, and one at 90% reaches the green. The colour
   therefore means something rather than decorating: it is the same fact
   as the length, read a second way.

   That works because all four meters point the same direction — more
   DNS records, more days of certificate left, faster response, more
   on-page checks passed. Fuller is better in every one, so redder is
   worse in every one. If a future meter ever inverts that, it needs its
   own treatment rather than this one. */
.flapboard-meter {
    grid-area: meter;
    position: relative;
    margin: 0.14rem 0 0;
    height: 3px;
    border-radius: 2px;
    background: linear-gradient(90deg, #c0392b 0%, #e8a33d 45%, #d8b43a 62%, #177245 100%);
    overflow: hidden;
}

/* ---- the domain input ---- */
.flapboard-xray {
    position: relative;
    z-index: 4;
    display: flex;
    gap: 0.4rem;
    margin-top: 0.32rem;
}

.flapboard-xray input {
    flex: 1 1 auto;
    min-width: 0;
    height: 1.55rem;
    padding: 0 0.55rem;
    /* Explicit line-height, or the value sits high in the field: the
       theme's own input line-height is inherited from a taller control. */
    line-height: 1.55rem;
    font-size: 0.75rem;
    color: #243141;
    border: 1px solid #e2d6c5;
    border-radius: 6px;
    background: #fff;
}

.flapboard-xray input::placeholder { color: #a9b2bb; }

.flapboard-xray input:focus-visible {
    outline: 2px solid #f09546;
    outline-offset: 1px;
    border-color: #f09546;
}

/* .flapboard prefix again: `body.template-nebulous button` is (0,1,2)
   and beat the bare (0,1,1) selector, which is why display:inline-flex
   never applied and the label sat off-centre. */
.flapboard .flapboard-xray button {
    flex: 0 0 auto;
    /* Grid rather than flex, so the label is centred on both axes with
       no dependence on line-height at all. */
    display: grid;
    place-items: center;
    height: 1.6rem;
    /* Nudged down to sit on the input's optical centre — the input has a
       border and the button does not, so matching their box tops leaves
       the button reading high. Wayne: "move it down 2px". */
    margin-top: 2px;
    padding: 0 0.85rem;
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1;
    color: #fff;
    border: 0;
    border-radius: 6px;
    background: linear-gradient(180deg, #f5a45f 0%, #e07b2a 100%);
    /* A lit top edge and a soft drop: it reads as a raised key rather
       than a coloured rectangle, which is the "more stylish" ask. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.45),
        inset 0 -1px 0 rgba(140, 60, 0, 0.25),
        0 2px 6px -2px rgba(224, 123, 42, 0.65);
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
}

.flapboard .flapboard-xray button:hover {
    filter: brightness(1.05);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.5),
        inset 0 -1px 0 rgba(140, 60, 0, 0.25),
        0 4px 10px -3px rgba(224, 123, 42, 0.75);
}

/* Presses in. A button that moves under the finger is the cheapest
   quality signal there is. */
.flapboard .flapboard-xray button:active {
    transform: translateY(1px);
    box-shadow:
        inset 0 1px 2px rgba(140, 60, 0, 0.35),
        0 1px 3px -2px rgba(224, 123, 42, 0.6);
}

.flapboard .flapboard-xray button:focus-visible {
    outline: 2px solid #243141;
    outline-offset: 2px;
}

.flapboard .flapboard-xray button[disabled] {
    filter: grayscale(0.4);
    cursor: default;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .flapboard .flapboard-xray button { transition: none; }
    .flapboard .flapboard-xray button:active { transform: none; }
}

/* Readable, which it was not. This was 0.55rem (8.8px) at #a0a8b0 —
   about 2.3:1 — because it kept getting shrunk to win back vertical
   space in a fixed box. That is the wrong thing to trade: the box is a
   constraint on how MUCH content fits, not a licence to make the content
   illegible. #55606c is 6.5:1 here and 0.66rem is a real size; the room
   came from the rows instead. */
/* .flapboard prefix is load-bearing, not tidiness:
   `.category-hero-banner-content p` is (0,1,1) and a bare
   `.flapboard-foot` is (0,1,0), so the banner's own light paragraph
   colour won and this text rendered at about 2.3:1. Two classes beat
   one. */
.flapboard .flapboard-foot {
    position: relative;
    z-index: 4;
    margin: 0.18rem 0 0;
    font-size: 0.66rem;
    line-height: 1.4;
    color: #55606c;
}

.flapboard .flapboard-foot a {
    color: #a85a0a;
    font-weight: 600;
}

.flapboard-error { color: #a32b2b; }

/* ---- the flip ----
   Each character is a span the script splits out, landing staggered by
   column and by row so the board settles in a cascade. The transform is
   on the child span, never the row, so a mid-flight reflow cannot leave
   a row rotated. */
.flapboard-value .flap {
    display: inline-block;
    transform-origin: 50% 0;
    animation: flapboard-land 0.26s cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
    animation-delay: calc((var(--flap-row, 0) * 90ms) + (var(--flap-col, 0) * 26ms));
}

@keyframes flapboard-land {
    0% { transform: rotateX(-92deg); opacity: 0; }
    60% { opacity: 1; }
    100% { transform: rotateX(0); opacity: 1; }
}

/* Non-negotiable for an animated hero, and specifically the detail a
   technical buyer checks. Everything rests in its true state: the meters
   keep their real widths, the values keep their real text, nothing
   moves. */
@media (prefers-reduced-motion: reduce) {
    .flapboard-value .flap,
    .flapboard-dot,
    .flapboard-led,
    .flapboard-meter-fill {
        animation: none;
        transform: none;
        opacity: 1;
    }

    .flapboard-sweep { display: none; }
}

/* Below the split the banner stacks. The board keeps its content but
   loses the fixed fill so it sizes to what is in it. */
@media (max-width: 900px) {
    .category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare .flapboard {
        position: static;
        max-width: none;
        max-height: none;
    }

    .flapboard-foot,
    .flapboard-sweep { display: none; }
}

/* ==================================================================
   THE X-RAY BRIEFING — the dialog behind the board's "Full briefing"
   link, and the same markup rendered inline with JavaScript off.
   See partials/hero/xray-briefing.php.

   THIS IS THE ARTEFACT A PROSPECT SCREENSHOTS, which is the whole
   reason the page has a live tool on it — so it is designed rather than
   merely laid out. It also has to hold a real technical report in one
   screen: measured at 1,339px stacked against an 890px viewport, now a
   two-column grid that fits without a scrollbar on a laptop.

   ONE SET OF RULES FOR BOTH DESTINATIONS. .xray-brief is styled on its
   own and the dialog only supplies the frame, so the no-JS inline render
   and the modal cannot drift into looking like two different features.
   ================================================================== */
.xray-dialog {
    width: min(60rem, calc(100vw - 2.5rem));
    max-height: calc(100vh - 3rem);
    padding: 0;
    border: 0;
    border-radius: 16px;
    background: #fff;
    color: #4b5563;
    /* Two shadows: a tight contact shadow so the panel sits ON the page,
       and a wide soft one so it lifts off it. One shadow reads as a
       sticker. */
    box-shadow:
        0 2px 8px rgba(16, 24, 40, 0.16),
        0 32px 70px -20px rgba(16, 24, 40, 0.42);
    overflow: hidden;
}

.xray-dialog[open] {
    /* A dialog is not centred by default; its margin is. */
    margin: auto;
    animation: xray-dialog-in 0.24s cubic-bezier(0.2, 0.9, 0.3, 1.05);
}

@keyframes xray-dialog-in {
    from { opacity: 0; transform: translateY(10px) scale(0.985); }
    to { opacity: 1; transform: none; }
}

.xray-dialog::backdrop {
    background: rgba(18, 24, 32, 0.6);
    backdrop-filter: blur(2px);
}

@media (prefers-reduced-motion: reduce) {
    .xray-dialog[open] { animation: none; }
}

.xray-dialog-body {
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    padding: 0;
}

/* .xray-dialog prefix is load-bearing: `body.template-nebulous button`
   is (0,1,2) and beat the bare (0,1,0) selector, so this rendered as the
   theme's orange gradient button — a loud blob on a dark masthead. Fifth
   time today that same theme rule has won against a single-class
   selector.

   A bare orange cross, no chrome. The masthead is dark and quiet; a
   filled button there competes with the domain name, which is the thing
   the eye should land on. The hit area stays a full 32px square even
   though nothing is drawn around it — a target you can hit is not the
   same as a target you can see, and shrinking the first to match the
   second is how close buttons become annoying on a phone. */
.xray-dialog .xray-dialog-close {
    position: absolute;
    top: 0.7rem;
    right: 0.8rem;
    z-index: 3;
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    color: #f5a45f;
    border: 0;
    border-radius: 6px;
    background: none;
    box-shadow: none;
    cursor: pointer;
    transition: color 0.12s ease, transform 0.12s ease;
}

.xray-dialog .xray-dialog-close:hover {
    color: #fff;
    background: none;
    /* A small grow rather than a background — it confirms the hover
       without putting a box back. */
    transform: scale(1.12);
}

.xray-dialog .xray-dialog-close:focus-visible {
    outline: 2px solid #f5a45f;
    outline-offset: 2px;
}

.xray-dialog .xray-dialog-close svg {
    /* The attributes carry the size; these keep it if the rem scale
       changes. Both are needed — see the note in hero-board.js.

       max-width: none is LOAD-BEARING. The theme's reset carries
       `img, picture, video, canvas, svg { max-width: 100% }`, and inside
       a `place-items: center` grid the item's containing block is sized
       by the item itself — so 100% is circular and resolves to 0. The
       icon measured 0x12: a cross drawn as a bare vertical stroke, with
       every width declaration on it looking perfectly correct.
       max-width: none breaks the loop. */
    width: 14px;
    height: 14px;
    max-width: none;
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
}

@media (prefers-reduced-motion: reduce) {
    .xray-dialog .xray-dialog-close { transition: none; }
    .xray-dialog .xray-dialog-close:hover { transform: none; }
}

/* The inline copy exists so the dialog can be reopened without asking
   the server twice. Hidden only when a script is there to show it — the
   no-JS path renders it visible. */
.xray-brief-inline[hidden] { display: none; }

.xray-brief-inline:not([hidden]) {
    max-width: var(--container-maxwidth, 1250px);
    width: var(--container-width, 90%);
    margin: 0 auto 2.5rem;
    border: 1px solid #e2e5ea;
    border-radius: 14px;
    overflow: hidden;
}

/* ---- the header band ----
   Dark, so the report opens on something with weight and the domain is
   unmistakably the subject. It is the only dark surface in the dialog,
   which is what makes it read as a masthead rather than a theme. */
.xray-brief-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1.5rem;
    flex-wrap: wrap;
    padding: 1.3rem 1.6rem 1.2rem;
    background:
        radial-gradient(120% 140% at 100% 0%, rgba(240, 149, 70, 0.22), transparent 62%),
        linear-gradient(160deg, #243141 0%, #1a2532 100%);
    color: #cfd8e3;
}

.xray-brief-eyebrow {
    margin: 0 0 0.15rem;
    font-size: 0.63rem;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: #f5a45f;
}

/* Two classes, because `body.template-nebulous h2` is (0,1,2) and a
   bare `.xray-brief h2` is (0,1,1) — so the theme's dark heading colour
   won and the domain rendered near-invisible on the dark masthead. The
   most prominent element in the dialog, lost to a selector nobody wrote
   for it. Fourth time this pattern has bitten today; the theme's
   `body.template-nebulous X` rules beat any single-class selector. */
.xray-brief .xray-brief-head h2 {
    margin: 0;
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 1.5rem;
    line-height: 1.15;
    color: #fff;
    /* A domain is not a sentence. */
    word-break: break-word;
}

.xray-brief-sub {
    margin: 0;
    max-width: 38ch;
    font-size: 0.75rem;
    line-height: 1.55;
    color: #9fadbd;
}

/* ---- the four headline checks ---- */
.xray-brief-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1px;
    /* The gap IS the rule: one background behind four cards, showing
       through as hairlines. No borders to keep in step. */
    background: #e6e9ee;
    border-bottom: 1px solid #e6e9ee;
}

.xray-brief-card {
    position: relative;
    padding: 0.85rem 0.95rem 0.8rem;
    background: #fff;
}

/* Status as a top rule, and the value text already says "Expired" or
   "54 days left" — so colour is never the only carrier. */
.xray-brief-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: #c9ced6;
}

.xray-brief-card.xray-status-ok::before { background: #177245; }
.xray-brief-card.xray-status-warn::before { background: #c98a10; }
.xray-brief-card.xray-status-fail::before { background: #a32b2b; }

.xray-brief-label {
    margin: 0 0 0.2rem;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #8a939c;
}

.xray-brief-value {
    margin: 0 0 0.15rem;
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.2;
    color: #243141;
    word-break: break-word;
}

.xray-brief-note {
    margin: 0;
    font-size: 0.68rem;
    line-height: 1.4;
    color: #7a8593;
}

/* ---- the two columns ---- */
.xray-brief-cols {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0;
    padding: 0;
}

.xray-brief-col {
    padding: 1.1rem 1.6rem 1.2rem;
}

/* A single hairline between the columns rather than a border on each. */
.xray-brief-col + .xray-brief-col {
    border-left: 1px solid #eceff3;
}

.xray-brief-section + .xray-brief-section {
    margin-top: 1.1rem;
    padding-top: 1rem;
    border-top: 1px solid #f1f3f6;
}

/* Same reason as the h2 above — this one happened to want a colour
   close to the theme's, so it looked fine while being just as fragile. */
.xray-brief .xray-brief-section h3 {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    margin: 0 0 0.55rem;
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: #243141;
}

.xray-brief-count {
    font-family: "Open Sans", sans-serif;
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: #8a939c;
}

/* ---- definition rows ---- */
.xray-brief-list {
    margin: 0;
    display: grid;
    gap: 0.3rem;
}

.xray-brief-list > div {
    display: grid;
    grid-template-columns: minmax(0, 6.5rem) minmax(0, 1fr);
    gap: 0.8rem;
    align-items: baseline;
}

.xray-brief-list dt {
    font-size: 0.7rem;
    font-weight: 600;
    color: #8a939c;
}

.xray-brief-list dd {
    margin: 0;
    font-size: 0.775rem;
    line-height: 1.45;
    color: #3f4854;
    word-break: break-word;
}

.xray-brief-dim { color: #a6aeb8; }
.xray-brief-bad { color: #a32b2b; font-weight: 600; }

/* ---- security headers ----
   Chips, not rows. Six of them read at a glance as a set, and the set
   is the finding: how many are on. */
.xray-brief-headers {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.xray-brief-headers li {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.22rem 0.5rem;
    font-family: Consolas, "Courier New", monospace;
    font-size: 0.66rem;
    border-radius: 5px;
    cursor: default;
}

/* A tick or a dash, so the state is never colour alone. */
.xray-brief-headers li::before { font-weight: 700; }

.xray-brief-headers li.is-on {
    color: #12603a;
    background: #eaf5ee;
    border: 1px solid #cbe4d6;
}

.xray-brief-headers li.is-on::before { content: "\2713"; }

.xray-brief-headers li.is-off {
    color: #8a939c;
    background: #f6f7f9;
    border: 1px solid #e6e9ee;
}

.xray-brief-headers li.is-off::before { content: "\2013"; }

.xray-brief-hint {
    margin: 0.45rem 0 0;
    font-size: 0.68rem;
    line-height: 1.45;
    color: #8a939c;
}

/* ---- footer ---- */
.xray-brief-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.2rem;
    flex-wrap: wrap;
    padding: 0.95rem 1.6rem;
    background: #fbfcfd;
    border-top: 1px solid #eceff3;
}

.xray-brief-foot p {
    margin: 0;
    max-width: 42ch;
    font-size: 0.74rem;
    line-height: 1.5;
    color: #7a8593;
}

.xray-brief-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* Print and Download are secondary to the CTA beside them, so they are
   quiet outline buttons rather than three things competing to be
   pressed. Same height as .button1 so the row sits on one baseline. */
.xray-brief .xray-brief-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.42rem 0.75rem;
    font-family: "Open Sans", "Segoe UI", sans-serif;
    font-size: 0.76rem;
    font-weight: 600;
    line-height: 1;
    color: #55606c;
    text-decoration: none;
    border: 1px solid #dde1e7;
    border-radius: 7px;
    background: #fff;
    cursor: pointer;
    transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}

.xray-brief .xray-brief-btn:hover,
.xray-brief .xray-brief-btn:focus-visible {
    color: #243141;
    border-color: #f09546;
    background: #fffaf4;
}

/* ---- print ----
   Everything except the open dialog is hidden, so Print (and the
   browser's own Save-as-PDF, which lives in the same dialog) produces
   the briefing rather than the marketing page it was sitting on.

   The dialog is un-positioned and un-scrolled for paper: a modal fixed
   to the viewport prints as one clipped screenful, which is the usual
   way this goes wrong. */
@media print {
    /* SCOPED TO THE DIALOG BEING OPEN, and it was not.
       This existed so the xray dialog could print on its own, but it
       applied on EVERY page and every print — hiding every direct child
       of body unconditionally. A print of anything else came out as a
       blank sheet, because display:none removes the whole subtree and
       no amount of `visibility: visible` further down can bring it
       back. That is exactly what happened to the calling card: the card
       was made visible, its ancestor main.site-main was display:none,
       and the page printed empty.
       :has() keeps the dialog's own print behaviour intact and lets
       every other print work again. */
    body:has(#xray-dialog[open]) > *:not(#xray-dialog) { display: none !important; }

    #xray-dialog,
    #xray-dialog[open] {
        position: static;
        display: block;
        width: auto;
        max-width: none;
        max-height: none;
        margin: 0;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        animation: none;
        overflow: visible;
    }

    #xray-dialog::backdrop { display: none; }

    .xray-dialog-body {
        max-height: none;
        overflow: visible;
    }

    /* Controls do nothing on paper. */
    .xray-dialog-close,
    .xray-brief-actions {
        display: none !important;
    }

    /* The masthead is a solid dark block; without this most browsers
       drop the background and print white on white. */
    .xray-brief-head {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* Keep a section and its heading together across a page break. */
    .xray-brief-section {
        break-inside: avoid;
    }
}

@media (max-width: 760px) {
    .xray-brief-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .xray-brief-cols { grid-template-columns: minmax(0, 1fr); }
    .xray-brief-col + .xray-brief-col { border-left: 0; border-top: 1px solid #eceff3; }
    .xray-brief-head { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
    .xray-brief-foot { flex-direction: column; align-items: flex-start; }
}

.hosting-tiles {
    display: grid;
    /* Five across, two down. Arithmetic, not taste: ten divides evenly
       as 5x2 or 2x5 and nothing else, so any three- or four-across grid
       ends on a ragged row. */
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.65rem;
}

.hosting-tile {
    display: flex;
    flex-direction: column;
    min-height: 11.6rem;
    padding: 0.75rem 0.75rem 0.65rem;
    border: 1px solid var(--color-box-background-border, #d7dbe0);
    border-radius: 9px;
    background: var(--color-box-background, #fff);
    text-decoration: none;
    /* The whole tile is the link, so the text must not inherit link
       colour — the name and features are content, not anchors. */
    color: inherit;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.hosting-tile:hover,
.hosting-tile:focus-visible {
    border-color: #f09546;
    background: #fdf3ea;
    text-decoration: none;
}

.hosting-tile-name {
    font-family: "Roboto Condensed", "Segoe UI", sans-serif;
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.2;
    color: var(--color-heading-text, #243141);
}

.hosting-tile-desc {
    margin-top: 0.25rem;
    font-size: 0.66rem;
    line-height: 1.45;
    color: #6b7480;
    /* Clamp rather than truncate in PHP: three lines of description
       either fit or wrap, and only a genuinely long one gets cut — by
       the browser, at the exact width, instead of by a character count
       that cannot know how wide a word renders. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Flows directly under the description rather than being pinned to the
   bottom of the tile.

   It WAS pinned, to line the feature lists up across the row. Measured,
   that cost 7 to 54px of white in the middle of each tile depending on
   whether its description ran to one line or three — and an uneven hole
   mid-card reads as missing content rather than as alignment. Sitting
   the list under the text it belongs to is worth more than ten lists
   agreeing on a baseline. */
.hosting-tile-features {
    list-style: none;
    margin: 0.45rem 0 0;
    padding: 0;
}

.hosting-tile-features li {
    position: relative;
    padding-left: 0.62rem;
    font-size: 0.66rem;
    line-height: 1.5;
    color: var(--color-body, #4b5563);
    /* Two lines, so a longer feature wraps instead of being cut with an
       ellipsis. An ellipsis on most of forty items looked like the data
       was missing, which is how it was reported. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hosting-tile-features li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.5em;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    /* Green, matching the ticks in the service panels above rather than
       introducing a third marker colour on one page. */
    background: #177245;
}

/* Ten tiles at five across need a real narrow-screen plan, or each one
   becomes a 60px column of broken words. Three, then two — never five. */
@media (max-width: 1100px) {
    .hosting-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .hosting-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; }
    .hosting-tile { min-height: 0; }
}

/* ------------------------------------------------------------------
   "Software We Have Actually Shipped" — the proof band at the top of
   /web-app-development.php. See partials/dev-built-band.php.

   Three cards, because the argument is three systems in three
   different shapes rather than one big one. Deliberately NOT the cream
   plate treatment the banner uses: this sits directly under the banner,
   and repeating its ground would make the two read as one long header.
   White cards on the page ground, with the orange kept for the figures
   and the link, so the eye lands on the checkable parts.
   ------------------------------------------------------------------ */
.dev-built {
    display: grid;
    /* Three across on a wide screen, and at 20rem the cards break to two
       and then one on their own rather than at a guessed breakpoint. */
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 1.1rem;
    margin: 0 0 3rem;
}

.dev-built-card {
    display: flex;
    flex-direction: column;
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-box-background-border, #d7dbe0);
    border-radius: 12px;
    padding: 1.5rem 1.6rem 1.4rem;
}

.dev-built-name {
    margin: 0 0 0.15rem;
    font-size: 1.12rem;
    line-height: 1.3;
    color: var(--color-heading-text, #243141);
    /* The theme centres h3 site-wide, so this heading sat centred above
       a left-aligned card and looked like a mistake. Found by reading
       the computed style, not by looking — at a glance it just seemed
       slightly off. */
    text-align: left;
}

/* Where the thing actually is — "You are using it right now",
   "quantumedgeai.net", "Private project". Small and quiet: it is a
   locator, not a claim. */
.dev-built-where {
    margin: 0 0 0.85rem;
    font-size: 0.78rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    font-weight: 600;
    color: #a85a0a;
}

.dev-built-body {
    margin: 0 0 1.05rem;
    font-size: 0.9rem;
    line-height: 1.65;
    color: var(--color-body, #4b5563);
}

/* The checkable facts, pinned to the bottom of the card by
   margin-top:auto so an unequal paragraph above does not leave a card
   with its figures floating in the middle.

   It does NOT make the three lists start at the same height, which an
   earlier version of this comment claimed — measured, they begin within
   about 25px of each other, because each list has its own height and
   only the block's END is anchored. Worth stating accurately: the next
   person to see the small stagger should know it is the design and not
   a bug to chase. */
.dev-built-facts {
    margin: auto 0 0;
    padding: 0.9rem 0 0;
    border-top: 1px solid var(--color-box-background-border, #e6e8ec);
    list-style: none;
}

.dev-built-facts li {
    position: relative;
    padding-left: 1.05rem;
    margin-bottom: 0.42rem;
    font-size: 0.855rem;
    line-height: 1.5;
    color: var(--color-heading-text, #243141);
}

.dev-built-facts li:last-child {
    margin-bottom: 0;
}

/* A small square rather than a tick. These are statements of fact, and
   a row of ticks would read as a feature list being sold. */
.dev-built-facts li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 6px;
    height: 6px;
    border-radius: 1px;
    background: #f09546;
}

.dev-built-link {
    margin: 1.05rem 0 0;
}

.dev-built-link a {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
    /* 5.08:1 on white, 4.64:1 on the cream card — measured, not picked.
       The storefront orange is 3.7:1 and fails AA at this size. */
    color: #a85a0a;
    text-decoration: none;
    border-bottom: 1px solid currentColor;
    padding-bottom: 1px;
}

.dev-built-link a:hover,
.dev-built-link a:focus-visible {
    color: var(--color-heading-text, #243141);
}

.dev-built-link a span {
    border-bottom: 0;
    transition: transform 0.15s ease;
}

.dev-built-link a:hover span {
    transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
    .dev-built-link a span { transition: none; }
    .dev-built-link a:hover span { transform: none; }
}

@media (max-width: 700px) {
    .dev-built { gap: 0.9rem; margin-bottom: 2.2rem; }
    .dev-built-card { padding: 1.25rem 1.25rem 1.15rem; }
}

.dev-pane-more a {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.92rem;
    font-weight: 600;
    /* --color-link does not exist in this theme; --color-primary-background
       is the token the rest of the .dev-* components use for the orange, and
       it is darkened here for text contrast on white. Checked rather than
       assumed: a var() with no fallback and no definition kills the whole
       declaration rather than inheriting.

       #a85a0a and not the storefront orange: measured, not eyeballed.
       #f09546 and #b8650f both fail AA here (3.7:1 and 4.3:1). This is
       5.08:1 on white and 4.64:1 on the cream card, so it passes on
       whichever ground --color-box-background resolves to. */
    color: #a85a0a;
    text-decoration: none;
    border-bottom: 1px solid currentColor;
    padding-bottom: 1px;
}

.dev-pane-more a:hover,
.dev-pane-more a:focus-visible {
    color: var(--color-heading-text, #243141);
}

.dev-pane-more a span {
    border-bottom: 0;
    transition: transform 0.15s ease;
}

.dev-pane-more a:hover span {
    transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
    .dev-pane-more a span { transition: none; }
    .dev-pane-more a:hover span { transform: none; }
}

/* Anchor targets clear the sticky header.
   Without this, scrolling to #quote-form puts its first line directly
   under the fixed menu — the browser scrolls correctly and the visitor
   sees the second question first. */
#quote-form,
#contact-form,
#services,
#quote,
#contact {
    scroll-margin-top: 6rem;
}

/* ---- the project quote form ---- */

.qr-page { max-width: 56rem; margin: 2.5rem auto 4rem; padding: 0 1.25rem; }

.qr-head { margin: 0 0 2rem; }
.qr-head h2 { margin: 0 0 .6rem; color: var(--color-heading, #111827); line-height: 1.15; text-align: left; font-size: 1.5rem; }
.qr-head p { margin: 0; color: var(--color-body, #4b5563); font-size: 1.02rem; max-width: 58ch; }

.qr-errors {
    margin: 0 0 1.6rem;
    padding: 1rem 1.2rem;
    border: 1px solid #dc2626;
    border-left-width: 3px;
    border-radius: 0 8px 8px 0;
    background: rgba(220, 38, 38, .06);
    color: #b91c1c;
}
.qr-errors ul { margin: .5rem 0 0; padding-left: 1.1rem; }
.qr-errors li { margin-bottom: .25rem; }

/* ---- steps ---- */

.qr-step {
    margin: 0 0 1.1rem;
    padding: 1.5rem 1.6rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 10px;
    background: var(--color-box-background, #fff);
}

.qr-step h2 {
    display: flex;
    align-items: center;
    gap: .7rem;
    margin: 0 0 .4rem;
    font-size: 1.15rem;
    color: var(--color-heading, #111827);
    text-align: left;
}

/* The number is a real sequence — four sections in the order they are
   filled in — so it earns being rendered as one. */
.qr-step-n {
    display: grid;
    place-items: center;
    width: 1.8rem;
    height: 1.8rem;
    flex: none;
    border-radius: 50%;
    background: var(--color-primary-background, #f09546);
    color: var(--color-primary-foreground, #fff);
    font-size: .85rem;
    font-weight: 700;
}

.qr-step-hint { margin: 0 0 1.1rem; color: var(--color-body, #6b7280); font-size: .88rem; }

/* ---- what do you need ---- */

.qr-services {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: .5rem;
}

/* Same (0,1,2) label rule, same fix — a tile whose checkbox and text
   fell out of flex would wrap exactly like the rail did. */
.qr-services label.qr-service {
    display: flex;
    margin-bottom: 0;
    align-items: center;
    gap: .6rem;
    padding: .65rem .8rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 8px;
    cursor: pointer;
    font-size: .9rem;
    color: var(--color-body, #4b5563);
    transition: border-color .14s ease, background .14s ease;
}

.qr-services label.qr-service:hover { border-color: var(--color-primary-background, #f09546); }

/* :has() so the whole tile reacts, not just the box. Where it is
   unsupported the checkbox still works perfectly — this is the
   decoration, not the mechanism. */
.qr-services label.qr-service:has(input:checked) {
    border-color: var(--color-primary-background, #f09546);
    background: rgba(240, 149, 70, .07);
    color: var(--color-heading, #111827);
    font-weight: 600;
}

.qr-service-icon { font-size: 1.05rem; line-height: 1; }

/* ---- fields ---- */

.qr-field { display: block; margin: 0 0 1.1rem; }
.qr-field-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
    gap: 0 1.1rem;
}

.qr-label {
    display: block;
    margin: 0 0 .2rem;
    font-weight: 600;
    font-size: .92rem;
    color: var(--color-heading, #111827);
}

.qr-optional {
    font-weight: 400;
    font-size: .78rem;
    color: var(--color-body, #9ca3af);
    /* Marking what is optional beats marking what is required: a form
       of nine fields with six asterisks reads as nine obligations. */
}

.qr-hint {
    display: block;
    margin: 0 0 .4rem;
    font-size: .82rem;
    line-height: 1.5;
    color: var(--color-body, #6b7280);
}

.qr-field input,
.qr-field select,
.qr-field textarea {
    width: 100%;
    /* Without this a padded input in a grid column overflows it. */
    box-sizing: border-box;
}

.qr-field textarea { min-height: 9rem; resize: vertical; }

/* ---- send ---- */

.qr-step-send { text-align: left; }
.qr-submit { margin-top: 1rem; }
.qr-privacy { margin: .9rem 0 0; font-size: .82rem; color: var(--color-body, #6b7280); }

/* ---- the thank you ---- */

.qr-done {
    max-width: 40rem;
    margin: 1rem auto;
    padding: 2.5rem 2rem;
    text-align: center;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 12px;
    background: var(--color-box-background, #fff);
}

.qr-done-mark {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    margin: 0 auto 1rem;
    border-radius: 50%;
    background: rgba(28, 107, 69, .1);
    color: #1c6b45;
    font-size: 1.4rem;
    font-weight: 700;
}

.qr-done h1 { margin: 0 0 .6rem; color: var(--color-heading, #111827); }
.qr-done p { color: var(--color-body, #4b5563); max-width: 46ch; margin: 0 auto 1rem; }
.qr-done-ref { font-size: 1.02rem; }
.qr-done-cta { margin-top: 1.6rem; }

/* ---- the domain checker inside an explorer panel ---- */

/* THE VIEWPORT IS WIDE; THE CONTAINER IS NOT.
   layout.css already stacks these columns and shrinks the heading —
   but only under `@media (max-width: 1000px)`, which asks about the
   WINDOW. Inside the explorer the window is 1900px and the panel is
   755px, so that breakpoint never fires and the component lays itself
   out as though it had the whole page: a 245px left column with 3em
   text towering down it, and a 421px right column squeezing SEARCH
   until it read "SEAR".

   Container queries would express this properly. Rather than depend on
   them, the same rules are applied by context — which is what the
   media query was standing in for anyway. */

.dev-pane-tool .domainchecker .domainchecker-columns {
    flex-direction: column;
    align-items: stretch;
    padding-top: 0;
}

.dev-pane-tool .domainchecker .domainchecker-columns-left,
.dev-pane-tool .domainchecker .domainchecker-columns-right {
    width: 100%;
    max-width: none;
}

/* The search row first, the sales line under it. Someone who opened a
   panel called "Choose a domain" came to type a name, not to read
   about why a domain matters. */
.dev-pane-tool .domainchecker .domainchecker-columns-right { order: 1; margin-bottom: 1.25rem; }
.dev-pane-tool .domainchecker .domainchecker-columns-left { order: 2; }

/* 3em was sized for a page-width column. At panel width it wrapped
   into a five-line tower; this is the same statement at a size the
   space can hold. */
.dev-pane-tool .domainchecker .domainchecker-heading {
    font-size: 1.5rem;
    line-height: 1.25;
    text-shadow: none;
}

.dev-pane-tool .domainchecker .domainchecker-subheading {
    font-size: .92rem;
    line-height: 1.55;
}

/* The search row wraps rather than crushing its buttons. A truncated
   "SEAR" is worse than a button on its own line. */
.dev-pane-tool .domainchecker form { flex-wrap: wrap; }

.dev-pane-tool .domainchecker form button,
.dev-pane-tool .domainchecker form .button1 {
    white-space: nowrap;
    flex: 0 0 auto;
}

/* The typed name needs room before anything else gets any. */
.dev-pane-tool .domainchecker form input[type="text"],
.dev-pane-tool .domainchecker form input[name="q"] {
    flex: 1 1 12rem;
    min-width: 0;
}

/* The heading the partial emits for itself. Inside a panel that
   already has its own title, a second full-width band heading reading
   "Domain Names" is a title above a title. */
.dev-pane-tool .section-heading { display: none; }

/* ---- quote page: the content around the form ---- */

.qr-assure {
    list-style: none;
    margin: 0 0 1.8rem;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
    gap: .7rem;
}

.qr-assure li {
    display: grid;
    gap: .2rem;
    padding: .9rem 1rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 9px;
    background: var(--color-box-background, #fff);
}

.qr-assure strong { color: var(--color-heading, #111827); font-size: .92rem; }
.qr-assure span { color: var(--color-body, #6b7280); font-size: .84rem; line-height: 1.5; }

/* Space BELOW as well as above. The theme's .section-heading band
   carries a bottom margin and no top one — it assumes whatever
   precedes it has provided the gap. Nothing did here, so the FAQ band
   sat flush against the last process card with a measured 0px between
   them. */
.qr-after { margin: 2.5rem 0 2.75rem; }

.qr-after h2 {
    margin: 0 0 1.1rem;
    font-size: 1.3rem;
    color: var(--color-heading, #111827);
    text-align: left;
}

/* Numbered because it IS a sequence — five stages in the order they
   happen. Counters rather than <ol> markers so the number can be
   styled to match the form's own step badges. */
.qr-process { list-style: none; margin: 0; padding: 0; counter-reset: qrstep; display: grid; gap: .8rem; }

.qr-process li {
    counter-increment: qrstep;
    display: grid;
    grid-template-columns: 2rem minmax(0, 1fr);
    gap: .2rem .9rem;
    padding: .9rem 1rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 9px;
    background: var(--color-box-background, #fff);
}

.qr-process li::before {
    content: counter(qrstep);
    grid-row: 1 / span 2;
    align-self: start;
    display: grid;
    place-items: center;
    width: 1.8rem;
    height: 1.8rem;
    border-radius: 50%;
    background: var(--color-primary-background, #f09546);
    color: var(--color-primary-foreground, #fff);
    font-size: .82rem;
    font-weight: 700;
}

.qr-process strong { color: var(--color-heading, #111827); font-size: .95rem; }
.qr-process span { color: var(--color-body, #4b5563); font-size: .88rem; line-height: 1.55; }



/* ---- the quote form's scope assistant ---- */

.qr-assist {
    margin: 0 0 1.1rem;
    padding: 1rem 1.15rem;
    border: 1px dashed var(--color-box-background-border, #d7dbe0);
    border-radius: 10px;
    background: var(--background-body, #f8f8f8);
}

/* Two classes, because a plain class on a BUTTON loses to the theme's
   own element-level rule at (0,1,2). That trap has bitten three times
   in this file already. */
.qr-assist button.qr-assist-run {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .5rem .9rem;
    border: 1px solid var(--color-primary-background, #f09546);
    border-radius: 8px;
    background: transparent;
    color: var(--color-primary-background, #f09546);
    font: inherit;
    font-size: .88rem;
    font-weight: 700;
    cursor: pointer;
}

.qr-assist button.qr-assist-run:hover:not(:disabled) {
    background: var(--color-primary-background, #f09546);
    color: var(--color-primary-foreground, #fff);
    filter: none;
}

.qr-assist button.qr-assist-run:disabled { opacity: .55; cursor: default; }

.qr-assist-lead {
    margin: .55rem 0 0;
    font-size: .82rem;
    line-height: 1.5;
    color: var(--color-body, #6b7280);
    max-width: 58ch;
}

.qr-assist-note {
    margin: .9rem 0 0;
    font-size: .85rem;
    color: var(--color-body, #6b7280);
}

.qr-assist-list { list-style: none; margin: .8rem 0 0; padding: 0; display: grid; gap: .5rem; }

.qr-assist-list label {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: .6rem;
    align-items: start;
    margin-bottom: 0;
    padding: .6rem .75rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 8px;
    background: var(--color-box-background, #fff);
    cursor: pointer;
    font-size: .87rem;
}

.qr-assist-list strong { display: block; color: var(--color-heading, #111827); }

.qr-assist-why {
    display: block;
    margin-top: .15rem;
    font-size: .82rem;
    line-height: 1.45;
    color: var(--color-body, #6b7280);
}

.qr-assist .qr-assist-add { margin-top: .8rem; }

/* ---- status messages, shown in front of the customer ---- */

/* The <p class="ca-alert"> markup is unchanged and keeps its own
   colours; this styles the dialog ca-alerts.js moves it into. With
   JavaScript off none of this applies and the alert stays a banner at
   the top of the page, which is the point — nothing here hides a
   message waiting for a script to reveal it. */
.ca-alert-dialog {
    /* A dialog is not centred by default; its margin is. */
    margin: auto;
    max-width: min(30rem, calc(100vw - 2.5rem));
    padding: 1.5rem 1.5rem 1.25rem;
    border: 1px solid var(--color-box-background-border, #d7dbe0);
    border-radius: 12px;
    background: var(--color-box-background, #fff);
    color: var(--color-body, #374151);
    box-shadow: 0 18px 45px rgba(0, 0, 0, 0.28);
    animation: ca-alert-pop 0.22s cubic-bezier(0.34, 1.4, 0.64, 1);
}

.ca-alert-dialog::backdrop {
    background: rgba(15, 20, 28, 0.55);
    animation: ca-alert-fade-in 0.22s ease;
}

/* The panel keeps its scale while fading, so it reads as one movement
   rather than shrinking away. */
.ca-alert-dialog.is-closing,
.ca-alert-dialog.is-closing::backdrop {
    animation: ca-alert-fade-out 0.18s ease forwards;
}

/* Inside the dialog the alert is the content, not a strip on a page —
   its own margin would push it off-centre. */
.ca-alert-dialog .ca-alert {
    margin: 0;
}

.ca-alert-dialog-body {
    display: grid;
    gap: 0.7rem;
}

.ca-alert-dialog-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 1.15rem;
}

@keyframes ca-alert-pop {
    from { opacity: 0; transform: scale(0.92); }
    to { opacity: 1; transform: scale(1); }
}

@keyframes ca-alert-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes ca-alert-fade-out {
    from { opacity: 1; }
    to { opacity: 0; }
}

/* Someone who has asked for less motion still gets the dialog, just
   without the movement. */
@media (prefers-reduced-motion: reduce) {
    .ca-alert-dialog,
    .ca-alert-dialog::backdrop {
        animation-duration: 0.01ms;
    }
}

/* ---- deep links into individual cPanel tools ---- */

/* These render as plain text links, not buttons. They ARE buttons —
   each is its own POST form so the one-time cPanel session URL never
   sits in an href — so every scrap of button chrome has to be taken
   off deliberately.

   SPECIFICITY IS THE WHOLE PROBLEM HERE, not the styling. The first
   attempt used `button.ca-tool`, which is (0,1,1), and it lost: the
   buttons rendered as solid orange blobs because something in the
   theme's own cascade outranks that. `body.template-nebulous button`
   alone is (0,1,2) and already beats it. Scoping to the container as
   well puts this at (0,3,2), which is above anything the theme does
   to a bare button, so the rule holds regardless of source order —
   the tie-break that quietly decides these. */
body.template-nebulous .ca-tools-heading {
    margin: 1.5rem 0 0.6rem;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    /* Uppercase sets too tight to scan without extra tracking. */
    letter-spacing: 0.07em;
    color: var(--color-body, #4b5563);
}

body.template-nebulous .ca-tools {
    display: grid;
    /* Two columns where there is room, one on a phone. Row gap is
       tight and column gap wide: with no boxes, whitespace is the only
       thing separating the columns. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
    gap: 0.1rem 2rem;
}

body.template-nebulous .ca-tools form {
    margin: 0;
    display: flex;
}

body.template-nebulous .ca-tools button.ca-tool {
    flex: 1;
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    margin: 0;
    padding: 0.4rem 0;
    /* Everything a button brings with it, removed explicitly. */
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    text-shadow: none;
    font: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    /* Every var() carries a fallback: an undefined custom property
       does not fall back to the previous rule, it invalidates the
       whole declaration. */
    color: var(--color-heading, #1f2937);
    text-align: left;
    cursor: pointer;
}

body.template-nebulous .ca-tools button.ca-tool i {
    /* Fixed width so the labels line up in a column rather than
       stepping in and out with each icon's own width. */
    width: 1.15rem;
    flex: none;
    text-align: center;
    font-size: 0.92rem;
    color: var(--color-primary-background, #f09546);
}

body.template-nebulous .ca-tools button.ca-tool:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
    /* The theme puts a brightness filter on every button hover. On a
       transparent link that just washes the text out. */
    filter: none;
}

/* Removing the button chrome removes the focus ring with it, so it is
   put back rather than left to keyboard users to do without. */
body.template-nebulous .ca-tools button.ca-tool:focus-visible {
    outline: 2px solid var(--color-primary-background, #f09546);
    outline-offset: 2px;
    border-radius: 3px;
}

/* ---- the account's backups ---- */

.ca-backups {
    list-style: none;
    margin: 0 0 1.1rem;
    padding: 0;
    display: grid;
    gap: 0.5rem;
}

.ca-backup {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.75rem 0.95rem;
    border: 1px solid var(--color-border, rgba(128, 128, 128, 0.28));
    border-radius: 9px;
}

.ca-backup-icon {
    flex: none;
    width: 2rem;
    height: 2rem;
    display: grid;
    place-items: center;
    border-radius: 7px;
    background: var(--color-primary-background, #f09546);
    color: var(--color-primary-foreground, #fff);
    font-size: 0.85rem;
}

.ca-backup-main {
    display: grid;
    gap: 0.15rem;
    /* Long archive names must wrap inside the row rather than widening
       it and pushing the size off the edge. */
    min-width: 0;
    flex: 1 1 auto;
}

.ca-backup-main strong {
    color: var(--color-heading, #1f2937);
    font-size: 0.92rem;
}

.ca-backup-file {
    color: var(--color-body, #6b7280);
    font-size: 0.76rem;
    /* An archive name is an identifier — read it as one. */
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    word-break: break-all;
}

.ca-backup-size {
    flex: none;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--color-heading, #1f2937);
    font-variant-numeric: tabular-nums;
}

.ca-backups-action {
    margin: 0;
}

/* ---- spam toggle and back-up-now ---- */

.ca-maint {
    display: grid;
    gap: 0.9rem;
}

.ca-maint-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    /* Wraps rather than crushing the button off the edge on a phone. */
    flex-wrap: wrap;
    padding: 0.9rem 1rem;
    border: 1px solid var(--color-border, rgba(128, 128, 128, 0.28));
    border-radius: 10px;
}

.ca-maint-row form {
    margin: 0;
    flex: none;
}

.ca-maint-text {
    display: grid;
    gap: 0.2rem;
    /* Without this the text refuses to wrap and pushes the button out. */
    min-width: 0;
    flex: 1 1 16rem;
}

.ca-maint-text strong {
    color: var(--color-heading);
    font-size: 0.95rem;
}

.ca-maint-text span {
    color: var(--color-body);
    font-size: 0.84rem;
}

/* A setting the customer genuinely cannot change reads as deliberate,
   not as a control that failed to load. */
.ca-maint-locked {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--color-body);
    flex: none;
}

/* ---- what the password is actually for ---- */

.ca-uses {
    list-style: none;
    margin: 0 0 1.4rem;
    padding: 0;
    display: grid;
    gap: 0.55rem;
    max-width: 62ch;
    font-size: 0.89rem;
    color: var(--color-body);
    line-height: 1.5;
}

.ca-uses li {
    padding-left: 1.15rem;
    position: relative;
}

/* A small accent tick rather than a bullet — three concrete uses read
   as a checklist, not as prose. */
.ca-uses li::before {
    content: "\2022";
    position: absolute;
    left: 0;
    top: -0.05em;
    color: var(--color-primary-background);
    font-size: 1.2em;
    line-height: 1.2;
}

.ca-uses strong { color: var(--color-heading); }

.ca-uses code {
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    background: var(--background-body);
    border: 1px solid var(--color-border);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.86em;
    color: var(--color-heading);
}

.ca-uses-note {
    font-size: 0.85rem;
    margin-bottom: 1.3rem;
}

/* ---- password form ---- */

.ca-form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 1rem;
    max-width: 680px;
}

.ca-form label {
    display: block;
    font-size: 0.88rem;
    color: var(--color-body);
}

/* Every text-ish input in a client-area form, not just password —
   profile.php has eight text fields and they were falling back to the
   theme default, which left the two-column rows ragged. Both .ca and
   .ca-shell, since a page can be inside either. */
.ca .ca-form input[type="password"],
.ca .ca-form input[type="text"],
.ca .ca-form input[type="email"],
.ca-shell .ca-form input[type="password"],
.ca-shell .ca-form input[type="text"],
.ca-shell .ca-form input[type="email"],
.ca-shell .ca-form input[type="tel"],
.ca-shell .ca-form select,
.ca-shell .ca-form textarea {
    width: 100%;
    margin-top: 0.35rem;
}

.ca-shell .ca-form + .ca-form { margin-top: 1.5rem; }

/* Secondary text inside a table cell or card — a status note that
   should not compete with the row's real content. */
.ca-muted {
    color: var(--color-body);
    font-size: 0.82em;
}

.ca-hint {
    margin: 0.8rem 0 0;
    font-size: 0.83rem;
    color: var(--color-body);
}

/* ---- narrow screens ---- */

@media (max-width: 860px) {
    .ca-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 600px) {
    .ca-card { padding: 1.25rem 1.15rem 1.4rem; }
    .ca-head { padding-top: 1.25rem; }
}

@media (prefers-reduced-motion: reduce) {
    .ca-action,
    .ca-action:hover {
        transition: none;
        transform: none;
    }
}


/* ---------------------------------------------------------------
   Client area — shared shell and remaining primitives

   Wave 1 of the client-area redesign (2026-08-26). The .ca-* card,
   tile, alert and button primitives above shipped first on
   account/cpanel-manage.php; this section adds what the other 27
   signed-in pages need so they can all use one system.

   Context for why this was worth doing: of 28 signed-in pages, only 14
   used the shared hero and 13 the sidebar — FOURTEEN had no shared
   chrome at all. They were not styled differently on purpose, they were
   simply never brought in.

   BUTTONS: everything that looks like a button uses .ca-btn. Navigation
   stays an <a> and actions stay a <button>, so middle-click,
   open-in-new-tab and screen readers keep working while the page reads
   as all-buttons. a.ca-btn therefore has to undo the link styling the
   theme applies.
   --------------------------------------------------------------- */

/* ---- shell ---- */

.ca-shell {
    display: grid;
    grid-template-columns: 250px minmax(0, 1fr);
    gap: 1.75rem;
    max-width: 1180px;
    margin: 2rem auto 3.5rem;
    align-items: start;
}

.ca-shell-main { min-width: 0; }

/* ---- The account menu on a phone (2026-09-07) -------------------
   The shell had no mobile breakpoint at all: a fixed 250px sidebar on
   a 400px screen left the page itself about 130px wide. */

/*
 * DESKTOP KEEPS THE SIDEBAR AND NEVER SHOWS THE BUTTON.
 *
 * At (0,1,0) this LOST to the theme's blanket button rule, so the
 * button stayed visible on desktop, became the first grid item, took
 * the 250px sidebar track and pushed the nav into the content column
 * with the page below it. The whole client area looked broken on a
 * wide screen -- from one unguarded `display: none`.
 *
 * Same specificity as the rules that style it, so hiding and showing
 * are decided by the same weight rather than one of them quietly
 * losing.
 */
body.template-nebulous .ca-shell .ca-nav-toggle,
.ca-shell .ca-nav-toggle {
    display: none;
}

@media (max-width: 860px) {
    .ca-shell {
        /* One column, and the positioning context the open menu hangs
           from. */
        position: relative;
        grid-template-columns: minmax(0, 1fr);
        gap: 0.9rem;
        margin-top: 1.25rem;

        /* The button's height, shared with the panel that sits under
           it. One value, so the two cannot drift apart -- and they
           would, being set in different rules. */
        --ca-menu-h: 3rem;
    }

    /*
     * NAMED CONTAINER on the button, because body.template-nebulous
     * button paints every button on the storefront at (0,1,2) and a
     * single class loses to it. .ca-shell .ca-nav-toggle is (0,2,0).
     */
    body.template-nebulous .ca-shell .ca-nav-toggle,
    .ca-shell .ca-nav-toggle {
        display: flex;
        align-items: center;
        gap: 0.55rem;
        width: 100%;
        min-height: var(--ca-menu-h);
        padding: 0.7rem 0.9rem;
        border: 1px solid var(--color-box-background-border, #d2d2d2);
        border-radius: 8px;
        background: var(--color-box-background, #ffffff);
        color: var(--color-heading, #243141);
        font-size: 0.92rem;
        font-weight: 600;
        cursor: pointer;
    }

    /*
     * EVERY STATE, AND AT A SPECIFICITY THAT ACTUALLY WINS.
     *
     * `body.template-nebulous button:hover` is (0,2,2) and paints the
     * brand orange; `.ca-shell .ca-nav-toggle` is only (0,2,0), so it
     * won the resting state and lost every other one. On a phone a tap
     * leaves the element in :hover AND :focus until something else is
     * tapped -- so the bar turned orange and stayed orange, taking the
     * orange icon with it into invisibility.
     *
     * Prefixing with the body class makes these (0,3,1), which beats
     * (0,2,2) on class count. That is the arithmetic; the browser is
     * the authority, and this file has three comments from previous
     * times the arithmetic looked right and was not.
     */
    body.template-nebulous .ca-shell .ca-nav-toggle,
    body.template-nebulous .ca-shell .ca-nav-toggle:hover,
    body.template-nebulous .ca-shell .ca-nav-toggle:focus,
    body.template-nebulous .ca-shell .ca-nav-toggle:active {
        background: var(--color-box-background, #ffffff);
        color: var(--color-heading, #243141);
        filter: none;
    }

    /* Open gets a tint rather than a fill, so the icon stays legible
       and the state is still obvious. */
    body.template-nebulous .ca-shell .ca-nav-toggle[aria-expanded="true"] {
        background: var(--color-box-background-divider, #f4f4f4);
        border-color: var(--color-primary-background, #f09546);
    }

    /*
     * The icon takes the TEXT colour, not the brand colour. An accent
     * that matches the background is invisible, and which background it
     * ends up on is exactly the thing that went wrong here.
     */
    .ca-shell .ca-nav-toggle i {
        color: inherit;
    }

    /* Keyboard focus still has to be visible; removing the theme's
       paint must not remove the ring with it. */
    body.template-nebulous .ca-shell .ca-nav-toggle:focus-visible {
        outline: 2px solid var(--color-primary-background, #f09546);
        outline-offset: 2px;
    }

    /*
     * COLLAPSED BY DEFAULT, and this needs its own rule rather than
     * [hidden]: .ca-nav sets an author display, which outranks the
     * browser's own [hidden] rule. This file has no global guard, so
     * every component that toggles visibility brings its own -- a trap
     * this codebase has hit six times.
     */
    .ca-shell-nav {
        display: none;
        position: static;
    }

    /*
     * OPEN OVER THE PAGE, NOT ABOVE IT.
     *
     * As a grid item the menu pushed everything down, so opening it
     * moved the thing you were looking at off the screen and closing it
     * moved it back. Floating it over the content leaves the page where
     * it was.
     *
     * Absolute rather than fixed: it belongs to the shell, so it scrolls
     * with the page instead of hanging in front of a page that has
     * moved on underneath it.
     */
    .ca-shell-nav.is-open {
        display: block;
        position: absolute;
        top: calc(var(--ca-menu-h) + 0.55rem);
        left: 0;
        right: 0;
        z-index: 60;

        /* Its own surface, because it is now over content rather than
           in the flow -- without this the page shows through it. */
        background: var(--color-box-background, #ffffff);
        border: 1px solid var(--color-box-background-border, #d2d2d2);
        border-radius: 10px;
        box-shadow: 0 18px 40px -18px rgba(16, 24, 40, 0.45);

        /* A long menu on a short screen scrolls inside itself rather
           than running off the bottom with no way to reach the end. */
        max-height: calc(100vh - 9rem);
        max-height: calc(100dvh - 9rem);
        overflow-y: auto;
    }

    /* The sticky offset is for a sidebar sitting beside a scrolling
       page. Stuck to the top of a phone screen it would cover the
       content it is meant to lead to. */
    .ca-shell-nav .ca-nav {
        position: static;
        top: auto;
    }
}


/* The sidebar tracks the page on long lists; 88px clears the sticky
   menu when that setting is on. */
.ca-shell-nav {
    position: sticky;
    top: 88px;
}

@media (max-width: 900px) {
    .ca-shell {
        grid-template-columns: minmax(0, 1fr);
        gap: 1.25rem;
    }
    .ca-shell-nav { position: static; }
}

/* ---- section nav ----

   Deliberately graphical, per Wayne 2026-08-26. The plain first pass
   was a list of text links; this is meant to read as a product
   surface, not a table of contents.

   Three things do that work: every item carries an icon in its own
   tinted chip, so the eye lands on shape before text; the active item
   is a filled gradient pill with a soft glow rather than a colour
   change; and the identity block up top is a real header rather than
   another row.

   Everything still resolves through theme tokens, so the whole thing
   inverts correctly in dark mode with no second palette.
   --------------------------------------------------------------- */

.ca-nav {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 2px 14px rgba(0, 0, 0, 0.06);
}

/* ---- identity header ---- */

.ca-nav-me {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 1.05rem 1.1rem;
    /* A tint of the accent rather than the accent itself — this is a
       header, not a call to action, and a solid orange block here would
       out-shout the active item below it. */
    background:
        linear-gradient(135deg, rgba(240, 149, 70, 0.16) 0%, rgba(240, 149, 70, 0.04) 100%);
    border-bottom: 1px solid var(--color-border);
}

.ca-nav-me-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: linear-gradient(135deg, #f0a05a 0%, #e07b2a 100%);
    color: #fff;
    font-weight: 700;
    font-size: 1.05rem;
    box-shadow: 0 2px 8px rgba(224, 123, 42, 0.35);
}

.ca-nav-me-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}

.ca-nav-me-text strong {
    color: var(--color-heading);
    font-size: 0.92rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ca-nav-me-text small {
    color: var(--color-body);
    font-size: 0.75rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- groups ---- */

.ca-nav-group { padding: 0.55rem 0.6rem; }

.ca-nav-group + .ca-nav-group {
    border-top: 1px solid var(--color-border);
}

.ca-nav-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.35rem;
    padding: 0.35rem 0.5rem 0;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--color-body);
}

/* A short rule trailing each group label — structure you can see at a
   glance without another border across the whole card. */
.ca-nav-title::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--color-border);
}

.ca-nav ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

.ca-nav li + li { margin-top: 2px; }

/* ---- items ---- */

.ca-shell .ca-nav a {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.45rem 0.55rem;
    border-radius: 9px;
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--color-heading);
    transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

/* The icon chip does most of the graphical work. */
.ca-shell .ca-nav a i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: var(--background-body);
    border: 1px solid var(--color-border);
    color: var(--color-body);
    font-size: 0.8rem;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.ca-shell .ca-nav a:hover {
    background: var(--background-body);
    color: var(--color-primary-background);
    text-decoration: none;
    transform: translateX(2px);
}

.ca-nav a:hover i {
    background: rgba(240, 149, 70, 0.16);
    border-color: rgba(240, 149, 70, 0.4);
    color: var(--color-primary-background);
}

/* The current page: a wash plus an orange bar down the left edge, the
   same treatment as the storefront nav and the admin bar.

   This was a solid orange gradient with a white label, which had a real
   problem beyond consistency: white on the gradient's own end colour
   (#e07b2a) measures 2.98:1, and this label is 0.8rem, so it needed
   4.5:1. The brightest point of the fill was worse still. The nav item
   you are ON was the least readable thing in the nav.

   Two sets of values, for the same reason the storefront nav needed
   them: a wash sits relative to the surface under it, and this sidebar
   is on a white card in light mode and a #2f2f2f one in dark. The old
   orange fill needed only one set because it ignored its surroundings.

   Chosen against the card behind, where a wash fails invisibly:

       light   #eceef1 on #ffffff   1.16 apart, label 11.36:1
       dark    #3c3c3c on #2f2f2f   1.21 apart, label 11.03:1

   A LEFT bar rather than the bottom bar the two top navs use, because
   this nav runs vertically -- the bar marks the edge the eye scans.

   Inset shadow again, so the 3px costs no layout and follows the item's
   own border radius. */
.ca-shell .ca-nav a.is-active {
    background: #eceef1;
    color: #243141;
    box-shadow: inset 3px 0 0 var(--color-primary-background);
    transform: none;
}

body.colortoggle-dark .ca-shell .ca-nav a.is-active {
    background: #3c3c3c;
    color: #ffffff;
}

/* The icon chip keeps the orange tint hover already gives it, rather
   than the white-on-orange it wore inside the old filled pill. */
.ca-shell .ca-nav a.is-active i {
    background: rgba(240, 149, 70, 0.16);
    border-color: rgba(240, 149, 70, 0.4);
    color: var(--color-primary-background);
}

.ca-shell .ca-nav a:focus-visible {
    outline: 2px solid var(--color-primary-background);
    outline-offset: 2px;
}

/* ---- footer ---- */

.ca-nav-foot { padding: 0.75rem 0.85rem 0.95rem; }

.ca-btn-block {
    display: flex !important;
    width: 100%;
    justify-content: center;
    gap: 0.45rem;
}

/* Dark mode: the tinted chips need more presence against #2f2f2f, and
   the identity gradient has to come up off the card. */
body.colortoggle-dark .ca-nav-me {
    background: linear-gradient(135deg, rgba(240, 149, 70, 0.2) 0%, rgba(240, 149, 70, 0.05) 100%);
}

body.colortoggle-dark .ca-shell .ca-nav a i {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.1);
}

body.colortoggle-dark .ca-shell .ca-nav a:hover i {
    background: rgba(240, 149, 70, 0.22);
    border-color: rgba(240, 149, 70, 0.45);
}

@media (prefers-reduced-motion: reduce) {
    .ca-shell .ca-nav a,
    .ca-shell .ca-nav a:hover {
        transition: none;
        transform: none;
    }
}

/* ---- page head (inside the shell, distinct from the hero band) ---- */

.ca-page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.25rem;
    flex-wrap: wrap;
    margin-bottom: 1.4rem;
}

.ca-page-head h1 {
    margin: 0;
    font-size: 1.6rem;
    text-wrap: balance;
}

.ca-page-head p {
    margin: 0.3rem 0 0;
    color: var(--color-body);
    font-size: 0.92rem;
    max-width: 62ch;
}

.ca-page-actions {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
}

/* ---- stat tiles ---- */

.ca-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(155px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.ca-stat {
    position: relative;
    display: block;
    padding: 1.15rem 1.25rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 10px;
    text-decoration: none;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

.ca-stat:hover {
    border-color: var(--color-primary-background);
    transform: translateY(-2px);
}

.ca-stat-value {
    display: block;
    font-size: 1.85rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--color-heading);
    font-variant-numeric: tabular-nums;
}

.ca-stat-label {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-body);
}

.ca-stat-icon {
    position: absolute;
    top: 1.1rem;
    right: 1.15rem;
    color: var(--color-primary-background);
    opacity: 0.5;
    font-size: 1.05rem;
}

/* A zero is information, not an error — mute it rather than shouting a
   bright number at someone with nothing to see. */
.ca-stat.is-empty .ca-stat-value { color: var(--color-body); }

/* ---- tables ---- */

.ca-table-wrap {
    /* Wide tables scroll inside their own box; the page body must never
       scroll sideways. */
    overflow-x: auto;
    margin: 0 -0.25rem;
    padding: 0 0.25rem;
}

.ca-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.ca-table th {
    text-align: left;
    padding: 0.7rem 0.9rem;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-body);
    border-bottom: 2px solid var(--color-border);
    white-space: nowrap;
}

.ca-table td {
    padding: 0.85rem 0.9rem;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-heading);
    vertical-align: middle;
}

.ca-table tbody tr:last-child td { border-bottom: none; }
.ca-table tbody tr:hover td { background: var(--background-body); }

.ca-table td.ca-table-actions {
    text-align: right;
    white-space: nowrap;
}

.ca-table .ca-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.86em;
    /* A date or an invoice number is one token — breaking "2026-09-26"
       across two lines at the hyphen makes it read as two values. */
    white-space: nowrap;
}

.ca-table td.ca-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/*
 * ON A PHONE A TABLE STOPS BEING A TABLE.
 *
 * `.ca-table-wrap` scrolls sideways, which is the honest fallback and
 * still a bad one: on Wayne's phone the Services table showed SERVICE,
 * BILLING CYCLE, STATUS and then "NEXT D" against the card edge. It
 * WAS scrollable -- a phone's overlay scrollbar is invisible until you
 * drag it, so the correct behaviour and a genuinely truncated table
 * look identical, and he reported it as data being cut off.
 *
 * Each row becomes its own little card of label/value pairs instead.
 * The labels come from the <thead>, copied onto each cell by
 * `labelTableCells()` in ca-ajax.js rather than typed into 43 tables
 * by hand -- so a heading and its label cannot drift apart.
 *
 * Nothing here applies above 620px: the desktop table is unchanged.
 */
@media (max-width: 620px) {
    /* Nothing overflows any more, so the scroll box would only add a
       stray gutter. */
    .ca-table-wrap {
        overflow-x: visible;
        margin: 0;
        padding: 0;
    }

    .ca-table,
    .ca-table tbody,
    .ca-table tr,
    .ca-table td {
        display: block;
        width: 100%;
    }

    /* Every cell carries its own heading now; the row of them at the
       top would just be a column of words with no values beside them. */
    .ca-table thead {
        display: none;
    }

    .ca-table tbody tr {
        border: 1px solid var(--color-border);
        border-radius: 8px;
        padding: 0.1rem 0.8rem;
        margin-bottom: 0.7rem;
    }

    .ca-table tbody tr:last-child {
        margin-bottom: 0;
    }

    .ca-table td,
    .ca-table tbody tr:last-child td {
        display: flex;
        gap: 1rem;
        align-items: baseline;
        justify-content: space-between;
        /* The value sits against the right edge, under the one above
           it -- a ragged left column of values is much harder to scan
           than a ragged right one. */
        text-align: right;
        padding: 0.6rem 0;
        border-bottom: 1px solid var(--color-border);
    }

    .ca-table tbody tr td:last-child {
        border-bottom: none;
    }

    .ca-table td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 auto;
        text-align: left;
        font-size: 0.7rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: var(--color-body);
    }

    /* A hover tint made sense on one row of a table. Painting a whole
       card on touch-and-hold does not. */
    .ca-table tbody tr:hover td {
        background: transparent;
    }

    /*
     * The actions cell has no heading, so it has no ::before to sit
     * opposite -- push its buttons to the right on their own.
     *
     * And let them WRAP. The desktop rule sets `white-space: nowrap`,
     * which is right in a table cell that can widen and wrong in one
     * that cannot: Manage / Addons / Request a change measures 387px
     * inside 387px here, so the very next button anyone adds to any of
     * these rows would push off the card with nothing to say so.
     */
    .ca-table td.ca-table-actions {
        justify-content: flex-end;
        flex-wrap: wrap;
        white-space: normal;
        gap: 0.4rem;
    }

    /*
     * The voicemail player carries a 200px FLOOR elsewhere in this file,
     * because below that Chrome drops the scrubber and leaves a message
     * you can start but cannot skip through. Beside a label on a phone
     * that floor is what would push the row wide, so here it takes
     * whatever the cell has left -- around 290px at this size, still
     * well clear of the scrubber.
     */
    .ca-table td audio {
        width: 100%;
        min-width: 0;
    }
}

/* ---- status badges ---- */

.ca-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    border: 1px solid transparent;
}

.ca-badge-ok      { background: rgba(34, 197, 94, 0.12);  border-color: rgba(34, 197, 94, 0.35);  color: #16a34a; }
.ca-badge-warn    { background: rgba(245, 158, 11, 0.14); border-color: rgba(245, 158, 11, 0.4);  color: #f09546; }
.ca-badge-danger  { background: rgba(220, 38, 38, 0.1);   border-color: rgba(220, 38, 38, 0.35);  color: #b91c1c; }
.ca-badge-neutral { background: rgba(120, 120, 120, 0.14);border-color: var(--color-border);       color: var(--color-body); }

/* The mid-tone badge colours go muddy on a dark card. */
body.colortoggle-dark .ca-badge-ok     { color: #4ade80; }
body.colortoggle-dark .ca-badge-warn   { color: #fbbf24; }
body.colortoggle-dark .ca-badge-danger { color: #fca5a5; }

/* ---- empty states ---- */

.ca-empty {
    text-align: center;
    padding: 2.5rem 1.5rem;
    color: var(--color-body);
}

.ca-empty-icon {
    display: block;
    font-size: 1.9rem;
    margin-bottom: 0.7rem;
    color: var(--color-primary-background);
    opacity: 0.45;
}

.ca-empty p { margin: 0 0 1.1rem; font-size: 0.94rem; }

/* ---- links that must look like buttons ----

   a.ca-btn has to undo what the theme gives every <a>, and match the
   <button> box exactly — otherwise a row of "buttons" sits at two
   different heights depending on which element each one happens to be.
   Scoped through .ca so it outranks nebulous.css's own
   body.template-nebulous button rule (0,1,2), the same rule that
   overrode these once already. */

.ca a.ca-btn,
.ca-shell a.ca-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    margin-top: 0;
    padding: 0.6rem 1.15rem;
    border: none;
    border-radius: 6px;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1.2;
    text-decoration: none;
    cursor: pointer;
    transition: filter 0.15s ease;
}

.ca a.ca-btn:hover,
.ca-shell a.ca-btn:hover { filter: brightness(1.07); text-decoration: none; }

.ca a.ca-btn-quiet,
.ca-shell a.ca-btn-quiet {
    background: transparent;
    border: 1px solid var(--color-primary-background);
    color: var(--color-primary-background);
}

.ca a.ca-btn-quiet:hover,
.ca-shell a.ca-btn-quiet:hover {
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    filter: none;
}

/* A third weight for destructive or secondary-to-secondary actions,
   so "Cancel" never wears the same colour as "Pay Now". */
/*
 * THE BUTTON SELECTORS ARE NOT REDUNDANT — they are the fix.
 *
 * `.ca .ca-btn-plain` is specificity (0,2,0) and `.ca button.ca-btn`,
 * which paints the orange fill, is (0,2,1). So on a <button> the
 * orange won at rest while the :hover rule below still applied — the
 * button looked like a normal primary button and then went WHITE the
 * moment you pointed at it, because hover sets background:transparent
 * over a fill that was never removed.
 *
 * Links were always fine: `.ca a.ca-btn-plain` is (0,2,1), ties with
 * the link's own fill rule, and wins on source order. That is why this
 * only ever showed on the handful of pages using <button>.
 *
 * Reported on the calling card page 2026-09-04; service-addons.php's
 * "Cancel" and the shell's own sidebar button had it silently too.
 */
.ca .ca-btn-plain,
.ca-shell .ca-btn-plain,
.ca a.ca-btn-plain,
.ca-shell a.ca-btn-plain,
.ca button.ca-btn-plain,
.ca-shell button.ca-btn-plain {
    background: transparent;
    border: 1px solid var(--color-border);
    color: var(--color-body);
}

.ca .ca-btn-plain:hover,
.ca-shell .ca-btn-plain:hover,
.ca a.ca-btn-plain:hover,
.ca-shell a.ca-btn-plain:hover,
.ca button.ca-btn-plain:hover,
.ca-shell button.ca-btn-plain:hover {
    border-color: var(--color-heading);
    color: var(--color-heading);
    background: transparent;
    filter: none;
}

.ca-btn-sm { padding: 0.4rem 0.8rem !important; font-size: 0.82rem !important; }

/* Recovery codes and the authenticator QR — the client area had no
   monospace block and no light plate, because until the security page
   grew second factors nothing here needed either.

   THE QR PLATE IS ALWAYS WHITE, deliberately. A QR code is read as dark
   modules on a light ground; letting it inherit the card background
   makes it unscannable the moment a customer is in dark mode, and the
   failure looks like a broken camera rather than a styling bug. */
.ca-code-block {
    margin: 0.75rem 0 0;
    padding: 0.9rem 1rem;
    background: var(--color-box-background-alt, rgba(0, 0, 0, 0.04));
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.95rem;
    line-height: 1.7;
    letter-spacing: 0.06em;
    overflow-x: auto;
}
.ca-qr-plate {
    display: inline-block;
    margin: 0.5rem 0 0.75rem;
    padding: 12px;
    background: #fff;
    border: 1px solid #e6e8ec;
    border-radius: 8px;
}
.ca-qr-plate svg { display: block; width: 220px; height: auto; }

/* ---- the shell's own button scoping ----
   .ca-shell needs the same button rules the .ca panel already has. */

.ca-shell button.ca-btn {
    margin-top: 0;
    padding: 0.6rem 1.15rem;
    border: none;
    border-radius: 6px;
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1.2;
    cursor: pointer;
    transition: filter 0.15s ease;
}

.ca-shell button.ca-btn:hover { filter: brightness(1.07); }

.ca-shell button.ca-btn-quiet {
    background: transparent;
    border: 1px solid var(--color-primary-background);
    color: var(--color-primary-background);
}

.ca-shell button.ca-btn-quiet:hover {
    background: var(--color-primary-background);
    color: var(--color-primary-foreground);
    filter: none;
}

/* ---- inline forms sitting in a card ---- */

.ca-inline-form {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
    align-items: center;
}

.ca-inline-form input[type="text"],
.ca-inline-form input[type="email"],
.ca-inline-form select {
    flex: 1 1 200px;
    min-width: 0;
    margin: 0;
}

@media (prefers-reduced-motion: reduce) {
    .ca-stat,
    .ca-stat:hover {
        transition: none;
        transform: none;
    }
}

/* ---- card head row, promo, plain lists ----
   Small additions the dashboard needed once it moved onto the shell. */

.ca-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.ca-card-head .ca-card-title { margin-bottom: 0; }

/* The promo card earns a tinted ground so it reads as an offer rather
   than another data panel, without becoming a solid orange slab. */
.ca-promo {
    background:
        linear-gradient(135deg, rgba(240, 149, 70, 0.1) 0%, rgba(240, 149, 70, 0.02) 100%),
        var(--color-box-background);
}

.ca-promo .ca-card-lead { margin-bottom: 1.1rem; }

.ca-plain-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.7rem;
}

.ca-plain-list li {
    display: flex;
    flex-direction: column;
    padding-bottom: 0.7rem;
    border-bottom: 1px solid var(--color-border);
}

.ca-plain-list li:last-child { border-bottom: none; padding-bottom: 0; }
.ca-plain-list strong { color: var(--color-heading); font-size: 0.92rem; }
.ca-plain-list small { color: var(--color-body); font-size: 0.8rem; }

/* The dashboard stacks two .ca-grid rows; the cards inside them should
   not inherit the manage page's 1.65fr split assumption when the side
   card is genuinely narrow content. */
.ca-shell .ca-grid { margin-bottom: 1.5rem; }

/* Cards stacked straight into the content column had no gap between
   them — only .ca-stats and .ca-grid carried a bottom margin, so two
   consecutive full-width cards sat flush against each other (found
   2026-08-26). Scoped to DIRECT children so cards inside a .ca-grid
   keep using the grid gap and do not get spaced twice. */
.ca-shell-main > .ca-card { margin-bottom: 1.5rem; }
.ca-shell-main > .ca-card:last-child { margin-bottom: 0; }

/* The hero already separates the banner from the content column, so
   the shell does not need its own top margin on top of it. */
.acct-hero + .ca-shell { margin-top: 1.75rem; }

/* A heading INSIDE a card, below its title — e.g. "Queue a post" under
   an account name. Smaller than .ca-card-title so the card still reads
   as one thing with sections, not several cards fused together. */
.ca-shell .ca-subhead {
    margin: 1.5rem 0 0.7rem;
    font-size: 0.98rem;
    color: var(--color-heading);
}

.ca-shell .ca-card-body { margin: 0; }

/* NAV HOVER/ACTIVE: a light wash, not a dark block.

   The theme fills the hovered/current nav item with a solid dark grey
   and forces a near-white label on top, which reads heavy against this
   header. This is the treatment the re-skinned admin bar uses instead:
   a light wash in the bar's own colour family, label left dark, and the
   orange accent bar below marking the current page.

   THIS NEEDS TWO SETS OF VALUES, which the dark block did not. A solid
   mid-grey with a forced light label happens to work on any background;
   a wash sits relative to the bar it is on, and the label has to follow.
   This bar is #f8f8f8 with #222 text in light mode and #111111 with
   white text in dark, so each mode gets its own pair.

   The binding constraint is NOT text contrast -- both pairs land between
   12:1 and 14:1. It is separation from the BAR, where a light wash fails
   invisibly:

       light   #e4e6e9 on #f8f8f8   1.18   chosen
               #eceded on #f8f8f8   1.10   too close to see
       dark    #2f2f2f on #111111   1.41   chosen

   Set as the navmain hover tokens rather than through
   --color-secondary-background/-foreground, which they resolve to by
   default: those globals are used across the whole storefront and would
   repaint unrelated components. */
body.template-nebulous {
    --navmain-headerdesign1-button-hover-background: #e4e6e9;
    --navmain-headerdesign1-button-hover-background-gradient-top: #e4e6e9;
    --navmain-headerdesign1-button-hover-background-gradient-bottom: #e4e6e9;
    --navmain-headerdesign1-button-hover-foreground: #222222;
    --navmain-headerdesign2-button-hover-background: #e4e6e9;
    --navmain-headerdesign2-button-hover-background-gradient-top: #e4e6e9;
    --navmain-headerdesign2-button-hover-background-gradient-bottom: #e4e6e9;
    --navmain-headerdesign2-button-hover-foreground: #222222;

    /* THE HOME ICON HAS ITS OWN TOKENS AND WAS LEFT BEHIND.
       Wayne 2026-09-02: "The home button dims to almost white then
       dissapears on hover."

       The block above repaints the hovered nav item as a light wash and
       darkens the label to follow it — but only through the BUTTON
       tokens. The home item reads --navmain-headerdesign*-home-hover-
       foreground, a separate pair the vendor sets to near-white because
       its own hover fill is a dark block. Against the light wash that
       replaced it, white on #e4e6e9 measures 1.11:1: the icon does not
       fade, it is simply gone.

       Same oversight the mobile home icon hit at line 4534 — revealing
       or restyling an element is not the same as styling every token it
       actually reads. Fixed at 12.72:1. */
    --navmain-headerdesign1-home-hover-foreground: #222222;
    --navmain-headerdesign2-home-hover-foreground: #222222;
}

body.colortoggle-dark.template-nebulous {
    --navmain-headerdesign1-button-hover-background: #2f2f2f;
    --navmain-headerdesign1-button-hover-background-gradient-top: #2f2f2f;
    --navmain-headerdesign1-button-hover-background-gradient-bottom: #2f2f2f;
    --navmain-headerdesign1-button-hover-foreground: #ffffff;
    --navmain-headerdesign2-button-hover-background: #2f2f2f;
    --navmain-headerdesign2-button-hover-background-gradient-top: #2f2f2f;
    --navmain-headerdesign2-button-hover-background-gradient-bottom: #2f2f2f;
    --navmain-headerdesign2-button-hover-foreground: #ffffff;

    /* Dark mode needs BOTH home states, not just hover — and the rest
       state was the worse of the two, though nobody had reported it.
       The vendor's --home-foreground is #222222, which on this mode's
       #111111 bar is 1.19:1: a black house on a black bar, invisible
       until you hover it. Found while fixing the light-mode hover, by
       checking the other three states rather than only the one that was
       reported. */
    --navmain-headerdesign1-home-foreground: #ffffff;
    --navmain-headerdesign2-home-foreground: #ffffff;
    --navmain-headerdesign1-home-hover-foreground: #ffffff;
    --navmain-headerdesign2-home-hover-foreground: #ffffff;
}

/* The current item's orange bar along its bottom edge.

   Orange as an accent rather than a fill: this nav already has exactly
   one orange item, the Order Now CTA at solid #f09546, and a second
   orange block would cost the CTA its meaning.

   Inset shadow rather than ::after so nothing needs position: relative
   -- these <li>s are the positioning context for their own mega-menu
   panels, and changing that would move the dropdowns. */
.navmain ul li.navmain-active {
    box-shadow: inset 0 -3px 0 var(--color-primary-background);
}

/* ===================================================================
   CURRENT PAGE IN THE NAV

   The top-level <li> is handled by the two blocks directly above --
   the theme styles .navmain-active identically to :hover, header.php
   sets it, and those tokens retune both. This section covers the
   mega-menu panel.
   =================================================================== */

/* The current category INSIDE an open mega-menu.

   This first copied the theme's megamenu HOVER styling, which is a
   solid dark fill. That was fine while the bar's own active state was
   also a dark block, and became obviously wrong the moment the bar
   moved to a light wash: with the menu open, the current category sat
   there as the one dark card among light ones, contradicting the light
   marker directly above it (Wayne, 2026-08-26).

   It now keeps whatever fill its siblings have — which the theme
   already adapts per colour mode, #dddddd on white in light and #4f4f4f
   on #2f2f2f in dark — and is marked with an orange bar down its left
   edge. Same language as the bar's orange bar underneath, and because
   the fill and label are untouched, it stays correct in both modes with
   no second set of values.

   An inset shadow rather than a border-left: these cards already carry
   a 1px border, so a 3px one would shift their content sideways by 2px
   relative to every other card. */
.navmain-dropdown-megamenu .navmain-subcontainer .navmain-link.navmain-link-current > a {
    box-shadow: inset 3px 0 0 var(--color-primary-background);
}

/* The panel's own hover, retuned for the same reason as the bar's.

   Left alone, hovering any card gave the theme's dark fill, so simply
   moving the pointer across an open menu produced the dark block the
   change above was removing. These are one step from the sibling card
   in each mode — darker on light, lighter on dark — so hover reads as a
   lift rather than an inversion.

   THE LABEL COLOUR HAS TO MOVE WITH IT. An earlier version of this
   comment said the opposite — that the theme's own value differs per
   mode and pinning it would break one of the two. That reasoning was
   wrong in the way that matters: the theme's value is WHITE, chosen for
   the dark fill this rule replaces. Leaving it meant white text on a
   light grey wash, which Wayne found by hovering a card and watching
   the words disappear.

   Changing a background without its paired foreground is the same
   mistake as the teal hover earlier today, from the other direction.
   Both halves are stated per mode below.

   HOVER IS ORANGE (Wayne's call). Orange text needs a light ground to
   carry, so the wash went warm and pale rather than grey:

       light   #b45309 on #fef5ec   4.66:1
       dark    #f0a05a on #3a3a3a   5.35:1

   The bright brand orange itself measures 2.02:1 as text on that wash
   and 1.85:1 on the grey it replaced — this is the same split the rest
   of the app uses, where the accent marks and a deeper step carries
   words. */
body.template-nebulous {
    --navmain-headerdesign1-megamenu-button-hover-background: #fef5ec;
    --navmain-headerdesign1-megamenu-button-hover-background-gradient-top: #fef5ec;
    --navmain-headerdesign1-megamenu-button-hover-background-gradient-bottom: #fef5ec;
    --navmain-headerdesign1-megamenu-button-hover-foreground: #f09546;
    /* The theme's hover border resolves to #000 (via
       --color-secondary-background-border) against a resting #9c9c9c —
       fine behind the dark fill it was drawn for, harsh on a pale warm
       wash. Matched to the wash instead. */
    --navmain-headerdesign1-megamenu-button-hover-background-border: #e5c9a9;
    --navmain-headerdesign2-megamenu-button-hover-background: #fef5ec;
    --navmain-headerdesign2-megamenu-button-hover-background-gradient-top: #fef5ec;
    --navmain-headerdesign2-megamenu-button-hover-background-gradient-bottom: #fef5ec;
    --navmain-headerdesign2-megamenu-button-hover-foreground: #f09546;
    --navmain-headerdesign2-megamenu-button-hover-background-border: #e5c9a9;
}

body.colortoggle-dark.template-nebulous {
    --navmain-headerdesign1-megamenu-button-hover-background: #3a3a3a;
    --navmain-headerdesign1-megamenu-button-hover-background-gradient-top: #3a3a3a;
    --navmain-headerdesign1-megamenu-button-hover-background-gradient-bottom: #3a3a3a;
    --navmain-headerdesign1-megamenu-button-hover-foreground: #f0a05a;
    --navmain-headerdesign1-megamenu-button-hover-background-border: #5a4a38;
    --navmain-headerdesign2-megamenu-button-hover-background: #3a3a3a;
    --navmain-headerdesign2-megamenu-button-hover-background-gradient-top: #3a3a3a;
    --navmain-headerdesign2-megamenu-button-hover-background-gradient-bottom: #3a3a3a;
    --navmain-headerdesign2-megamenu-button-hover-foreground: #f0a05a;
    --navmain-headerdesign2-megamenu-button-hover-background-border: #5a4a38;
}

/* ===================================================================
   MEGA-MENU ICON TILES: ONE COLOUR, NOT TWELVE

   header.php used to emit a per-category hex as an inline
   background-color, so an open menu showed blue, indigo, sky, pink,
   violet, green, red, orange, teal and slate tiles side by side. The
   colours carried no meaning — they were one-per-slug, not one-per-kind
   — so all they did was compete.

   Same treatment as the admin's System Settings cards: a tinted chip
   with a brand-orange glyph. That was chosen there because a filled
   chip forces the fill dark enough for white to sit on it; a tint lets
   the brand orange stay bright and puts the contrast burden on the
   glyph, which is easy to satisfy.

   Both colour modes, because the chip is a tint of the panel behind it
   and the panel differs: white in light mode, #2f2f2f in dark.
   =================================================================== */

body.template-nebulous .navmain-link.navmain-icon i {
    background: rgba(240, 149, 70, 0.16) !important;
    color: #f09546;
    border: 1px solid rgba(240, 149, 70, 0.34);
}

body.colortoggle-dark.template-nebulous .navmain-link.navmain-icon i {
    background: rgba(240, 149, 70, 0.20) !important;
    color: #f09546;
    border-color: rgba(240, 149, 70, 0.38);
}

/* ===================================================================
   STOREFRONT CONTRAST

   From the same sweep that cleared the admin, run over 40 storefront
   and client-area pages. Five root causes, 383 instances. These are
   customer-facing on unknown screens in unknown light, so they matter
   more here than in a tool only staff use.
   =================================================================== */

/* Badges. Ten-pixel uppercase white on mid-tone fills — the smallest
   text on the site, on the least forgiving grounds. Same hues, darker:
   
       NEW  #38a169  3.25:1  ->  #276749  6.73:1
       HOT  #e53e3e  4.13:1  ->  #c53030  5.47:1 */
body.template-nebulous .footer-badge-new,
body.template-nebulous .nav-highlight-new {
    background: #276749;
}

body.template-nebulous .footer-badge-hot,
body.template-nebulous .nav-highlight-hot {
    background: #c53030;
}

/* Plan feature lists: brand orange body text on white at 15px, 2.31:1,
   105 of them across the category and shop pages. The SPECIFICATIONS of
   the thing being sold, which is the copy a buyer most needs to read.
   Same split as the admin — the accent stays where it marks, and text
   takes the darker step. */
/* Plan feature lines: body-coloured text with a small green tick.

   They were orange (#f09546), semi-bold, and had no marker at all —
   list-style:none with nothing put back in its place. A column of bold
   orange sentences with no bullets, under an orange header, on every
   plan card on the site. Wayne: "I don't like the orange text with no
   bullets everywhere."

   The tick is the lightweight version of the badge the feature blocks
   use (.nebulous-alt-block-checklist). That badge is a white glyph on a
   filled green square, which suits a wide block but dominates a 260px
   card once four or seven of them stack up. Same colour and same
   meaning — "included" — at a weight the card can carry. */
body.template-nebulous .pricing-card-body li {
    color: var(--color-body);
    font-weight: 400;
    position: relative;
    padding-left: 1.5rem;
    text-align: left;
}
body.template-nebulous .pricing-card-body li::before {
    content: "\2713";
    position: absolute;
    left: 0;
    top: 0.42rem;
    font-size: 0.9rem;
    line-height: 1;
    font-weight: 700;
    color: var(--color-badge-new-background);
}

/* A description that is one prose sentence rather than a list takes a
   round dot instead of a tick.

   A tick claims "included", which is right for one line of a feature
   list and wrong against a four-line paragraph describing the product —
   that is how the SSL cards looked when the marker first went on. The
   dot marks the paragraph without asserting anything, and the text is
   indented to the same measure so a prose card still lines up with a
   list card beside it. */
body.template-nebulous .pricing-card-body li:only-child::before {
    content: "";
    top: 0.62rem;
    left: 0.1rem;
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: var(--color-badge-new-background);
}

/* DARK MODE NEEDS THE OPPOSITE DIRECTION.
   
   The three rules in this block were written against white cards and
   hardcoded a DARKER colour to gain contrast. In dark mode the card is
   #2f2f2f, so darker is exactly wrong: #b45309 on #2f2f2f measures
   2.67:1 and the price suffix's #575757 measures 1.88:1 — worse than
   what they replaced.
   
   Caught by sweeping in real dark mode rather than by injecting the
   toggle class, which is what the earlier attempts did. Those reported
   13 artifacts and none of these 183 real failures, because the
   injected class never switched the card backgrounds. */
body.colortoggle-dark.template-nebulous .pricing-card-body li {
    color: #f09546;
}

/* The price suffix, /mo and /monthly, on the grey card head. */
body.template-nebulous .pricing-card-price-term {
    color: #575757;
}

body.colortoggle-dark.template-nebulous .pricing-card-price-term {
    color: #b9bcc2;
}

/* Download call-to-action. */
body.template-nebulous .download-card-cta {
    color: #f09546;
}

body.colortoggle-dark.template-nebulous .download-card-cta {
    color: #f09546;
}

/* The last few: white text on the brand orange, 2.04:1 — the same
   decision already taken in the admin, applied to the storefront's own
   calls to action.
   
   The brand colour is NOT moved. Dark text on the unchanged orange
   reaches about 6:1, better than any darker fill white could sit on,
   and the button stays exactly the colour of the CTA beside it. That
   matters more here than in the admin: this orange is the public
   brand, and darkening it to suit white text would have changed the
   thing customers recognise in order to fix the text on top of it. */
/* Stated, not inherited from --color-heading. That token is dark in
   light mode and WHITE in dark, but the button's orange fill is the
   same in both — so following it put white back on orange at 2.31:1 the
   moment dark mode was on. The fill does not change, so neither should
   the label. */
body.template-nebulous .domainsearch-transfer,
body.template-nebulous .kb-btn,
body.template-nebulous .product-detail-add button,
body.template-nebulous button:not([class]) {
    color: #2c2d30;
}

/* The product price. At 28px it only needs 3:1 and still missed, at
   2.21 — the number a buyer looks at before anything else. */
body.template-nebulous .product-detail-price {
    color: #f09546;
}

body.colortoggle-dark.template-nebulous .product-detail-price {
    color: #f09546;
}

/* Breathing room under a comparison column's header.
   
   The deleted blurb row was also, incidentally, the only thing holding
   the first feature row off the header — take it out and the grid butts
   straight against the heading (Wayne, 2026-08-26).
   
   Padding rather than a spacer element, deliberately: a spacer is what
   the blurb row effectively became, and an empty element that exists
   only for height is exactly the thing that ended up filled with a spec
   and mistaken for a value. Padding cannot be filled with anything.
   
   The selector is structural — first feature list after a header — so it
   applies to the label column and every plan column alike, which is the
   only reason the rows stay level. */
body.template-nebulous .product-tables1-head + .product-tables1-features-list,
body.template-nebulous .product-tables1-head2 + .product-tables1-features-list {
    /* 0.85rem first, which measured near-balanced against the ~20px
       below the list — but it still read as high, because what Wayne
       was comparing against was the OLD spacing, where the blurb row
       pushed the first feature 107px clear of the header. Balanced and
       "looks right" were not the same thing here; the eye was matching
       a remembered position, not the space below. */
    padding-top: 2rem;
}

/* An unclassed <button> takes the theme's base style — brand orange
   with a near-white label, 2.04:1. The earlier pass fixed the buttons
   that carry a class (.domainsearch-transfer, .kb-btn, the product add
   form); this is the one that carries none, on the domain-transfer
   form. Same treatment: the orange stays, the text darkens. */


/* ===================================================================
   STOREFRONT VISUAL DESIGN
   ===================================================================

   Folded in from reskin.css on 2026-08-26, which existed as a separate
   stylesheet behind ?reskin=1 while Wayne decided whether to keep it.
   He kept it, so the gate and the second file are gone.

   Appended rather than merged into the rules above, for the same reason
   the admin fold was: it reproduces the cascade this was written under,
   where reskin.css loaded after nebulous.css and won on order.

   Its :root token block is kept as :root, unlike the admin fold. That
   one had to be scoped because app.css is shared with the storefront;
   this file is loaded only by the Nebulous template, and every token
   below is one of the theme's own --color-*, --shadow or
   --border-radius-* values. There is nowhere for them to leak to.

   ONE REAL CHANGE IN SCOPE, worth knowing: the gate lived in
   NebulousTemplate::headHtml(), which the client-area shell never
   calls, so /account/* never saw this preview. Folding it into
   nebulous.css applies it there for the first time — the client area
   is template-nebulous too. Swept afterwards for exactly that reason.
   =================================================================== */

/* =================================================================
   Storefront re-skin — the client-area look, applied to the vendor
   theme WITHOUT replacing any of its markup.

   Built 2026-08-26 as a preview. Load it by adding ?reskin=1 to any
   storefront URL; nothing changes for anyone who does not.

   WHY THIS WORKS

   Nebulous's own stylesheet is 88% token-driven — 1,962 var() uses
   against 271 hardcoded colours, over 156 variables. So the difference
   between the vendor look and the client-area look is almost entirely
   the VALUES of those tokens, not the rules that consume them.

   Redefining them re-skins every vendor component at once — plan
   tables, boxes, sliders, mega-menus, badges — while their HTML and
   their JavaScript carry on untouched. That matters because those
   components are where the six plan layouts live.

   Wayne maintains this theme himself and takes no upstream updates, so
   there is no merge cost to weigh against editing it.

   WHAT ACTUALLY DIFFERS

   Four things carry most of the change:

     radius   5px  ->  10px      cards read as soft, not boxy
     shadow   heavy grey -> soft  0 3px 8px 2px #999 is a 2010 shadow
     borders  #d2d2d2 -> #e6e8ec  quieter, so content leads
     text-shadow: removed        the theme shadows almost every label

   The palette itself barely moves: the client area was built from this
   theme's own accent and heading colours, so they already agree.
   ================================================================= */

:root {
    /* ---- the primary button's gradient ----
       DEFINED HERE, NOT ONLY IN app.css.

       These four are read further down by .button1.color1 — the
       theme's primary button, used in about 30 places — to repaint the
       vendor's `background: linear-gradient(...) !important` by
       redefining the tokens it resolves. They were only ever declared
       in app.css, which the STOREFRONT NEVER LOADS: it loads
       variables.css, layout.css, slider.css, animate.css,
       brand-overrides.css and this file, and that is all.

       An undefined var in a custom property makes it
       guaranteed-invalid, which makes the gradient that reads it
       invalid at computed-value time, which drops `background` to its
       initial value — transparent. Combined with the `color: #fff`
       those same rules set, every primary button on the storefront was
       white text on nothing. Not a colour that was wrong; a background
       that was never painted.

       Reported as "white buttons throughout the site on some of the
       products" and confirmed on category.php's plan-comparison Order
       Now buttons, which sit on a light column and so disappeared
       completely.

       Values are kept identical to app.css's on purpose — the same
       button should not be two oranges depending on which half of the
       app you are in. If one moves, move both. */
    /* The vendor ships --color-default UNDEFINED and then reads it bare
       in two places: a button's `color`, and
       --product-tables1-box-features-foreground, which colours the
       comparison table's label column. An undefined var makes those
       declarations invalid at computed-value time, so both fall back to
       inherited rather than to any chosen colour — they happened to
       land near the right shade, which is why nobody noticed.

       Found by the CSS audit, which flagged both on every one of 51
       pages. Pointed at the theme's real body-text token so the value
       is chosen rather than accidental. */
    --color-default: var(--color-body);

    --btn-grad-from: #f5a45f;
    --btn-grad-to: #e07b2a;
    --btn-grad-hover-from: #f0a05a;
    --btn-grad-hover-to: #d9761f;

    /* ---- corners ----
       The single biggest tell. Every panel, button, input and badge in
       the vendor sheet reads this one value. */
    --border-radius-default: 10px;

    /* ---- shadows ----
       The vendor shadow is a hard grey halo. The client area uses a
       shallow, low-opacity drop that reads as depth rather than glow. */
    --shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    --color-box-background-shadow: transparent;
    --color-primary-background-shadow: transparent;
    --color-secondary-background-shadow: transparent;
    --color-tertiary-background-shadow: transparent;

    /* ---- borders ----
       Softer, so a card is defined by its surface rather than its
       outline. */
    --color-box-background-border: #e6e8ec;
    --color-border: #e6e8ec;
    --color-box-background-divider: #eef0f2;
    --color-tertiary-background-border: #e6e8ec;

    /* ---- text shadows ----
       The theme puts a 1px shadow behind most labels, which muddies
       small type. The client area has none anywhere. */
    --color-primary-foreground-shadow: transparent;
    --color-secondary-foreground-shadow: transparent;
    --color-tertiary-foreground-shadow: transparent;
    --color-box-foreground-shadow: transparent;
    --color-box-foreground-bold-shadow: transparent;
    --color-heading-shadow: transparent;
}

/* -----------------------------------------------------------------
   The 271 hardcoded spots the tokens cannot reach.

   These are deliberately few and targeted. Each one is a place the
   vendor sheet wrote a literal instead of a variable.
   ----------------------------------------------------------------- */

/* Panels and boxes: the vendor draws a 1px border plus its shadow
   colour as a second ring. One quiet border is enough. */
body.template-nebulous .box,
body.template-nebulous .product-tables1-box,
body.template-nebulous .product-boxes1-box,
body.template-nebulous .product-boxes2-box,
body.template-nebulous .product-boxes3-box {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    border-radius: 10px;
    overflow: hidden;
}

/* Buttons: match the client-area button box exactly — same radius,
   same weight, no text shadow, a hover that brightens rather than
   swapping to a second gradient. */
body.template-nebulous .button1,
body.template-nebulous .button2,
body.template-nebulous a.button,
body.template-nebulous button {
    border-radius: 6px;
    text-shadow: none;
    box-shadow: none;
    font-weight: 700;
    transition: filter .15s ease;
}

body.template-nebulous .button1:hover,
body.template-nebulous .button2:hover,
body.template-nebulous a.button:hover,
body.template-nebulous button:hover {
    filter: brightness(1.07);
}

/* Inputs: the theme gives them the same square corners as its panels. */
body.template-nebulous input[type="text"],
body.template-nebulous input[type="email"],
body.template-nebulous input[type="password"],
body.template-nebulous input[type="search"],
body.template-nebulous select,
body.template-nebulous textarea {
    border-radius: 6px;
    border-color: #e6e8ec;
}

/* Plan tables: the component the six layouts are built on. Rounding
   the outer shell and quietening the internal rules is enough — the
   structure, the popular ribbon and the JS all stay as they are. */
body.template-nebulous .product-tables1,
body.template-nebulous .product-tables2 {
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}

body.template-nebulous .product-tables1 td,
body.template-nebulous .product-tables1 th {
    border-color: #eef0f2;
}

/* Section headings sit in a bordered pill in this theme; soften it to
   match the card family rather than looking like a tab. */
body.template-nebulous .section-heading h2,
body.template-nebulous .section-heading h1,
body.template-nebulous .headingstyle2 {
    border-radius: 8px;
}

/* -----------------------------------------------------------------
   Dark mode
   The theme's own .colortoggle-dark block redefines the palette but
   not these shape tokens, so they carry over. Only the borders need
   re-stating against a dark surface.
   ----------------------------------------------------------------- */

body.colortoggle-dark {
    --color-box-background-border: #3a3a3a;
    --color-border: #383838;
    --color-box-background-divider: #333333;
}

body.colortoggle-dark.template-nebulous .box,
body.colortoggle-dark.template-nebulous .product-tables1,
body.colortoggle-dark.template-nebulous .product-tables2 {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

/* =================================================================
   PASS 2 — the parts you can actually see.

   Pass 1 was tokens: radius, shadow, borders, text-shadow. Wayne's
   response was "I'm not seeing much of a change", which is fair and
   worth recording. Those are the quiet half of the job. The client
   area does not read as modern because its corners are 10px — it reads
   that way because of three signatures:

     1. a short orange rule on the top-left of every card
     2. icons in tinted chips rather than bare glyphs
     3. soft surfaces with quiet dividers, so content leads and the
        chrome recedes

   None of that arrives from a token. This pass applies those three to
   the vendor's own components, still without touching their markup.
   ================================================================= */

/* ---- 1. The accent rule ----------------------------------------
   The single most recognisable thing about the client area. Applied
   to every vendor surface that behaves like a card. ::before is free
   here — none of these components use it. */

body.template-nebulous .product-tables1,
body.template-nebulous .product-tables2,
body.template-nebulous .product-boxes1-box,
body.template-nebulous .product-boxes2-box,
body.template-nebulous .product-boxes3-box,
body.template-nebulous .content-block,
body.template-nebulous .box {
    position: relative;
}

body.template-nebulous .product-tables1::before,
body.template-nebulous .product-tables2::before,
body.template-nebulous .product-boxes1-box::before,
body.template-nebulous .product-boxes2-box::before,
body.template-nebulous .product-boxes3-box::before,
body.template-nebulous .content-block::before,
body.template-nebulous .box::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 74px;
    height: 3px;
    background: var(--color-primary-background);
    z-index: 2;
}

/* ---- 1a. Plan cards join the same language ----------------------
   The plan cards were built from the vendor's own greys — #525252 for
   the head, #dddddd for the price strip — and neither is in this
   site's palette. Next to everything else now using #243141 ink and
   #e6e8ec hairlines they read as a different era of the design, which
   is what "polish the plans" actually comes down to.

   Overridden SCOPED TO .pricing-card rather than by repointing
   --color-box-head-background and --color-box-footer-background
   themselves: the vendor derives other boxes from those two tokens, so
   moving them would drag panels nobody asked about into this change.

   The artwork gets the same recessed panel the feature rows got — a
   tinted ground with a hairline, so the illustration looks placed
   rather than sitting loose on the card. */

/* ---- Card edges that actually read ------------------------------
   Measured, not guessed: the hairline was #e6e8ec on a #f2f3f5 page
   ground — 1.10:1. That is not a subtle border, it is an invisible one.
   The card surface was barely better at 1.11:1 against the same ground,
   so white cards were floating on near-white with nothing marking where
   they stopped. Wayne: "The card borders are blending in with the
   background."

   Two changes, because darkening the line alone only fixes half of it:
   a deeper edge (#ccd3dc, 1.36:1 against the ground) AND a real shadow,
   which separates the card BODY rather than just outlining it.

   Deliberately not repointing --color-box-background-border itself. That
   token is also the edge of inputs, tables, the recessed panels INSIDE
   these cards, and dividers between rows — darkening all of those was
   tried across this app once before and came back as "everything looks
   so heavy". This is a separate token used only where a card meets the
   page. */
:root {
    --card-edge: #ccd3dc;
    --card-lift:
        0 1px 2px rgba(16, 24, 40, 0.06),
        0 4px 12px rgba(16, 24, 40, 0.08);
}
body.colortoggle-dark {
    /* Dark inverts: the edge must go LIGHTER than the card, and the
       shadow deeper, since a soft shadow does nothing on a dark ground. */
    --card-edge: #454545;
    --card-lift:
        0 1px 2px rgba(0, 0, 0, 0.4),
        0 6px 16px rgba(0, 0, 0, 0.45);
}

/* ONE HAIRLINE AROUND EVERY PLAN CARD.

   The vendor pairs each badge with a matching card outline: popular and
   hot get #dc1010, new #45a523, free #15ace5. Four cards side by side
   therefore came framed in four different colours, and the red one read
   as an error rather than a recommendation — red is what this app uses
   for genuine failure states (.ca-alert-error, the danger button), so a
   red-outlined plan says "something is wrong with this one".

   Wayne, 2026-08-28: "no red border on any cards" / "put 1 border
   around all of them like you did below". So every card now takes the
   same #e6e8ec hairline as the feature rows and the cart, and the
   RIBBON alone carries which plan is popular — one signal instead of
   two competing ones.

   .featured keeps its shadow: that is depth, not another colour, and it
   is still a useful way to lift the recommended plan. */
body.template-nebulous .pricing-card,
body.template-nebulous .pricing-card.featured,
body.template-nebulous .pricing-card.badge-popular,
body.template-nebulous .pricing-card.badge-hot,
body.template-nebulous .pricing-card.badge-new,
body.template-nebulous .pricing-card.badge-free {
    border-color: var(--card-edge);
}

/* The light cap, same as the mini-cart's: #f1f3f5 with dark grey text
   and a hairline under it. This was briefly the navy #243141 — Wayne,
   2026-08-28: "It's too dark. I like the light style we were using and
   the tighter borders." The light cap keeps the card bright all the way
   down and lets the hairlines do the structural work instead of a heavy
   bar. */
body.template-nebulous .pricing-card-head {
    /* Brand orange from the start, not only on hover (Wayne,
       2026-08-28: "I think they should start orange" / "the top of the
       cards"). Same gradient the primary buttons use, so the card top
       and its Order Now button are demonstrably the same orange rather
       than two that nearly match. */
    background: linear-gradient(var(--btn-grad-from) 0%, var(--btn-grad-to) 100%);
    border-bottom: 1px solid var(--btn-grad-to);
}
/* Three classes, because the vendor's own
   "body.template-nebulous h3 { color }" outranks two — the same
   specificity fight already documented on .pricing-card-head h3 above,
   which set white for the old dark bar. Dark text now, since the bar is
   light. */
/* DARK label on the orange, not white — the precedent this theme
   already set for the comparison table's highlighted column, where
   white measured 2.04:1 on the same fill and was replaced with #2c2d30.
   Repeating that here keeps the two plan presentations consistent AND
   is the readable choice: dark clears AA on both stops of the gradient,
   white clears neither. */
body.template-nebulous .pricing-card .pricing-card-head h3 { color: #2c2d30; }

/* Hover deepens the same orange rather than changing colour — the card
   is already orange, so the affordance is a shift in depth, not a
   different hue. Label stays dark for the same reason. */
body.template-nebulous .pricing-card:hover .pricing-card-head {
    background: linear-gradient(var(--btn-grad-hover-from) 0%, var(--btn-grad-hover-to) 100%);
}
body.template-nebulous .pricing-card:hover .pricing-card-head h3 { color: #2c2d30; }

body.template-nebulous .pricing-card-image {
    background: #f7f8fa;
    border-bottom: 1px solid var(--color-box-background-divider);
    padding: 0.85rem;
}
body.template-nebulous .pricing-card-image img {
    border-radius: 8px;
}

body.template-nebulous .pricing-card-features-label {
    background: #f7f8fa;
}

body.template-nebulous .pricing-card-price {
    background: #f7f8fa;
    border-top: 1px solid var(--color-box-background-divider);
}

/* Dark partners. Same inversion as everywhere else today: the tinted
   strips have to go DARKER than the card, not lighter. */
/* No dark override for the head any more: the orange fill is the same
   in both themes, so following --color-heading (white in dark) would
   have put white back on orange at 2.03:1 the moment dark mode was on.
   The fill does not change, so neither does the label. */
body.colortoggle-dark.template-nebulous .pricing-card-image,
body.colortoggle-dark.template-nebulous .pricing-card-features-label,
body.colortoggle-dark.template-nebulous .pricing-card-price { background: #262626; }

/* ---- 1b. Alternating image/text rows become cards ---------------
   The big illustration-plus-copy rows (partials/marketing-sections.php
   renders these) were the last flat thing left on the storefront —
   artwork and text floating directly on the page ground with nothing
   holding them together. They read as unfinished next to everything
   else that is now a card.

   They were missed by section 2 below for a simple reason: these are
   .nebulous-alt-block, NOT .content-block. Two different classes for
   two different layouts, and the card treatment only ever named one of
   them. Worth remembering when the next "why is this bit still flat"
   comes up — check the class before assuming the rule is broken.

   The image sits on its own recessed ground inside the card, rather
   than on the card surface. These illustrations are transparent PNGs
   with no rectangle of their own, so on a plain white card they float
   with no edge at all; a tinted panel gives them a shape to sit in and
   makes the artwork look placed rather than dropped. */

body.template-nebulous .nebulous-alt-block {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 10px;
    padding: 1.75rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    /* NOT clipped. The halves slide in by their own full width, the way
       globalwebconcepts.net's equivalent rows do, so they must be free
       to travel across the card while they arrive. Clipping was tried
       and made the card render EMPTY for the second the slide takes,
       which reads far worse than content moving over an edge.
       Measured on the reference: -581px of travel on a 586px element,
       over 1s. */
    overflow: visible;
}

/* The figure half: a recessed panel for the artwork to sit on.
   flex/min-width are restated because this element is a flex item and
   giving it a background does not change that — see the min-width:0
   note on .nebulous-alt-block img further up, which is the same trap. */
body.template-nebulous .nebulous-alt-block > .nebulous-alt-block-figure,
body.template-nebulous .nebulous-alt-block > img {
    background: var(--color-box-background-divider);
    border: 1px solid var(--color-box-background-border);
    border-radius: 10px;
    padding: 1.25rem;
    min-width: 0;
}

/* The artwork itself keeps its own sizing rules; it just no longer
   needs to be the thing defining the row's edge. */
body.template-nebulous .nebulous-alt-block > .nebulous-alt-block-figure img {
    max-height: 380px;
}

/* A hairline above the checklist, so the "what you get" list reads as a
   distinct block rather than another paragraph. */
body.template-nebulous .nebulous-alt-block-checklist {
    padding-top: 1rem;
    border-top: 1px solid var(--color-box-background-divider);
}

/* Dark: the recessed panel has to go DARKER than the card, where in
   light mode it goes darker than white by being grey. Same relationship,
   opposite direction — the same inversion the mini-cart needed. */
body.colortoggle-dark.template-nebulous .nebulous-alt-block > .nebulous-alt-block-figure,
body.colortoggle-dark.template-nebulous .nebulous-alt-block > img {
    background: #262626;
    border-color: #3a3a3a;
}

@media (max-width: 767px) {
    body.template-nebulous .nebulous-alt-block { padding: 1.15rem; }
    body.template-nebulous .nebulous-alt-block > .nebulous-alt-block-figure,
    body.template-nebulous .nebulous-alt-block > img { padding: 0.9rem; }
}

/* ---- 2. Content blocks become cards ----------------------------
   These are the feature rows down a category page. They were flat
   text on the page ground; as cards they match the client area's
   panels and the page gains structure. */

body.template-nebulous .content-block {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 10px;
    padding: 1.6rem 1.75rem 1.75rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    overflow: hidden;
    margin-bottom: 1.5rem;
}

/* Their icon becomes a tinted chip, exactly as in the client-area nav
   and the knowledgebase category cards. */
body.template-nebulous .content-block-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 10px;
    background: rgba(240, 149, 70, 0.14);
    font-size: 1.15rem;
    margin-bottom: 0.6rem;
}

/* ---- 3. Plan tables -------------------------------------------
   The component the six layouts are built on. Structure, ribbon and
   JavaScript all stay; only the surfaces change. */

/* Column headers: flat fills rather than the theme's gradient-plus-
   shadow, matching the client area's flat card titles. */
body.template-nebulous .product-tables1-head2,
body.template-nebulous .product-tables1-heading2 {
    text-shadow: none;
    box-shadow: none;
}

/* Feature rows: the theme rules every row at full strength, which
   reads as a spreadsheet. The client-area table uses one quiet
   divider and lets the values carry. */
body.template-nebulous .product-tables1-features-list-item {
    border-color: #eef0f2;
}

/* The buy button in each column gets the client-area button box. */
body.template-nebulous .product-tables1-buttons .button1 {
    border-radius: 6px;
    text-shadow: none;
    box-shadow: none;
}

/* ---- 4. Section headings --------------------------------------
   The theme frames each one in a bordered pill floating on a rule.
   Softening the frame lets it read as a heading rather than a tab. */

body.template-nebulous .section-heading h2,
body.template-nebulous .section-heading h1 {
    border-radius: 8px;
    text-shadow: none;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

/* ---- 5. Mega-menu panels --------------------------------------
   These already carry icon chips — that pattern came FROM here — so
   they only need the softer card treatment to agree with the rest. */

body.template-nebulous .navmain-subcontainer {
    border-radius: 0 0 12px 12px;
    /* Raised to match the admin chrome's treatment (see
       .admin-topbar in app.css). Two layers rather than one: the tight
       layer seats the panel against the header it drops from, the wide
       layer is what actually makes it float over the page beneath.

       A dropped panel needs more depth than a fixed bar, not the same
       amount — it overlaps live content, so the shadow is the only
       thing separating its text from the text behind it. */
    box-shadow:
        0 2px 4px rgba(16, 24, 40, 0.08),
        0 12px 28px rgba(16, 24, 40, 0.16);
    border: 1px solid var(--color-box-background-border);
    border-top: none;
}

body.template-nebulous .navmain-link.navmain-icon > a {
    border-radius: 9px;
    transition: background .14s ease, transform .14s ease;
}

/* The 2px nudge on hover is gone.
   
   It was added as a "lift" micro-interaction, copied from the
   client-area nav where the items are full-width rows with room either
   side. These cards sit flush against the panel edge, so shifting one
   right pushed its right border past that edge and the line vanished —
   Wayne saw the block move and the right edge disappear, which are the
   same event.
   
   The colour change alone is enough feedback for a hover here. */
body.template-nebulous .navmain-link.navmain-icon > a {
    transition: background .14s ease, color .14s ease, border-color .14s ease;
}

body.template-nebulous .navmain-link.navmain-icon i {
    border-radius: 9px;
}

/* ---- 6. Footer badges -----------------------------------------
   Same chip treatment as everywhere else. */

body.template-nebulous .footer-badge {
    border-radius: 8px;
}

/* ---- 7. The page ground ---------------------------------------
   #f8f8f8 is a flat grey. The client area sits on a neutral with a
   faint cool bias, which makes white cards read as lifted rather than
   merged. A small change that does a lot of the work. */

body.template-nebulous {
    --background-body: #f2f3f5;
}

body.colortoggle-dark.template-nebulous {
    --background-body: #111111;
}

/* =================================================================
   PASS 3 — modernise the comparison grid, keep it a grid.

   The first attempt separated each plan into its own card with gaps
   between them. Wayne: "It's a comparison card setup they need to be
   together not alone." Correct, and it is the whole point of the
   component — the Main Information column labels the rows, and those
   labels only line up with the values if every plan shares one row
   grid. Gaps let the columns drift and the comparison stops working.

   So the whole table stays one surface. What changes is everything
   INSIDE it: the fused grey slabs become quiet columns, the rules
   between rows soften, and the plan being considered lifts instead of
   the plans being pulled apart.
   ================================================================= */

/* One card around the whole comparison, not one per plan. */
body.template-nebulous .product-tables1 {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: 12px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    /* NOT clipped. The columns now slide in by their own width like the
       reference site's plan boxes, so the frame must let them travel —
       clipping here would show an empty framed box for the second the
       animation takes, which is the same fault the feature rows had.
       The outermost columns carry their own corner radii, so the frame
       still reads as rounded without clipping to achieve it. */
    overflow: visible;
    gap: 0;
    align-items: stretch;
}

/* Columns share the surface; a hairline separates them rather than a
   border per box. */
body.template-nebulous .product-tables1-box {
    position: relative;
    flex: 1 1 0;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    border-left: 1px solid #eef0f2;
    transition: background .15s ease;
}

body.template-nebulous .product-tables1-box:first-child {
    border-left: none;
}

/* The accent rule belongs to the card, so the per-column one goes. */
body.template-nebulous .product-tables1-box::before {
    display: none;
}

/* Hovering a column tints it rather than lifting it out of the grid —
   the rows stay aligned, and the plan under the cursor still reads as
   the one being considered. */
body.template-nebulous .product-tables1-box:hover {
    background: rgba(240, 149, 70, 0.04);
}

/* Column headers: flat fills, no gradient or text shadow. */
body.template-nebulous .product-tables1-head {
    border-radius: 0;
    box-shadow: none;
    text-shadow: none;
}

/* Row rules quiet enough to read across, which is what a comparison
   asks of them. */
body.template-nebulous .product-tables1-features-list-item {
    border-color: #eef0f2;
    padding-top: 0.7rem;
    padding-bottom: 0.7rem;
}

body.template-nebulous .product-tables1-buttons .button1 {
    border-radius: 6px;
}

body.colortoggle-dark.template-nebulous .product-tables1-box {
    border-left-color: #333333;
}

body.colortoggle-dark.template-nebulous .product-tables1-box:hover {
    background: rgba(240, 149, 70, 0.07);
}

body.colortoggle-dark.template-nebulous .product-tables1-features-list-item {
    border-color: #333333;
}

/* The category hero's purchase card is the same shape of problem: a
   deliberately warm off-white (#fbfaf8) chosen against a light page,
   with no dark counterpart, which left its label and stock note at
   2.23:1 on product pages. */
body.colortoggle-dark.template-nebulous .category-hero-banner-purchase {
    background: #2f2f2f;
    border-color: rgba(255, 255, 255, 0.10);
}

/* ---- Card headings follow their own body text ----

   layout.css centres EVERY heading globally:

       h1, h2, h3, h4, h5, h6 { text-align: center; }

   That is fine for a page title or a section band, where the heading
   stands alone and centred is the theme's voice. Inside a card it is
   not: the card's body copy is left-aligned, so a centred title sits
   over a left-aligned paragraph and reads as a mistake — which is
   exactly how Wayne read it on the configure step.

   Left-aligned only where a heading has left-aligned content beneath
   it. The page H1 and the section bands keep the theme's centring,
   because there the centre is deliberate rather than inherited.

   Applies to every cart template: none of the nine sets text-align, so
   they were all inheriting the same global rule. */
body.template-nebulous .configure-choice-card h2,
body.template-nebulous .configure-choice-card h3,
body.template-nebulous .checkout-section h2,
body.template-nebulous .order-summary h2 {
    text-align: left;
}

/* The footer's two calls to action — "Get Started Today" and "More" —
   are the theme's .button1, a brand-orange gradient with a near-white
   label: 2.31:1, the same figure corrected everywhere else today.
   
   They read as fine at a glance because the FOOTER behind them is very
   dark, so the buttons stand out as shapes. What is hard to read is the
   text ON them, which is a different measurement and the one that
   matters for a call to action.
   
   The footer itself is deliberately left alone: it is scheme-
   independent by design, its headings measure 16.67:1 and its links
   12.2:1, and it already carries the brand exactly where it should. */
/* .color1 IS THE THEME'S PRIMARY BUTTON, USED IN 30 PLACES, and its
   label was still white on the brand orange at 2.04:1 — the last
   instance of the figure corrected everywhere else today.
   
   !important is required rather than chosen: layout.css declares
   `.color1 { color: var(--color-primary-foreground) !important; }`, so
   no amount of specificity beats it. Two of my own rules had already
   tried and silently lost — the footer one below, and the Nexus accent
   rule.
   
   WHY EVERY SWEEP MISSED THESE. The harness skips any element whose
   background is an image, because a gradient has no single colour to
   measure against. These buttons are gradient-filled, so all 30 were
   stepped over in every run today. The zeros reported were real for
   what was measured and silent about this. Found only because Wayne
   asked about the footer and the two CTAs there turned out to be the
   same class. */
/* WHITE LABEL ON THE SITE-WIDE BUTTON GRADIENT, matching the admin.
   Wayne, 2026-08-27: "White Text on orange buttons all consitant sexy
   and fancy".
   
   REDEFINES THE THEME'S OWN TOKENS ON THE BUTTON ITSELF rather than
   fighting layout.css. That file paints these with
   `background: linear-gradient(..., var(--color-primary-background-gradient-top) ...) !important`
   — an !important no selector of mine outranks. But the var is resolved
   AT THE ELEMENT, so redefining those two custom properties here
   changes what that !important rule draws, with no specificity contest
   at all. Same move that fixed the plan-table heading earlier today.
   
   Scoped to .color1 deliberately, NOT to :root. The theme derives a
   long list of other tokens from --color-primary-background — accent
   bars, active nav, plan-table highlight heads — and repainting that
   globally would drag every orange surface on the shop into this
   deeper shade. Only the buttons were asked for.
   
   STALE CLAIM CORRECTED 2026-08-28. This used to say both stops clear
   AA at 5.18:1 / 7.31:1. That was true of the #c2410c -> #9a3412 fill
   it was written for, and stopped being true when the tokens moved to
   the brighter brand orange: white now measures 2.31:1 at the light
   stop and 2.98:1 at the dark one.

   That is a DECISION, not a regression — Wayne asked for the bright
   orange three times on 2026-08-27, compared it against the darker
   pair, and preferred it. See the full record beside the token
   declarations in app.css. The note is corrected here only so nobody
   reads a false AA claim and "fixes" the brand colour on the strength
   of it. */
body.template-nebulous .button1.color1,
body.template-nebulous a.color1,
body.template-nebulous button.color1 {
    --color-primary-background: var(--btn-grad-from);
    --color-primary-background-gradient-top: var(--btn-grad-from);
    --color-primary-background-gradient-bottom: var(--btn-grad-to);
    --color-primary-background-border: var(--btn-grad-to);
    color: var(--btn-label, #243141) !important;
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.18),
        inset 0 1px 0 rgba(255, 255, 255, 0.22);
    transition: filter 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease;
}

body.template-nebulous .button1.color1:hover,
body.template-nebulous a.color1:hover,
body.template-nebulous button.color1:hover,
body.template-nebulous .button1.color1:focus,
body.template-nebulous a.color1:focus,
body.template-nebulous button.color1:focus {
    --color-primary-active-background: var(--btn-grad-hover-from);
    --color-primary-active-background-gradient-top: var(--btn-grad-hover-from);
    --color-primary-active-background-gradient-bottom: var(--btn-grad-hover-to);
    --color-primary-active-background-border: var(--btn-grad-hover-to);
    color: var(--btn-label, #243141) !important;
    transform: translateY(-1px);
    box-shadow:
        0 4px 10px rgba(154, 52, 18, 0.34),
        inset 0 1px 0 rgba(255, 255, 255, 0.28);
}

@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .button1.color1,
    body.template-nebulous a.color1,
    body.template-nebulous button.color1 {
        transition: none;
    }
    body.template-nebulous .button1.color1:hover,
    body.template-nebulous a.color1:hover,
    body.template-nebulous button.color1:hover {
        transform: none;
    }
}

/* ---- Plan comparison table: what the gradient blind spot was hiding ----

   Found the moment the harness learned to measure gradient fills. All
   three of these sit on gradients, so no sweep today had looked at them.

   The highlighted plan's column heading is the brand orange with a
   near-white label: 2.04:1, on the name of the plan being pushed
   hardest. Dark label, as on every other orange surface.

   The feature values sit on the table's grey band at 4.04:1 — the same
   near-miss the price suffix had, from the same #696969. */
/* Set the TOKEN, not a competing selector.
   
   The first attempt stated these two classes directly and lost: the
   theme colours them through
   `.product-tables1 .product-tables1-box.product-tables1-highlight
    .product-tables1-head .product-tables1-heading` — five classes, which
   no reasonable selector of mine beats.
   
   That rule reads --color-box-active-head-foreground, so setting the
   token gets there without a specificity contest. It applies only to
   the HIGHLIGHTED column, whose head is the brand orange; the ordinary
   columns keep --color-box-head-foreground white on their dark grey. */
body.template-nebulous {
    --color-box-active-head-foreground: #2c2d30;
}

/* And the other way in dark mode. The highlighted column's head is the
   brand orange in light mode and #1b1b1b in dark — the theme darkens it
   — so a dark label that is right on orange becomes 1.25:1 on the dark
   head. The token has to move with the scheme, like every other
   foreground paired to a background today. */
body.colortoggle-dark.template-nebulous {
    --color-box-active-head-foreground: #f1f1f1;
}

body.template-nebulous .product-tables1-features-list-item {
    color: #575757;
}

body.colortoggle-dark.template-nebulous .product-tables1-features-list-item {
    color: #b9bcc2;
}

/* ------------------------------------------------------------------
   Network status (account/network-status.php)

   The "affecting you" card is deliberately the loudest thing on the
   page: the whole reason this feature stops tickets being opened is
   that a customer can tell at a glance whether an incident is theirs.
   ------------------------------------------------------------------ */

.ca-card-alert {
    border-left: 4px solid #e07b2a;
}

.net-status-item {
    padding: 1rem 0;
    border-bottom: 1px solid var(--color-border, #e2e2e2);
}

.net-status-item:last-of-type { border-bottom: 0; }

.net-status-item h3 {
    margin: 0 0 0.4rem;
    font-size: 1.05rem;
    color: var(--color-heading);
}

.net-status-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0 0 0.6rem;
    font-size: 0.85rem;
    opacity: 0.8;
}

.net-status-badge {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: #fff;
    /* Fallback so an unrecognised status is still legible rather than
       rendering white-on-white. */
    background: #6b7280;
}

.net-status-investigating { background: #c2410c; }
.net-status-identified    { background: #b45309; }
.net-status-monitoring    { background: #1d4ed8; }
.net-status-scheduled     { background: #4f46e5; }
.net-status-resolved      { background: #15803d; }

/* Resolved entries stay readable but visibly settled. */
.net-status-resolved.net-status-item { opacity: 0.7; }

.net-status-updates {
    margin: 0.6rem 0 0;
    padding-left: 1rem;
    list-style: none;
    border-left: 2px solid var(--color-border, #e2e2e2);
}

.net-status-updates li {
    margin-bottom: 0.5rem;
    font-size: 0.9rem;
}

.net-status-updates time {
    display: block;
    font-size: 0.75rem;
    opacity: 0.65;
}

.net-status-note {
    margin: 1rem 0 0;
    font-size: 0.85rem;
    opacity: 0.75;
}

/* Dashboard outage banner. Shown only when an issue actually affects
   this customer — a banner about somebody else's server teaches people
   to ignore banners. */
.ca-outage {
    display: flex;
    gap: 0.9rem;
    align-items: flex-start;
    padding: 1rem 1.15rem;
    margin-bottom: 1.4rem;
    border: 1px solid #f0b27a;
    border-left: 4px solid #e07b2a;
    border-radius: 4px;
    background: #fdf4ea;
    /* Explicit colours, not inherited: this sits above the theme's own
       cards and must stay legible on either ground. */
    color: #4a2c10;
}

.ca-outage-icon {
    margin-top: 0.15rem;
    font-size: 1.2rem;
    color: #c2410c;
}

.ca-outage-list {
    margin: 0.4rem 0 0.6rem;
    padding-left: 1.1rem;
    font-size: 0.9rem;
}

.ca-outage a { color: #b45309; font-weight: 600; }


/* =================================================================
   CARD EDGES — APPLIED LAST, DELIBERATELY

   These four selectors are exactly as specific as the rules that set
   each component's own border further up this file
   (body.template-nebulous + one class, 0-2-0 apiece). On an equal
   specificity tie the LATER rule wins, so this has to sit at the end of
   the file or it silently loses.

   It did lose, first time: .pricing-card and .faq-list picked up the
   new edge while .nebulous-alt-block and .content-block kept the old
   #e6e8ec, because their own rules happen to be declared below where
   this block originally went. Caught by measuring all four rather than
   checking the one card in front of me.

   See --card-edge / --card-lift above for why this is a separate token
   and not a change to --color-box-background-border.
   ================================================================= */
body.template-nebulous .pricing-card,
body.template-nebulous .nebulous-alt-block,
body.template-nebulous .content-block,
body.template-nebulous .faq-list,
/* The five below were missed on the first pass and reported as "still
   not seeing any new card borders" — because the pages Wayne was
   looking at used none of the four above. Found by sweeping eight
   pages for anything card-shaped (a fill, a visible border, a real
   size) still on the old #e6e8ec edge, rather than by naming the
   components I happened to remember. */
body.template-nebulous .product-feature-card,
body.template-nebulous .kb-card,
body.template-nebulous .testimonial-card {
    border-color: var(--card-edge);
    box-shadow: var(--card-lift);
}

/* Deliberately NOT included, though the same sweep surfaced them:
   .pricing-card-buttons and .nebulous-alt-block-figure are INNER
   sections of a card — their lighter divider is what separates them
   from the card they sit in, and promoting them would draw a hard line
   through the middle of a card. .navmain-subcontainer has its own
   dropdown treatment. The hero panels (.banner-headline-panel,
   .category-hero-banner-*) sit on photography, where a pale hairline
   reads as a mistake rather than an edge.

   .cart-item-card and .order-summary were in this list for one commit
   and are deliberately OUT. The cart runs its own theme —
   body.cart-theme-nexus, a warm palette with #eaeae7 edges — and
   adding .cart-item-card here gave it the cool #ccd3dc edge while
   .order-summary sitting right beside it kept the warm one. Two
   mismatched cards in the same view, which is worse than the thing it
   was fixing. The cart theme owns the cart; leave it alone. */


/* =================================================================
   CARD GRIDS BECOME ONE CARD

   Wayne: "put all the cards in 1 card like we did with the bottom
   images and pics". The plan cards were a loose row floating on the
   page; now the row itself is a card, and the plans sit ON it.

   This is the same ground-and-cards composition as the mini-cart and
   the feature rows, one level up: the WRAPPER supplies the tinted
   ground, and the cards stay white on top of it. That is also why the
   wrapper is #f7f8fa rather than white — a white wrapper behind white
   cards gives you a big white rectangle with faint lines in it, which
   is the problem this was meant to solve, not a fix for it.

   Also declared last, for the same source-order reason as the card
   edges above.
   ================================================================= */
/* .domainchecker-framed and .tldp-panel JOIN THIS RULE rather than
   getting a frame of their own (Wayne 2026-09-02: "frame these 2
   sections like the others below them"). Two more selectors on the
   existing rule is the whole change — a second rule with the same
   values is how the two halves of a component drift apart, which the
   service strip on this site already did once. */
body.template-nebulous .pricing-cards,
body.template-nebulous .content-block-grid,
body.template-nebulous .testimonial-grid,
/* THE LEDE, INSIDE THE PANEL AND IN ITS OWN CARD (Wayne 2026-09-02:
   "can we put the text above each in the frame also with another frame
   around the text also").

   White card on the panel's #f7f8fa, which is the nesting this site
   already uses everywhere else: .content-block sits inside
   .content-block-grid exactly this way. Same tokens as .content-block
   rather than new numbers, so the two stay one system. */
body.template-nebulous .panel-lede {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 1rem 1.25rem;
    margin: 0 0 1.25rem;
}

body.template-nebulous .panel-lede > :last-child { margin-bottom: 0; }
body.template-nebulous .panel-lede p { margin: 0; }

/* THE CHIP ROW PACKS LEFT INSIDE THE FRAME.
   .domainpricing is `justify-content: space-between`, which is only
   ever fine while the chips divide evenly into full rows. They did —
   ten chips, five per row, two rows — until this frame's 24px of
   padding took the container from 668px to 620px and dropped it to
   four per row. The last row then held two, and space-between pinned
   one to each end with a hole between them.
   Scoped to the framed variant on purpose: the home page renders this
   same partial unframed and still divides evenly, and changing it
   there would spread rows that currently look right. */
body.template-nebulous .domainchecker-framed .domainpricing {
    justify-content: flex-start;
}

body.template-nebulous .domainchecker-framed,
body.template-nebulous .tldp-panel,
body.template-nebulous .domain-panel {
    /* 2.5rem below, matching .content-block-grid's own margin — the
       page's rhythm is 40px between a block and the heading under it. */
    margin-bottom: 2.5rem;
    background: #f7f8fa;
    border: 1px solid var(--card-edge);
    border-radius: 12px;
    box-shadow: var(--card-lift);
    padding: 1.5rem;
    box-sizing: border-box;
    /* Same reason as .nebulous-alt-block: the cards inside animate in
       with percentage-based slides, and the moment this wrapper gained
       a background they had something to visibly escape from. Found by
       the CSS audit, on a regression this very commit's wrapper
       introduced — the cards were fine sliding over the page, and not
       fine sliding out of a panel.

       The POPULAR ribbon is safe: it overflows its own card's corner,
       and 1.5rem of wrapper padding keeps it well inside this box. */
    overflow: hidden;
}

/* Inside the wrapper the cards no longer need to lift off the page —
   the wrapper does that for them. A shadow on both reads as mud. */
body.template-nebulous .pricing-cards .pricing-card,
body.template-nebulous .content-block-grid .content-block,
body.template-nebulous .testimonial-grid .testimonial {
    box-shadow: none;
}

body.colortoggle-dark.template-nebulous .pricing-cards,
body.colortoggle-dark.template-nebulous .content-block-grid,
body.colortoggle-dark.template-nebulous .testimonial-grid,
body.colortoggle-dark.template-nebulous .domainchecker-framed,
body.colortoggle-dark.template-nebulous .tldp-panel,
body.colortoggle-dark.template-nebulous .domain-panel {
    background: #242424;
}

@media (max-width: 767px) {
    body.template-nebulous .pricing-cards,
    body.template-nebulous .content-block-grid,
    body.template-nebulous .testimonial-grid,
    body.template-nebulous .domainchecker-framed,
    body.template-nebulous .tldp-panel,
    body.template-nebulous .domain-panel {
        padding: 1rem;
    }
}


/* =================================================================
   PLAN COMPARISON TABLE joins the card language

   The table was the last plan presentation still in the vendor's own
   palette: #525252 column heads, a #dddddd body for the highlighted
   column and a #9c9c9c edge around it. Beside plan cards with orange
   tops and #ccd3dc hairlines it read as a different product.

   SET THE TOKENS, SCOPED TO THE TABLE — do not restate the vendor's
   selectors. It paints these through chains up to five classes deep
   (see --color-box-active-head-foreground above, where stating the
   classes directly lost), and those same tokens have 47 other
   consumers in layout.css, so redefining them at :root would repaint
   components nobody asked about. Scoping to .product-tables1 gets the
   value in without a specificity contest and without the blast radius.

   Hierarchy is deliberately NOT the same as the plan cards. Every plan
   card top is orange because a catalogue treats its items equally and
   the ribbon marks the popular one. A comparison table exists to steer
   you to one column, so here orange marks THAT column and the rest take
   the light cap — the same #f1f3f5 the cart and the old card heads use.
   ================================================================= */
body.template-nebulous .product-tables1 {
    /* EVERY plan column head is orange, not just the pushed one (Wayne,
       2026-08-28: "they should all be orange"). These were briefly the
       light cap so the highlighted column could stand out on colour
       alone — but the plan cards elsewhere are all orange, and a
       category page showing one treatment above and the other below
       read as two different products.

       The highlighted column keeps its distinction without needing a
       different head colour: it carries the POPULAR ribbon and its own
       tinted body.

       The label column is NOT a plan and is deliberately left alone —
       "Main Information" is the row legend, and colouring it like a
       purchasable tier would be wrong. */
    --color-box-head-background: var(--btn-grad-from);
    --color-box-head-background-gradient-top: var(--btn-grad-from);
    --color-box-head-background-gradient-bottom: var(--btn-grad-to);
    --color-box-head-background-border: var(--btn-grad-to);
    --color-box-head-foreground: #2c2d30;
    --color-box-head-foreground-bold: #2c2d30;
    --color-box-head-foreground-shadow: transparent;
    --color-box-head-foreground-bold-shadow: transparent;

    /* the pushed column: the same gradient as the buttons and the plan
       card tops, with the dark label this theme already established for
       orange fills */
    --color-box-active-head-background: var(--btn-grad-from);
    --color-box-active-head-background-gradient-top: var(--btn-grad-from);
    --color-box-active-head-background-gradient-bottom: var(--btn-grad-to);
    --color-box-active-head-background-border: var(--btn-grad-to);
    --color-box-active-head-foreground: #2c2d30;
    --color-box-active-head-foreground-bold: #2c2d30;
    --color-box-active-head-foreground-shadow: transparent;

    /* its body becomes the ground tint the cards sit on, not a slab of
       #dddddd */
    --color-box-active-background: #f7f8fa;
    --color-box-active-background-gradient-top: #f7f8fa;
    --color-box-active-background-gradient-bottom: #f7f8fa;
    --color-box-active-background-border: var(--card-edge);
    --color-box-active-background-divider: var(--color-box-background-divider);
    --color-box-active-background-shadow: transparent;

    border-color: var(--card-edge);
    box-shadow: var(--card-lift);
}

body.template-nebulous .product-tables1 .product-tables1-box {
    border-color: var(--card-edge);
}

body.colortoggle-dark.template-nebulous .product-tables1 {
    --color-box-head-background: #262626;
    --color-box-head-background-gradient-top: #262626;
    --color-box-head-background-gradient-bottom: #262626;
    --color-box-head-foreground: #e8ecf2;
    --color-box-head-foreground-bold: #e8ecf2;
    --color-box-active-background: #242424;
    --color-box-active-background-gradient-top: #242424;
    --color-box-active-background-gradient-bottom: #242424;
}


/* =================================================================
   THE SECONDARY COLOUR JOINS THE PALETTE

   #525252 was the last vendor grey doing real visible work. One token
   family paints all of it: the secondary buttons ("Learn More" beside
   every Order Now), the hero's "Starting From" price chip, the domain
   search's www prefix and Search button, and the back-to-top control.
   A neutral mid-grey next to #243141 ink and brand orange reads as a
   leftover, which is exactly what it is.

   Repointed to the storefront's own heading ink rather than picked
   fresh, so the secondary button is the same colour as the headings
   above it instead of a fourth near-black.

   Repointing the TOKEN rather than the components is right here — the
   opposite call from the comparison table above. There the tokens had
   47 unrelated consumers and a scoped override was the only safe move;
   here every consumer is the same idea (the secondary action) and every
   one of them wants the change.

   Contrast improves as a side effect: the #f1f1f1 label measures about
   13:1 on #243141, against roughly 7:1 on the old grey.
   ================================================================= */
body.template-nebulous {
    --color-secondary-background: #243141;
    --color-secondary-background-gradient-top: #243141;
    --color-secondary-background-gradient-bottom: #243141;
    --color-secondary-background-border: #1b2532;

    /* Hover lifts rather than greys out — #686868 was a lighter grey,
       which on a navy button would have read as the button switching
       colour instead of responding. */
    --color-secondary-active-background: #2f4058;
    --color-secondary-active-background-gradient-top: #2f4058;
    --color-secondary-active-background-gradient-bottom: #2f4058;
    --color-secondary-active-background-border: #1b2532;
}


/* =================================================================
   MEGA-MENU ITEMS BECOME CARDS

   The last flat thing on the storefront, and the sweep for legacy
   greys is what surfaced it: 22 links per page carrying the vendor's
   #dddddd, invisible to a screenshot because they only paint on hover
   inside a closed dropdown.

   Same card-on-ground composition as everywhere else — the panel
   supplies the tinted ground, each destination is a card on it. A menu
   of things you can go to is a list of destinations, which is exactly
   what that composition is for.

   The hover tint is the brand orange at 6%, not the vendor's grey: the
   panel is now light, so a grey hover on a light ground is nearly
   invisible, where a warm tint reads instantly and ties to the accent.
   ================================================================= */
/* SET THE TOKENS, not `background`. The vendor paints both the panel
   and each item with token-resolved gradients, so a plain `background`
   here loses exactly the way it lost on the buttons — first attempt did
   precisely that and the items stayed #dddddd. The rest colour derives
   from --color-box-active-background; the HOVER was already overridden
   to a warm #fef5ec further up this file, which is the tint this wanted
   anyway, so only the resting state needed moving. */
body.template-nebulous {
    /* panel: the tinted ground */
    --navmain-headerdesign1-megamenu-background: #f7f8fa;
    --navmain-headerdesign1-megamenu-background-gradient-top: #f7f8fa;
    --navmain-headerdesign1-megamenu-background-gradient-bottom: #f7f8fa;
    --navmain-headerdesign2-megamenu-background: #f7f8fa;
    --navmain-headerdesign2-megamenu-background-gradient-top: #f7f8fa;
    --navmain-headerdesign2-megamenu-background-gradient-bottom: #f7f8fa;

    /* items: white cards with a hairline, on that ground */
    --navmain-headerdesign1-megamenu-button-background: #ffffff;
    --navmain-headerdesign1-megamenu-button-background-gradient-top: #ffffff;
    --navmain-headerdesign1-megamenu-button-background-gradient-bottom: #ffffff;
    --navmain-headerdesign1-megamenu-button-background-border: var(--color-box-background-divider);
    --navmain-headerdesign2-megamenu-button-background: #ffffff;
    --navmain-headerdesign2-megamenu-button-background-gradient-top: #ffffff;
    --navmain-headerdesign2-megamenu-button-background-gradient-bottom: #ffffff;
    --navmain-headerdesign2-megamenu-button-background-border: var(--color-box-background-divider);
}

body.template-nebulous .navmain-dropdown-megamenu .navmain-link.navmain-icon > a {
    transition: border-color .14s ease, background-color .14s ease, box-shadow .14s ease;
}
body.template-nebulous .navmain-dropdown-megamenu .navmain-link.navmain-icon > a:hover {
    box-shadow: 0 1px 3px rgba(16, 24, 40, 0.09);
}

body.colortoggle-dark.template-nebulous {
    --navmain-headerdesign1-megamenu-background: #242424;
    --navmain-headerdesign1-megamenu-background-gradient-top: #242424;
    --navmain-headerdesign1-megamenu-background-gradient-bottom: #242424;
    --navmain-headerdesign2-megamenu-background: #242424;
    --navmain-headerdesign2-megamenu-background-gradient-top: #242424;
    --navmain-headerdesign2-megamenu-background-gradient-bottom: #242424;
    --navmain-headerdesign1-megamenu-button-background: #2f2f2f;
    --navmain-headerdesign1-megamenu-button-background-gradient-top: #2f2f2f;
    --navmain-headerdesign1-megamenu-button-background-gradient-bottom: #2f2f2f;
    --navmain-headerdesign1-megamenu-button-background-border: #3d3d3d;
    --navmain-headerdesign2-megamenu-button-background: #2f2f2f;
    --navmain-headerdesign2-megamenu-button-background-gradient-top: #2f2f2f;
    --navmain-headerdesign2-megamenu-button-background-gradient-bottom: #2f2f2f;
    --navmain-headerdesign2-megamenu-button-background-border: #3d3d3d;
}


/* The FAQ list had a card edge and a card shadow but no fill — an
   outline drawn around nothing, which on the page ground reads exactly
   as "no background on the cards". It was picked up by the card-edge
   rule above (which sets border and shadow) without ever having had a
   surface of its own to begin with, so the edge made an existing gap
   visible rather than causing it.

   Found by sweeping for elements carrying the card border with a
   transparent background, not by looking at the FAQ. */
body.template-nebulous .faq-list {
    background: var(--color-box-background);
}
body.colortoggle-dark.template-nebulous .faq-list {
    background: #2f2f2f;
}


/* ---- Plan Specs card (product.php's fourth "Everything Included" card)
   Label left, value right, on hairline rows — the same figure treatment
   the mini-cart uses, for the same reason: a column of values you can
   compare by scanning rather than reading. */
body.template-nebulous .product-spec-list {
    margin: 0;
    padding: 0;
    /* Two columns: eight stacked rows made this card noticeably taller
       than the three beside it, and a grid row is only as short as its
       tallest card — so one long card was stretching the whole row.

       CSS grid rather than `columns`, because multi-column would break
       a label away from its own value across a column boundary. Grid
       fills row-wise, so items 1 and 2 are the tops of the two columns
       and are the only ones that must not draw a top rule. */
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 1.5rem;
}
@media (max-width: 560px) {
    body.template-nebulous .product-spec-list { grid-template-columns: 1fr; }
}
body.template-nebulous .product-spec-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.4rem 0;
}
body.template-nebulous .product-spec-row {
    border-top: 1px solid var(--color-box-background-divider);
}
body.template-nebulous .product-spec-list .product-spec-row:nth-child(-n + 2) {
    border-top: none;
}
/* Single column below the breakpoint, so only the very first row is a
   column top again. */
@media (max-width: 560px) {
    body.template-nebulous .product-spec-list .product-spec-row:nth-child(2) {
        border-top: 1px solid var(--color-box-background-divider);
    }
}
body.template-nebulous .product-spec-list dt {
    margin: 0;
    font-size: 0.85rem;
    color: var(--color-body);
}
body.template-nebulous .product-spec-list dd {
    margin: 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--color-heading);
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}


/* ---- Plan comparison columns: give the ordinary ones a surface ----
   Reported as "still no padding and background on the plan cards".
   Measured: .product-tables1-box-2 and -box-3 had background
   transparent, background-image none and a 0px border — nothing painted
   them at all. The white was the table container showing through, so
   the labels column and the highlighted column looked like cards and
   every other plan looked like a gap between them.

   VERTICAL padding is deliberately not touched. Each column is its own
   stack and the rows line up across columns only because their heights
   match; adding vertical padding to a column slides its rows out of
   step with its neighbours. Horizontal padding and a surface are safe,
   a top or bottom one is not. */
body.template-nebulous .product-tables1 .product-tables1-box {
    background: var(--color-box-background);
}
/* THE PLAN COLUMNS ARE CARDS, NOT A JOINED TABLE.

   A separator between columns was the wrong answer to "the top cards
   have no border". The plans on /shop.php and the homepage are separate
   rounded cards with space between them; this section renders the same
   products, so a butted-together table read as a different component
   for the same thing. Asked about four times before I stopped measuring
   borders and looked at what "card" meant here.

   Wrapper supplies the ground, columns sit on it as cards — the same
   composition as .pricing-cards, the mini-cart and the feature rows.

   overflow stays VISIBLE: the highlighted column's POPULAR ribbon is
   positioned across its top corner and clipping the column would cut
   the ribbon in half. The head is rounded explicitly instead. */
/* NO GAP between the columns (Wayne: "they don't need padding in
   between them"). They sit flush and are separated by a single visible
   line each — the doubled edge where two bordered cards meet is
   collapsed with a -1px pull, so every division through the table is
   exactly one pixel rather than two.

   The wrapper keeps its own border, radius and lift, so the whole
   section is still one card; it is the INSIDE that is a grid rather
   than a row of separate cards. */
body.template-nebulous .product-tables1 {
    /* FRAMED like the plan cards on /shop.php: the wrapper is a card
       with a tinted ground and padding, and the plans sit inside it.

       The gap between COLUMNS stays 0 — that is what "they don't need
       padding in between them" meant, and I wrongly took it to mean the
       frame as well, so the table lost its surround entirely.

       Columns keep their own borders and butt together inside the
       frame; the outermost two are rounded so the block reads as one
       panel rather than a strip of cells. */
    gap: 0;
    padding: 1rem;
    background: #f7f8fa;
}
body.template-nebulous .product-tables1 .product-tables1-box:first-child {
    border-radius: 9px 0 0 9px;
}
body.template-nebulous .product-tables1 .product-tables1-box:last-child {
    border-radius: 0 9px 9px 0;
}
body.colortoggle-dark.template-nebulous .product-tables1 {
    background: #242424;
}
/* A full border around EVERY column, not just a divider between them —
   so each plan is outlined as its own card while still sitting flush
   against its neighbour.

   The -1px pull collapses the doubled edge where two bordered columns
   meet, so a division reads as one pixel rather than two. Without it,
   every internal line is twice the weight of the outer one and the
   table looks drawn by two different hands. */
body.template-nebulous .product-tables1 .product-tables1-box {
    border: 1px solid var(--card-edge);
    background: var(--color-box-background);
}
body.template-nebulous .product-tables1 .product-tables1-box + .product-tables1-box {
    margin-left: -1px;
}
/* No pull-out any more: with the frame's padding restored the
   outermost columns no longer sit on top of the wrapper's border, so
   there is no doubled edge to collapse. */
body.colortoggle-dark.template-nebulous .product-tables1 {
    background: #242424;
}

/* The highlighted column keeps its own tint — it is set from
   --color-box-active-background above and must win over the plain
   surface, so it is restated here rather than left to source order. */
body.template-nebulous .product-tables1 .product-tables1-box.product-tables1-highlight {
    background: var(--color-box-active-background);
}

body.colortoggle-dark.template-nebulous .product-tables1 .product-tables1-box {
    background: #2f2f2f;
}
body.colortoggle-dark.template-nebulous .product-tables1 .product-tables1-box.product-tables1-highlight {
    background: #242424;
}


/* ---- The comparison table animates in, like everything below it ----
   Reported as "the bottom content is animating just not the ones in the
   pic": the table carried NO animation classes at all, so the plans —
   the part of the page someone is actually deciding on — were the one
   static thing between an animated hero and animated feature rows.

   A LOCAL keyframe rather than animate.css's fadeInUp, on purpose.
   animate.css translates by a percentage of the element's OWN height,
   and these columns are hundreds of pixels tall, so fadeInUp would
   throw them far outside the table before settling — the same fault
   that made the feature rows look broken mid-scroll. 16px is a rise,
   not a flight.

   WOW drives it: it caches whatever animation-name the element has,
   blanks it until the element is in view, then restores it. That works
   with any keyframe, not only animate.css's. */
@keyframes hca-rise {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: none; }
}
body.template-nebulous .hca-rise {
    animation-name: hca-rise;
    animation-duration: 0.5s;
    animation-fill-mode: both;
    animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1);
}
@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .hca-rise { animation: none; }
}


/* The hero headline panel has the same shape of problem: it paints a
   background, and its heading and body text slide in by a percentage of
   their own width. Clipping keeps that movement inside the panel.
   Reported by the CSS audit on every page carrying a hero. */
body.template-nebulous .banner-headline-panel,
body.template-nebulous .category-hero-banner-headline {
    overflow: hidden;
}


/* =================================================================
   THE SIX REAL CONTRAST FAILURES the audit surfaced once 249 copies
   of one accepted decision stopped burying them.

   Two of the original eight were the audit measuring text that is not
   rendered — the slider's pager links carry "1"/"2" for assistive tech
   while drawn as dots (font-size 0, text-indent -9999px). That was
   fixed in the checker, not here: "fixing" the CSS to satisfy it would
   have put stray numerals on the homepage.
   ================================================================= */

/* Dark text on the brand orange, exactly as the plan card tops and the
   comparison table's highlighted column already do. Near-white measured
   2.04:1 on both of these; #2c2d30 clears AA on the same fill.

   The accepted exception covers .button1.color1 — the primary BUTTON,
   which Wayne chose deliberately. These two are a discount pill and a
   status badge that happen to share its background, and no decision was
   ever made about them. Inheriting an exception by proximity is how an
   accepted trade-off quietly becomes a site-wide default. */
body.template-nebulous .billing-cycle-save,
body.template-nebulous .product-compare-badge {
    color: #2c2d30 !important;
}

/* The active top navigation item: #696969 on its #e4e6e9 active band is
   4.39:1, against a 4.5:1 bar — a near miss rather than a disaster, and
   exactly the kind that never gets found by eye. The item is also the
   one telling you where you are, so it should be the most legible in
   the bar, not the least. */
body.template-nebulous .navmain .navmain-link.navmain-active > a,
body.template-nebulous .navmain [class*="navmain-link"][class*="navmain-active"] > a,
body.template-nebulous .navmain [class*="navmain-link"][class*="navmain-active"] {
    color: #2c2d30;
}


/* =================================================================
   THE FEATURE ROW'S HALVES SLIDE IN FROM OPPOSITE SIDES — bounded.

   globalwebconcepts.net is the house style, and its equivalent rows
   (.gridboxes2-image / .gridboxes2-text) come in with fadeInLeft and
   fadeInRight. So does this site's markup. I briefly replaced both with
   a plain rise, which lost the effect entirely.

   The direction is kept; only the DISTANCE changes. animate.css slides
   from a full ±100% of the element's own width — fine on the reference
   site, where those rows are flat on the page and have nothing to slide
   out of. Here the row is a card with a background, so a 100% slide
   put the artwork 402px outside it mid-flight, and clipping the card
   instead left it empty for the 600ms the slide took. Both were
   reported as the cards looking broken.

   40px reads as the same movement, arrives from the same side, and
   cannot cross the card's edge.
   ================================================================= */
/* hca-in-left / hca-in-right removed: the bounded 40px slides they
   defined were replaced by animate.css's own full-width fadeInLeft and
   fadeInRight, to match the reference site. */
}
/* The halves use animate.css's OWN fadeInLeft / fadeInRight, untouched.

   I replaced them with a bounded 40px slide, which is why Wayne kept
   saying the animation was wrong: measured against the reference, its
   rows travel -581px on a 586px element over a full second, and mine
   moved 40px in 0.6s. Same direction, nothing like the same motion.

   The bounding existed to stop the artwork leaving its card. The right
   answer is to let it leave — the card does not clip, so the halves fly
   in across it exactly as they do on the reference site, which has no
   card at all. */
body.template-nebulous .nebulous-alt-block > .animate__animated {
    /* 1s, matching the reference exactly. */
    animation-duration: 1s;
    animation-fill-mode: both;
    animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1);
}
@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .nebulous-alt-block > .animate__animated {
        animation: none !important;
    }
}



/* Breathing room inside the plan columns.

   The buttons block had zero padding, so Order Now sat flush against
   the bottom edge of its column, and the price sat flush under the last
   feature row. Padding is added to the BLOCKS INSIDE the column rather
   than to the column itself, for two reasons: the head is a full-bleed
   orange band and insetting it would leave white margins around the
   colour, and vertical padding on the column would push its own rows
   out of step with its neighbours' — the rows line up across columns
   only because their heights match. */
body.template-nebulous .product-tables1 .product-tables1-buttons {
    padding: 0.35rem 25px 1.35rem;
}
/* NO padding on the price block.
   It has a fixed height from the vendor and overflow:hidden, so the
   0.6rem of top padding I briefly added here pushed its content from
   65px to 75px and the bottom 10px was simply cut off — five of six
   prices clipped at EVERY viewport width, which is what "prices getting
   cut off on the bottom" was.

   Breathing room below the price is added by the buttons block instead,
   which has no fixed height to overflow. */
body.template-nebulous .product-tables1 .product-tables1-price {
    overflow: visible;
}


/* The label column and row heights are left at the vendor's own
   values on purpose.

   The labels genuinely are truncated where they need two lines — the
   rows are a fixed 47px with overflow:hidden. I tried to fix that with
   a wider column (265, then 240, then 256, then fit-content) and each
   attempt broke something else: buttons wrapping, then button text
   clipped, then the column collapsing at 1200px. The row height is what
   keeps the columns aligned, and every width I picked was wrong for
   some category or some viewport.

   The truncation is a CONTENT problem wearing a CSS costume: the labels
   that overflow are the ones duplicated across groups with longer
   wording ("Dedicated vCPU (never shared)" in Key Features vs
   "Dedicated vCPU" in Main Information). Shortening those fixes the
   display without touching a layout that four attempts could not
   improve.

   The audit's clipped-content check still reports them, which is
   correct — they are still clipped, and it should keep saying so until
   the labels are shortened. */

body.template-nebulous .product-tables1 .product-tables1-box.product-tables1-box-features .product-tables1-features-list .product-tables1-features-list-item {
    justify-content: flex-start;
}


/* ---- The last 7 pixels ------------------------------------------
   "Dedicated vCPU" needs 119px and the label cell gives it 112 — the
   column is 194px, but 30px goes to the bullet indent and clientWidth
   INCLUDES that padding, which is the arithmetic I got wrong three
   times while "measuring". Seven pixels short, so it wrapped to two
   lines in a 47px row with overflow:hidden, and the second line was
   cut.

   Fixed by giving the text those pixels rather than by resizing the
   column: a slightly tighter bullet indent and a slightly smaller
   label. Both are local to the label cell, so no column width moves
   and nothing else on the page has to be re-checked.

   Deliberately not white-space:nowrap with a fit-content column — that
   was tried and collapsed the column to 0px at 1200px wide. */
body.template-nebulous .product-tables1 .product-tables1-box.product-tables1-box-features .product-tables1-features-list .product-tables1-features-list-item {
    padding-left: 24px;
    font-size: 0.94rem;

    /* The indent above is the vendor's 30px minus the 6px the label
       needed, and shrinking it also ate the gap after the bullet — the
       20px glyph sat 4px from the text and read as one jammed-together
       lump. Shrinking the glyph to 16px puts 8px back between them and
       costs the label nothing, which resizing the column would not. */
    background-size: auto 16px;
}

/* The Order Now button, sized to its own label.

   nowrap alone stopped it wrapping to two lines and immediately
   produced the other failure: the button was 96px wide, the label needs
   115px, so "Order Now" rendered as "Order No". Both symptoms are the
   same cause — the button was never wide enough for its text and was
   either breaking it or cutting it.

   width:auto lets it take the width the label needs, the block's side
   padding drops from 25px to 10px to give it that room, and a slightly
   smaller label means it fits inside even the narrowest column of a
   six-plan category. */
body.template-nebulous .product-tables1 .product-tables1-buttons {
    padding-left: 10px;
    padding-right: 10px;
}
body.template-nebulous .product-tables1 .product-tables1-buttons .button1 {
    white-space: nowrap;
    width: auto;
    max-width: 100%;
    padding: 0.5rem 0.7rem;
    font-size: 0.85rem;
}


/* =================================================================
   THE VARIED ANIMATE.CSS ENTRANCES ARE KEPT — deliberately.

   I briefly forced every entrance to one 16px rise. Wrong: the house
   style is globalwebconcepts.net, which uses the full animate.css set —
   fadeInUp, fadeInDown, fadeInLeft, fadeInRight, fadeIn and
   jackInTheBox — and this site is meant to match it.

   The reason I flattened them was real, though, so it is worth being
   precise about where it applies. A percentage-based slide is only a
   problem for an element INSIDE a card: it travels by a share of its
   own width and leaves the card while it moves. A card that animates as
   a whole unit has nothing to escape from — which is why the reference
   site has never had this problem, and why the plan cards here never
   did either.

   So: cards keep their animate.css entrances, and only the halves that
   live inside a card (the feature row's figure and text, the plan
   table's columns) are pinned to .hca-rise, where a fixed 16px cannot
   cross an edge. Those two overrides are further up this file, each
   with its own note.
   ================================================================= */
}


/* The plan columns animate at the reference's tempo — 1s, like its
   .gridboxes1-box and like the feature rows above. */
body.template-nebulous .product-tables1 .product-tables1-box.animate__animated {
    animation-duration: 1s;
    animation-fill-mode: both;
}


/* Every card and plan entrance runs for 1s, which is what
   globalwebconcepts.net uses for all of them — measured on
   /web-hosting.php, where .product-tables1-box, .gridboxes1-box and
   .gridboxes2-* are all 1s.

   Stated once here rather than per component so the tempo cannot drift
   apart again. */
body.template-nebulous .pricing-card.animate__animated,
body.template-nebulous .content-block.animate__animated,
body.template-nebulous .testimonial-card.animate__animated,
body.template-nebulous .product-feature-card.animate__animated,
body.template-nebulous .kb-card.animate__animated,
body.template-nebulous .product-tables1-box.animate__animated,
body.template-nebulous .nebulous-alt-block > .animate__animated {
    animation-duration: 1s;
    animation-fill-mode: both;
}


/* ---- Pay invoice ----
   The page had no container at all: a bare h1, two paragraphs and five
   unstyled forms, so every button sized itself to its own text and they
   stacked with no gap. A payment page is the last thing a customer sees
   before handing over money, and it read as a broken form. */
.pay-invoice { max-width: 34rem; margin: 0 auto 3rem; }
.pay-invoice-card {
    background: var(--color-box-background);
    border: 1px solid var(--card-edge);
    border-radius: var(--border-radius-default);
    box-shadow: var(--card-lift);
    padding: 1.75rem;
}
.pay-invoice-summary {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.5rem 1.25rem;
    margin: 0 0 1.5rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--card-edge);
}
.pay-invoice-summary dt { color: var(--color-body); }
.pay-invoice-summary dd { margin: 0; text-align: right; font-weight: 600; color: var(--color-heading); }
.pay-invoice-total { font-size: 1.35rem; }

/* Every method the same width, so the eye compares them rather than
   their label lengths. */
.pay-method-form { margin: 0 0 0.6rem; }
.pay-method-btn {
    display: block;
    width: 100%;
    padding: 0.8rem 1rem;
    font-size: 0.95rem;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
    border-radius: var(--border-radius-default);
    border: 1px solid var(--card-edge);
    background: var(--color-background);
    color: var(--color-heading);
    transition: background 0.15s ease, border-color 0.15s ease;
}
.pay-method-btn:hover { border-color: var(--color-primary-background); }
.pay-method-btn-primary {
    background: var(--color-primary-background);
    border-color: var(--color-primary-background);
    color: #fff;
}
.pay-method-btn-primary:hover { filter: brightness(1.05); }

.pay-invoice-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 1.25rem 0 1rem;
    color: var(--color-body);
    font-size: 0.85rem;
}
.pay-invoice-divider::before,
.pay-invoice-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--card-edge);
}
.pay-invoice-note { margin: 0 0 1rem; color: var(--color-body); font-size: 0.9rem; }
.pay-invoice-error {
    margin: 0 0 1rem;
    padding: 0.85rem 1rem;
    border-radius: var(--border-radius-default);
    border-left: 4px solid #c0392b;
    background: var(--color-background);
}
.pay-invoice-back { margin-top: 1.25rem; text-align: center; }

/* Partial credit: an amount to apply, beside the button that applies it. */
.pay-credit-form { margin: 0 0 1rem; }
.pay-credit-form label { display: block; font-weight: 600; margin-bottom: 0.45rem; color: var(--color-heading); }
.pay-credit-row { display: flex; align-items: stretch; gap: 0.5rem; }
.pay-credit-prefix {
    display: flex;
    align-items: center;
    padding: 0 0.7rem;
    border: 1px solid var(--card-edge);
    border-right: none;
    border-radius: var(--border-radius-default) 0 0 var(--border-radius-default);
    background: var(--color-background);
    color: var(--color-body);
}
.pay-credit-row input {
    flex: 1;
    min-width: 0;
    border-radius: 0 var(--border-radius-default) var(--border-radius-default) 0;
    margin-left: -0.5rem;
}
.pay-credit-row .pay-method-btn { width: auto; flex: 0 0 auto; padding-left: 1.5rem; padding-right: 1.5rem; }
.pay-invoice-credit { color: var(--color-primary-background); }
.pay-invoice-notice {
    margin: 0 0 1rem;
    padding: 0.85rem 1rem;
    border-radius: var(--border-radius-default);
    border-left: 4px solid var(--color-primary-background);
    background: var(--color-background);
}

/* Client-area filter tabs.
   Added with the invoice status filters (2026-08-30). Deliberately
   quiet — these sit above a table and must not compete with it, so the
   active one is marked by weight and a rule rather than a filled pill.
   The count is what a customer actually scans for. */
.ca-filter-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.25rem;
    align-items: center;
    margin: 0 0 1.1rem;
    padding: 0 0 0.6rem;
    border-bottom: 1px solid var(--ca-rule, #e6e8ec);
}
.ca-filter-tab {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    padding: 0.2rem 0;
    font-size: 0.92rem;
    color: var(--ca-muted, #6b7280);
    text-decoration: none;
    border-bottom: 2px solid transparent;
}
.ca-filter-tab:hover,
.ca-filter-tab:focus-visible {
    color: var(--ca-ink, #111827);
}
.ca-filter-tab.is-active {
    font-weight: 600;
    color: var(--ca-ink, #111827);
    border-bottom-color: currentColor;
}
.ca-filter-count {
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    color: var(--ca-muted, #6b7280);
}
.ca-filter-tab.is-active .ca-filter-count {
    color: inherit;
}
.ca-figure {
    font-size: 1.9rem;
    font-weight: 700;
    margin: 0.2rem 0 0.4rem;
    font-variant-numeric: tabular-nums;
}

/* Addon picker on the Configure step (2026-08-30).
   Sits above the domain choice, so it reads as part of configuring the
   plan rather than an upsell interrupting checkout. Quiet by design —
   a boxed, coloured upsell here competes with the domain decision the
   customer actually came to make. */
.configure-addons {
    margin: 0 0 1.75rem;
    padding: 0 0 1.5rem;
    border-bottom: 1px solid var(--ca-rule, #e6e8ec);
}
.configure-addons h2 {
    font-size: 1.05rem;
    margin: 0 0 0.75rem;
}
.configure-addons-saved {
    margin: 0 0 0.75rem;
    font-size: 0.9rem;
    color: var(--ca-ok, #28734e);
}
.configure-addon {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 0.4rem 0.7rem;
    align-items: baseline;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--ca-rule-soft, #f0f2f5);
}
.configure-addon:last-of-type {
    border-bottom: 0;
}
.configure-addon input {
    width: auto;
    margin: 0;
}
.configure-addon-name {
    font-weight: 600;
}
.configure-addon-price {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--ca-muted, #6b7280);
    font-size: 0.92rem;
}
.configure-addon-desc {
    grid-column: 2 / -1;
    font-size: 0.88rem;
    color: var(--ca-muted, #6b7280);
}


/* ==========================================================================
   Client-area pieces that were only ever styled in app.css
   ==========================================================================

   app.css IS NOT LOADED ON THE STOREFRONT. The Nebulous theme serves its
   own stylesheets and deliberately keeps app.css out so the two cannot
   fight (see the note in partials/header.php). Twelve client-area
   classes were nevertheless written only into app.css, so every one of
   them rendered as unstyled markup for real customers:

       ca-detail-list  ca-btn-row     ca-callout    ca-callout-value
       ca-subheading   ca-field-grid  ca-field-wide ca-ns-list
       ca-ns-index     ca-dns-table   ca-dns-digest ca-tab

   plus ca-btn-quiet, which IS in this file but selectored as
   `.ca button.ca-btn-quiet` — and no element with class "ca" exists
   anywhere in the shell, so it never matched either.

   Found 2026-08-30 when Wayne looked at his own live VPS page: the
   server details rendered as a flat stack of unstyled text. Confirmed by
   reading computed styles in the browser rather than from the source —
   the rules existed, they were simply in a file this page never loads.

   Two of these are not cosmetic. ca-callout is the root-password box,
   the one value shown once and unrecoverable afterwards; ca-dns-table
   is how customers edit their DNS. */

/* Label/value pairs. Two columns where there is room, stacked pairs on a
   phone — a fixed grid squeezes long values like an SSH command into a
   sliver. */
.ca-detail-list {
    display: grid;
    grid-template-columns: minmax(8rem, auto) 1fr;
    gap: 0.55rem 1.75rem;
    margin: 0 0 0.25rem;
    align-items: baseline;
}
.ca-detail-list dt {
    color: var(--ca-muted, #6b7280);
    font-size: 0.9rem;
}
.ca-detail-list dd {
    margin: 0;
    color: var(--ca-text, #111827);
    font-weight: 600;
    min-width: 0;
    overflow-wrap: anywhere;
}
/* The machine-readable original, kept beside the readable version so a
   support conversation can still refer to it. */
.ca-detail-note {
    display: inline-block;
    margin-left: 0.5rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.82rem;
    font-weight: 400;
    color: var(--ca-muted, #6b7280);
}
@media (max-width: 32rem) {
    .ca-detail-list { grid-template-columns: 1fr; gap: 0.15rem; }
    .ca-detail-list dd { margin-bottom: 0.6rem; }
    .ca-detail-note { display: block; margin-left: 0; }
}

/* Buttons that belong together, in a row that wraps — instead of a
   column of separate forms each sized to its own label. */
.ca-btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
}
.ca-btn-row form { margin: 0; }

/* Secondary action. Same size and shape as .ca-btn so a row of them
   lines up, but visually subordinate — the destructive and the
   reversible should not compete for the eye. Unscoped, because the
   `.ca` ancestor the existing rule waits for does not exist. */
.ca-btn-quiet,
button.ca-btn-quiet,
.ca-shell button.ca-btn-quiet {
    background: transparent;
    color: var(--color-body, #374151);
    border: 1px solid var(--color-border, #d7dbe0);
}
.ca-btn-quiet:hover,
button.ca-btn-quiet:hover,
.ca-shell button.ca-btn-quiet:hover {
    background: rgba(0, 0, 0, 0.04);
    color: var(--color-heading, #111827);
    border-color: var(--color-primary-background, #f09546);
}

/* A value shown once and never again — a root password. This carried no
   styling at all, which made the least recoverable thing on the page
   look like ordinary paragraph text. */
.ca-callout {
    margin: 1rem 0 0;
    padding: 1rem 1.15rem;
    border: 1px solid var(--color-primary-background, #f09546);
    border-left-width: 4px;
    border-radius: 6px;
    background: rgba(240, 149, 70, 0.07);
}
.ca-callout p { margin: 0 0 0.4rem; }
.ca-callout p:last-child { margin-bottom: 0; font-size: 0.9rem; color: var(--ca-muted, #6b7280); }
.ca-callout-value {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    overflow-wrap: anywhere;
    user-select: all;   /* one click selects the whole password */
}

.ca-subheading {
    margin: 1.6rem 0 0.6rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--color-heading, #111827);
}

/* Form fields laid out side by side where they fit. */
.ca-field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 0.9rem 1.25rem;
    margin: 0 0 1rem;
}
.ca-field-grid label { display: block; }
.ca-field-grid input,
.ca-field-grid select { width: 100%; }
.ca-field-wide { grid-column: 1 / -1; }

/* Nameserver rows: a small ordinal, then the field. */
.ca-ns-list {
    display: grid;
    gap: 0.6rem;
    margin: 0 0 1rem;
    padding: 0;
    list-style: none;
}
.ca-ns-list li {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.75rem;
    align-items: center;
}
.ca-ns-list input { width: 100%; }
.ca-ns-index {
    color: var(--ca-muted, #6b7280);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
}

/* DNS records. Scrolls inside itself rather than pushing the page
   sideways, which is what a wide table does on a phone. */
.ca-dns-table { width: 100%; }
.ca-dns-table input,
.ca-dns-table select { width: 100%; min-width: 0; }
.ca-dns-digest {
    word-break: break-all;
    max-width: 22rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
}

/* CLIENT-AREA TABS - the same folder tab the admin uses (2026-09-06).

   Wayne asked for one tab appearance across both areas, and folder tabs
   rather than pills. An active tab painted the same colour as the card
   below it, with no bottom border, reads as the front face of a stack;
   the underline this used to be could not say that, and left the strip
   looking like a row of links.

   The strip had no container rule at all before this, so there was no
   line for the tabs to stand on and nothing tied them to the panel.

   Tokens are the THEME's own (--color-box-background and friends), not
   the --ca-* names used elsewhere in this file: those are never defined
   anywhere and only ever resolve to their fallbacks, so a tab styled
   with them would not follow a licensee's theme. */
.ca-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 2px;
    margin: 0 0 1.25rem;
    padding: 0;
    border-bottom: 1px solid var(--color-box-background-border, #d2d2d2);
}
.ca-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.55rem 1rem;
    /* Pulls the tab onto the strip's line so the active one can cover
       it. Without this the line runs unbroken under every tab and the
       active tab never joins the card. */
    margin-bottom: -1px;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-radius: 4px 4px 0 0;
    background: var(--color-box-background-divider, #f4f4f4);
    color: var(--color-primary-background, #f09546);
    text-decoration: none;
    font-size: 0.88rem;
    font-weight: 500;
    line-height: 1.4;
}
/* Literal rather than a token: the theme has no colour for "a light
   surface, slightly darker" - the nearest, --color-box-background-shadow,
   means a shadow and would be borrowed for the wrong job. */
.ca-tab:hover {
    background: #e9e9e9;
    color: var(--color-primary-background, #f09546);
}
/* A tab strip is navigation and has to be usable from the keyboard.
   Inset so the neighbouring tab cannot clip the ring. */
.ca-tab:focus-visible {
    outline: 2px solid var(--color-primary-background, #f09546);
    outline-offset: -2px;
}
.ca-tab.is-active {
    background: var(--color-box-background, #ffffff);
    color: var(--color-heading, #111827);
    font-weight: 700;
    /* Bottom edge painted in the CARD's colour rather than removed - the
       border still takes its pixel, so dropping it would drop the label
       one pixel below its neighbours. */
    border-color: var(--color-box-background-border, #d2d2d2)
                  var(--color-box-background-border, #d2d2d2)
                  var(--color-box-background, #ffffff);
}

/* THE THEME PAINTS EVERY ANCHOR, at a specificity a plain class cannot
   reach. `body.template-nebulous a { color: inherit }` sits at the top of
   this file at (0,2,1), so `.ca-tab` at (0,1,0) - and even
   `.ca-tabs .ca-tab` at (0,2,0) - both lose it, and the tab silently
   takes the body's grey instead of the accent.

   Measured, not reasoned about: the label computed to #696969 with the
   correct rule sitting right there in the served file, which is exactly
   how this looks when it goes unnoticed.

   Only the contested declaration is restated. The rules above stay
   unprefixed on purpose, so an install running a different template still
   gets the whole layout rather than losing the tabs to a body class it
   does not have. */
body.template-nebulous .ca-tabs .ca-tab {
    color: var(--color-primary-background, #f09546);
}
body.template-nebulous .ca-tabs .ca-tab:hover {
    color: var(--color-primary-background, #f09546);
}
body.template-nebulous .ca-tabs .ca-tab.is-active {
    color: var(--color-heading, #243141);
}

/* Illustration in the corner of a card. Decorative only: it sits behind
   the content in the stacking order and is dropped entirely on narrow
   screens, where the space belongs to the details. */
.ca-card-figure {
    position: absolute;
    top: 1rem;
    right: 1.25rem;
    width: 132px;
    max-width: 34%;
    height: auto;
    opacity: 0.95;
    pointer-events: none;
    user-select: none;
}
.ca-card-has-figure { position: relative; }
.ca-card-has-figure .ca-detail-list { max-width: calc(100% - 150px); }
@media (max-width: 46rem) {
    .ca-card-figure { display: none; }
    .ca-card-has-figure .ca-detail-list { max-width: none; }
}


/* --------------------------------------------------------------------------
   Second pass: the rest of the client area
   --------------------------------------------------------------------------

   A full sweep of every class used on a page that does NOT load app.css,
   checked against the seven stylesheets the storefront actually serves.
   Most of the initial 61 suspects were false alarms and are deliberately
   left alone:

     - pay-offline, subscribe, unsubscribe, product, knowledgebase, cart
       and bundle all render through partials/header.php, which DOES load
       app.css, so their classes resolve there.
     - ca-card-main / ca-card-side are semantic hooks with no rules
       anywhere; .ca-grid already does the column split and collapses at
       6350. Left as they are rather than invented into meaning.
     - .wow and .animate__* are JavaScript and animate.css hooks.

   What follows is the remainder that genuinely rendered unstyled. */

/* Readonly value a customer is meant to copy — a referral link, a key.
   Unstyled it was an unbordered run of text that did not read as a
   field, so nothing suggested it could be selected. */
.ca-input {
    width: 100%;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--color-border, #d7dbe0);
    border-radius: 6px;
    background: var(--color-box-background, #fff);
    color: var(--ca-text, #111827);
    font-size: 0.95rem;
    font-family: inherit;
}
.ca-input[readonly] {
    background: rgba(0, 0, 0, 0.03);
    color: var(--ca-muted, #6b7280);
    cursor: text;
}
.ca-input:focus {
    outline: 2px solid var(--color-primary-background, #f09546);
    outline-offset: 1px;
    border-color: var(--color-primary-background, #f09546);
}

/* Copy inside the dashboard's promotional panel. */
.ca-promo-text {
    flex: 1 1 20rem;
    min-width: 0;
}
.ca-promo-text p { margin: 0.35rem 0 0; color: var(--ca-muted, #6b7280); }
.ca-promo-text p:first-child { margin-top: 0; }

/* An outage that affects THIS customer, not the network at large. It
   already sits inside .ca-card-alert, so this is emphasis rather than
   alarm — a second loud treatment inside a loud card reads as noise. */
.net-status-mine {
    border-left: 3px solid var(--color-primary-background, #f09546);
    padding-left: 0.9rem;
}

/* The invoice document. The surrounding .ca-card supplies the frame;
   these two were the only invoice-view-* hooks with no rules at all,
   while the other eighteen were styled. */
.invoice-view-card { max-width: 52rem; }
.invoice-view-brand {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
}

/* Opening paragraph that introduces a page. */
.lead {
    font-size: 1.05rem;
    line-height: 1.6;
    color: var(--ca-muted, #6b7280);
    max-width: 60ch;
}

/* A small muted tag beside a heading. On the site editor this carries
   the page's URL slug, so without a rule "/about" rendered at full
   heading size and weight and read as part of the title itself. (The
   name is borrowed from the admin panel's own placeholder badge, which
   is a different thing on a different stylesheet.) */
.phase-tag {
    font-size: 0.8rem;
    font-weight: 400;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: var(--ca-muted, #6b7280);
    margin-left: 0.4rem;
    vertical-align: middle;
}

/* A delete sitting beside a save. Without this the form was a block and
   dropped its button onto its own line, so the pair never lined up. */
.remove-item-form {
    display: inline-block;
    margin: 0 0 0 0.5rem;
}


/* A destructive action that is NOT the primary one.
   The theme had .ca-btn-quiet (subordinate) and nothing for
   "subordinate AND consequential", so Power off first went out styled
   the same as any secondary button. Wayne, seeing it live: "The power
   off and restart buttons went white. Was that intentional?"
   It was, and it was wrong — with Power on correctly hidden while the
   server runs, the card was left with no primary action at all and read
   as disabled. Restart carries the primary weight now; this marks the
   one action that loses unsaved work, without shouting at somebody who
   only came to reboot. */
.ca-btn-danger,
button.ca-btn-danger,
.ca-shell button.ca-btn-danger {
    background: transparent;
    color: #b91c1c;
    border: 1px solid rgba(185, 28, 28, 0.35);
}
.ca-btn-danger:hover,
button.ca-btn-danger:hover,
.ca-shell button.ca-btn-danger:hover {
    background: rgba(185, 28, 28, 0.08);
    border-color: #b91c1c;
    color: #991b1b;
}


/* ---- Client-area inline forms: fields that fill their card ----

   Two things looked wrong on the VPS page and both were real.

   A .ca-inline-form is a flex row, and its inputs carry
   flex: 1 1 200px — but where the input sits inside a <label> (which
   it does everywhere, for the accessible name), the LABEL is the flex
   item and the input's own flex value means nothing. The label was
   display:block with no growth, so the snapshot name field sat at
   204px inside a card whose plan select ran the full 826px. Same card,
   two different field widths, for no reason a reader could see.

   And .ca-subheading inherited text-align:center from a theme h3 rule,
   so "Snapshots" was centred in a card where everything else is
   left-aligned. My own rule set the size and weight and never thought
   to check the alignment it was inheriting. */
.ca-inline-form > label {
    flex: 1 1 220px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.ca-inline-form > label input,
.ca-inline-form > label select,
.ca-inline-form > label textarea {
    width: 100%;
    min-width: 0;
}

/* A short field has no business claiming the same room as a long one:
   a port or a subnet mask is a handful of characters. */
.ca-inline-form > label:has(input[name="port"]),
.ca-inline-form > label:has(input[name="subnet_size"]) {
    flex: 0 1 9rem;
}

.ca-subheading {
    text-align: left;
}


/* ---- Dropdowns that look like dropdowns ----

   Measured before changing anything: a <select> in the client area had
   the SAME border, radius, padding and background as a text input, and
   appearance:auto. The only thing marking it as a dropdown was the
   small native arrow the operating system draws, which is easy to miss
   and looks different on every platform. Wayne: "they look plain and
   it's hard to tell at first glance they are even dropdown selectors
   at all."

   Their heights disagreed too — 45px against the input's 47px — so a
   select and an input side by side never quite lined up.

   The chevron is an inline SVG data URI rather than a font icon or an
   image file: it cannot fail to load, it needs no request, and it
   inherits nothing that could go missing. A pseudo-element would have
   been the other option, but ::after does not work on a replaced
   element like <select>.

   appearance:none removes the native control, so the arrow below is
   the ONLY affordance — which is why the padding-right reserving room
   for it is not cosmetic: without it the longest option text runs
   underneath the chevron. */
.ca-shell select:not([multiple]):not([size]),
.admin-panel select:not([multiple]):not([size]),
.admin-form-panel select:not([multiple]):not([size]) {
    -webkit-appearance: none;
    appearance: none;

    /* Room for the chevron, so no option text can run under it. */
    padding-right: 2.4rem;

    background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' fill='none' stroke='%23f09546' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
    background-size: 12px 8px;

    cursor: pointer;

    /* Matches the inputs beside it, which sat 2px taller. */
    min-height: 47px;
}

.ca-shell select:not([multiple]):not([size]):hover,
.admin-panel select:not([multiple]):not([size]):hover,
.admin-form-panel select:not([multiple]):not([size]):hover {
    border-color: var(--color-primary-background, #f09546);
}

/* A select is a control, and a control has to show focus. Removing the
   native widget removes the native focus ring with it, so it is put
   back explicitly rather than left to chance. */
.ca-shell select:not([multiple]):not([size]):focus-visible,
.admin-panel select:not([multiple]):not([size]):focus-visible,
.admin-form-panel select:not([multiple]):not([size]):focus-visible {
    outline: 2px solid var(--color-primary-background, #f09546);
    outline-offset: 1px;
    border-color: var(--color-primary-background, #f09546);
}

/* Windows draws its own arrow on top of ours in older Edge/IE. */
.ca-shell select::-ms-expand,
.admin-panel select::-ms-expand,
.admin-form-panel select::-ms-expand {
    display: none;
}


/* ---- Form controls: one look, and a visible focus state ----

   Wayne: "All the input fields and dropdown lists need some site
   style."

   WHERE THE OLD LOOK CAME FROM. Nothing in this file styled a text
   input at all — the border, radius and padding were inherited from
   the purchased theme's own vendor CSS. That is why the fields looked
   generic rather than like this site: they were never given its
   colours, only its neighbours'.

   THE PART THAT IS NOT COSMETIC. There was no focus style on any form
   control in either stylesheet. Somebody navigating by keyboard could
   not see which field they were in — and on a page that can power off
   a server or delete a snapshot, that is a real problem rather than a
   polish item. Every control below gets a visible ring.

   Scoped to the client area and the admin panel rather than applied
   globally, so the storefront's own cart, checkout and domain-search
   forms keep the theme's styling and nothing that already works is
   disturbed. */

.ca-shell input[type="text"],
.ca-shell input[type="email"],
.ca-shell input[type="password"],
.ca-shell input[type="number"],
.ca-shell input[type="url"],
.ca-shell input[type="tel"],
.ca-shell input[type="search"],
.ca-shell input[type="date"],
.ca-shell textarea,
.admin-panel input[type="text"],
.admin-panel input[type="email"],
.admin-panel input[type="password"],
.admin-panel input[type="number"],
.admin-panel input[type="url"],
.admin-panel input[type="tel"],
.admin-panel input[type="search"],
.admin-panel input[type="date"],
.admin-panel textarea,
.admin-form-panel input[type="text"],
.admin-form-panel input[type="email"],
.admin-form-panel input[type="password"],
.admin-form-panel input[type="number"],
.admin-form-panel input[type="url"],
.admin-form-panel input[type="tel"],
.admin-form-panel input[type="search"],
.admin-form-panel input[type="date"],
.admin-form-panel textarea {
    border: 1px solid var(--color-border, #e6e8ec);
    border-radius: 6px;
    padding: 0.6rem 0.75rem;
    background: #fff;
    color: var(--ca-text, #111827);
    font-family: inherit;
    font-size: 0.95rem;
    line-height: 1.4;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Matches the select beside it, which sets the same minimum. */
.ca-shell input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.admin-panel input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.admin-form-panel input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
    min-height: 47px;
}

.ca-shell textarea,
.admin-panel textarea,
.admin-form-panel textarea {
    min-height: 6rem;
    resize: vertical;
}

/* A hint is not a value, and should not read as one. */
.ca-shell ::placeholder,
.admin-panel ::placeholder,
.admin-form-panel ::placeholder {
    color: var(--ca-muted, #9aa1ab);
    opacity: 1;
}

.ca-shell input:hover:not(:disabled):not([readonly]),
.ca-shell textarea:hover:not(:disabled):not([readonly]),
.ca-shell select:hover:not(:disabled),
.admin-panel input:hover:not(:disabled):not([readonly]),
.admin-panel textarea:hover:not(:disabled):not([readonly]),
.admin-form-panel input:hover:not(:disabled):not([readonly]),
.admin-form-panel textarea:hover:not(:disabled):not([readonly]) {
    border-color: #c8ccd3;
}

/*
 * THE ONE THAT MATTERS. No form control in either stylesheet had a
 * focus style, so a keyboard user could not tell which field they were
 * in — on pages that power off servers and delete snapshots. The ring
 * is the site's own orange so it reads as this site rather than as the
 * browser's default.
 */
.ca-shell input:focus,
.ca-shell textarea:focus,
.ca-shell select:focus,
.admin-panel input:focus,
.admin-panel textarea:focus,
.admin-panel select:focus,
.admin-form-panel input:focus,
.admin-form-panel textarea:focus,
.admin-form-panel select:focus {
    outline: none;
    border-color: var(--color-primary-background, #f09546);
    box-shadow: 0 0 0 3px rgba(240, 149, 70, 0.18);
}

/* Keyboard focus additionally gets a real outline, which survives a
   high-contrast mode that discards box-shadow. */
.ca-shell input:focus-visible,
.ca-shell textarea:focus-visible,
.ca-shell select:focus-visible,
.admin-panel input:focus-visible,
.admin-panel textarea:focus-visible,
.admin-form-panel input:focus-visible,
.admin-form-panel textarea:focus-visible {
    outline: 2px solid var(--color-primary-background, #f09546);
    outline-offset: 1px;
}

/* Not editable, and it should be obvious before somebody tries. */
.ca-shell input:disabled,
.ca-shell textarea:disabled,
.ca-shell select:disabled,
.admin-panel input:disabled,
.admin-panel textarea:disabled,
.admin-form-panel input:disabled,
.admin-form-panel textarea:disabled {
    background: rgba(0, 0, 0, 0.04);
    color: var(--ca-muted, #6b7280);
    cursor: not-allowed;
}

.ca-shell input[readonly],
.admin-panel input[readonly],
.admin-form-panel input[readonly] {
    background: rgba(0, 0, 0, 0.03);
}

/*
 * One line that makes every checkbox and radio on the site the brand
 * colour instead of the operating system's blue. Nothing else here
 * reaches those controls — a checkbox is not styleable the way a text
 * field is — so without it they stay the one obviously foreign element
 * on an otherwise consistent form.
 */
.ca-shell input[type="checkbox"],
.ca-shell input[type="radio"],
.admin-panel input[type="checkbox"],
.admin-panel input[type="radio"],
.admin-form-panel input[type="checkbox"],
.admin-form-panel input[type="radio"] {
    accent-color: var(--color-primary-background, #f09546);
    width: 1rem;
    height: 1rem;
    cursor: pointer;
}


/* ---- A field is as wide as its content needs, not as wide as its card ----

   Wayne: "dropdowns are also stretched unlike the text input boxes."

   A <select> in a plain form is a block element, so it took the full
   826px of the card while a text input inside a .ca-inline-form sat at
   about 220px. Same card, two field widths, and the wider one was the
   one that needed it least — a plan name is forty characters.

   This is the same lesson as the admin panels earlier tonight: the cap
   belongs on the FIELD, not on the card. Capping the card there made
   every card disagree with its neighbours; capping the field leaves the
   cards aligned and stops a single dropdown spanning a screen's width,
   which is hard to read and harder to aim at.

   Textareas keep more room because they hold prose, and a comfortable
   line length is the point of the wider cap rather than an exception
   to the rule. */
.ca-shell input,
.ca-shell select {
    max-width: 30rem;
}

/* Set back for the two that are not text fields — a checkbox already
   has an explicit width and must not inherit a text field's cap. */
.ca-shell input[type="checkbox"],
.ca-shell input[type="radio"] {
    max-width: none;
}

.ca-shell textarea {
    max-width: 46rem;
}

/* Short values get short fields: a port, a mask or a year is a handful
   of characters, and a box sized for a hostname invites the wrong
   thing to be typed into it. */
.ca-shell input[type="number"],
.ca-shell input[name="port"],
.ca-shell input[name="subnet_size"] {
    max-width: 9rem;
}

/*
 * The confirmation box under Reinstall holds one word, five letters,
 * and ran the full width of the card — which made the most destructive
 * control on the page look like an ordinary long-form field. A box
 * sized for a sentence invites one; this one wants ERASE and nothing
 * else. Wayne: "erase box under the reinstall is stretched."
 */
.ca-shell input[name="confirm_phrase"] {
    max-width: 12rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
}

/* Inside an inline row the flex basis already decides the width, so
   the cap must not fight it — it only stops the row's single field
   stretching when there is nothing beside it. */
.ca-inline-form > label {
    max-width: 30rem;
}


/* ---- Cards touching, because I broke a child selector ----

   .ca-shell-main > .ca-card carries the 1.5rem gap between cards, and
   it is a DIRECT-CHILD selector. Wrapping the content column in
   <div id="ca-shell-content"> for fragment swapping moved every card
   one level down, so that rule stopped matching and every card in the
   client area lost its spacing at once. Wayne: "Will this fix the
   padding between cards also?" — it was my regression, introduced with
   the AJAX work.

   Matching the wrapper as well rather than removing it: the wrapper is
   what makes fragment navigation possible, and a rule that names both
   keeps working whichever way the markup is nested later. */
#ca-shell-content > .ca-card {
    margin-bottom: 1.5rem;
}

#ca-shell-content > .ca-card:last-child {
    margin-bottom: 0;
}

/* ---- Fields that look like fields before you click them ----

   Wayne: "They still look unstyled until they are clicked on. No style
   when not clicked yet."

   Measured, and every rule was applying — border, padding, height,
   radius, the chevron, all present. The problem was the COLOUR: a
   #e6e8ec border is very pale, and a white field carrying it inside a
   white card has almost nothing to separate the two. The field only
   appeared once focus turned its border orange, which is exactly what
   he described.

   So the resting state gets a border with some presence and a faint
   ground of its own. Not a heavy box — the card is still the thing
   being read — but enough that an empty field is visibly a field
   rather than a gap in the page. */
.ca-shell input:not([type="checkbox"]):not([type="radio"]),
.ca-shell select,
.ca-shell textarea,
.admin-panel input:not([type="checkbox"]):not([type="radio"]),
.admin-panel select,
.admin-panel textarea,
.admin-form-panel input:not([type="checkbox"]):not([type="radio"]),
.admin-form-panel select,
.admin-form-panel textarea {
    border-color: #cfd4dc;
    background-color: #f4f6f9;
}

.ca-shell input:not([type="checkbox"]):not([type="radio"]):hover,
.ca-shell select:hover,
.ca-shell textarea:hover,
.admin-panel input:not([type="checkbox"]):not([type="radio"]):hover,
.admin-panel select:hover,
.admin-panel textarea:hover,
.admin-form-panel input:not([type="checkbox"]):not([type="radio"]):hover,
.admin-form-panel select:hover,
.admin-form-panel textarea:hover {
    border-color: #aeb6c2;
    background-color: #fff;
}

/* Focus keeps the white ground, so typing happens on a clean surface
   and the orange ring does the signalling. */
.ca-shell input:focus,
.ca-shell select:focus,
.ca-shell textarea:focus,
.admin-panel input:focus,
.admin-panel select:focus,
.admin-panel textarea:focus,
.admin-form-panel input:focus,
.admin-form-panel select:focus,
.admin-form-panel textarea:focus {
    background-color: #fff;
}


/* The resting colours above reach inputs and textareas but not
   selects: .ca-shell select scores (0,1,1) and loses to the theme's
   own select rules, while the chevron block right above it wins only
   because :not([multiple]):not([size]) lifts it to (0,3,1). Same trap
   as the field widths, one layer down — so the select's resting
   colours are stated at the SAME specificity as its chevron rather
   than relying on a weaker selector to carry them. */
.ca-shell select:not([multiple]):not([size]),
.admin-panel select:not([multiple]):not([size]),
.admin-form-panel select:not([multiple]):not([size]) {
    border-color: #cfd4dc;
    background-color: #f4f6f9;
}

.ca-shell select:not([multiple]):not([size]):hover,
.admin-panel select:not([multiple]):not([size]):hover,
.admin-form-panel select:not([multiple]):not([size]):hover {
    border-color: #aeb6c2;
    background-color: #fff;
}

.ca-shell select:not([multiple]):not([size]):focus,
.admin-panel select:not([multiple]):not([size]):focus,
.admin-form-panel select:not([multiple]):not([size]):focus {
    background-color: #fff;
}


/* ---- Vertical rhythm inside client-area cards ----

   Wayne: "Can we space this out a tiny bit. They seem crowded
   vertically."

   Measured in the Plan card before changing anything, and the spacing
   was not merely tight, it was uneven: 6px between the heading and the
   details, 4px between the details and the paragraph, then 30px before
   the form. Three different gaps in one card, none of them chosen —
   each was whatever its element's own default happened to be.

   So this sets the gaps rather than nudging them. Not large: the card
   padding is already 28px and the point is to let the parts separate,
   not to spread them out. The heading gets slightly more room than the
   rest because it introduces everything under it. */

/* The resting ground started at #fcfcfd, which measured correctly and
   could not be seen: rgb(252,252,253) is 1.1% darker than white, so
   "it doesn't shade" was an accurate report of a rule that was in fact
   applying. #f4f6f9 is 3.6% off white — still quiet enough that the
   card leads, but enough that an empty field reads as a field. A value
   verified by measurement rather than by looking is only verified
   against the wrong question. */

.ca-shell .ca-card > .ca-card-title {
    margin: 0 0 1rem;
}

.ca-shell .ca-card > p,
.ca-shell .ca-card > .ca-detail-list,
.ca-shell .ca-card > .ca-table-wrap,
.ca-shell .ca-card > form {
    margin-top: 0;
    margin-bottom: 1rem;
}

/* Nothing should push against the bottom edge of its card. */
.ca-shell .ca-card > *:last-child {
    margin-bottom: 0;
}

/* A subheading starts a new section, so it needs air above it and
   less below — it belongs to what follows, not to what came before. */
.ca-shell .ca-card > .ca-subheading {
    margin: 1.6rem 0 0.6rem;
}

.ca-shell .ca-card > .ca-subheading:first-child {
    margin-top: 0;
}

/* Label/value rows: a touch more room per row so the pairs read as
   pairs rather than as a block of text. */
.ca-shell .ca-detail-list {
    gap: 0.7rem 1.75rem;
}

/* A row of buttons is a decision point and should not sit tight
   against the field above it. */
.ca-shell .ca-card > .ca-btn-row,
.ca-shell .ca-card form > .ca-btn-row {
    margin-top: 1.25rem;
}

/* Inside a form, a label and its control are one thing; the next
   label is a different thing. */
.ca-shell .ca-card form > label {
    display: block;
    margin-bottom: 1rem;
}

.ca-shell .ca-card form > label:last-child {
    margin-bottom: 0;
}

/* --- Configure step: operating system and datacenter -----------------
 *
 * Sits beside .configure-addons and borrows its rhythm deliberately —
 * both answer "what exactly am I buying" on the same page, and two
 * blocks a few pixels out of step with each other read as a bug.
 *
 * The stylesheet is nebulous.css and not app.css BECAUSE OF THE EARLY
 * RETURN in public/partials/header.php: when a template is active it
 * hands the whole document to templates/nebulous/header.php, and the
 * app.css <link> further down that file is never reached. A rule for
 * this page written into app.css would be correct, present, and never
 * loaded.
 */
.configure-server {
    margin: 0 0 1.75rem;
    padding: 0 0 1.5rem;
    border-bottom: 1px solid var(--ca-rule, #e6e8ec);
}
.configure-server h2 {
    font-size: 1.05rem;
    margin: 0 0 0.75rem;
}
.configure-server-saved {
    margin: 0 0 0.75rem;
    font-size: 0.9rem;
    color: var(--ca-ok, #28734e);
}
.configure-server-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 1rem 1.25rem;
    margin: 0 0 1rem;
}
.configure-field {
    /* min-width:0 or a long option name in a flex/grid child pushes the
     * select past the card instead of truncating inside it. */
    min-width: 0;
}
.configure-field label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-weight: 600;
    font-size: 0.92rem;
    margin: 0 0 0.35rem;
}
.configure-field select {
    width: 100%;
    max-width: 100%;
    padding: 0.55rem 0.65rem;
    font: inherit;
    color: inherit;
    background-color: var(--ca-input-bg, #fff);
    border: 1px solid var(--ca-rule, #e6e8ec);
    border-radius: 6px;
}
.configure-field select:focus-visible {
    outline: 2px solid var(--ca-accent, #2f6fed);
    outline-offset: 1px;
}

/* --- The little "i" and its popover ---------------------------------
 *
 * NO JAVASCRIPT. The panel shows on hover, on keyboard focus, and on a
 * tap (a <button> takes focus when tapped), so the same markup works
 * for a mouse, a keyboard and a phone. A JS toggle would be a third
 * behaviour to keep in step with the other two for no gain.
 */
.hca-info {
    position: relative;
    display: inline-flex;
}
/* The real Font Awesome glyph (FA5 on this theme — fa-info-circle, NOT
 * FA6's fa-circle-info, which is absent from the vendor file and would
 * render nothing at all). It draws its own ring, so the button is a
 * bare hit area: a CSS circle around it would show two.
 *
 * SELECTOR CARRIES THE THEME CLASS ON PURPOSE. This theme paints every
 * bare <button> orange via `body.template-nebulous button` at the top
 * of this file, which is specificity (0,1,2) — so a plain .hca-info-btn
 * (0,1,0) loses and the info icon rendered as a solid orange rectangle.
 * Adding the theme class makes it (0,2,1) and wins honestly. The bare
 * class is kept alongside so the rule still applies if this markup is
 * ever used outside the theme. Note the file already solves this once
 * with !important on .cart-tab-button; specificity does the same job
 * without starting an escalation the next rule has to beat. */
.hca-info-btn,
body.template-nebulous .hca-info-btn {
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.92rem;
    line-height: 1;
    color: var(--ca-muted, #9aa1ad);
    background: transparent;
    border: 0;
    /* The theme's button rule also sets these two. Neither shows on a
     * transparent borderless button, but leaving them inherited means
     * the next person to give this a background inherits a pill shape
     * they did not ask for. */
    border-radius: 0;
    font-weight: 400;
    cursor: help;
}
.hca-info-btn:hover,
.hca-info-btn:focus-visible,
body.template-nebulous .hca-info-btn:hover,
body.template-nebulous .hca-info-btn:focus-visible {
    color: var(--ca-accent, #2f6fed);
    background: transparent;
}
.hca-info-pop {
    position: absolute;
    bottom: calc(100% + 0.5rem);
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    width: max-content;
    max-width: min(19rem, 80vw);
    padding: 0.7rem 0.8rem;
    display: none;
    font-weight: 400;
    font-size: 0.83rem;
    line-height: 1.45;
    color: var(--ca-text, #1f2430);
    background: var(--ca-input-bg, #fff);
    border: 1px solid var(--ca-rule, #e6e8ec);
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.13);
}
.hca-info:hover .hca-info-pop,
.hca-info:focus-within .hca-info-pop {
    display: block;
}
.hca-info-pop strong {
    font-weight: 700;
}
.hca-info-pop > strong:first-child {
    display: block;
    margin: 0 0 0.3rem;
}
.hca-info-note,
.hca-info-warn {
    display: block;
    margin: 0.45rem 0 0;
    padding: 0.4rem 0.5rem;
    border-radius: 5px;
    font-size: 0.79rem;
}
.hca-info-note {
    color: var(--ca-muted, #6b7280);
    background: var(--ca-rule-soft, #f4f6f9);
}
/* The one thing on this page that cannot be undone later gets the
 * only coloured panel, so it is not read as more small print. */
.hca-info-warn {
    color: #8a4b08;
    background: #fdf3e3;
    border-left: 3px solid #d98324;
}

/* On a narrow screen a centred popover overflows the viewport; pin it
 * to the field instead of to the icon. */
@media (max-width: 480px) {
    .hca-info-pop {
        left: 0;
        transform: none;
        max-width: 78vw;
    }
}

/* Licence notes under the server picker. Deliberately quiet rather than
 * alarming — this is a condition of sale a customer needs to read, not a
 * warning about something going wrong. */
.configure-licence-notes {
    margin: 0 0 1rem;
    padding: 0.7rem 0.85rem;
    background: var(--ca-rule-soft, #f4f6f9);
    border-left: 3px solid var(--ca-rule, #d7dae0);
    border-radius: 5px;
    font-size: 0.85rem;
}
.configure-licence-heading {
    margin: 0 0 0.35rem;
    font-weight: 600;
}
.configure-licence-notes ul {
    margin: 0;
    padding-left: 1.1rem;
}
.configure-licence-notes li {
    margin: 0.2rem 0;
    color: var(--ca-muted, #6b7280);
}

/* Order-summary breakdown — why this line costs what it costs.
 *
 * The item price above ALREADY includes these; they are an explanation,
 * not additional charges, so they are set quieter than the figure they
 * explain. Getting that hierarchy wrong would read as three separate
 * things being billed. */
.order-summary-breakdown {
    grid-column: 1 / -1;
    margin: 0.3rem 0 0;
    padding: 0;
    list-style: none;
    font-size: 0.78rem;
    color: var(--ca-muted, #6b7280);
}
.order-summary-breakdown li {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.1rem 0;
}
.order-summary-breakdown li span:last-child {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* --- Configure step: server tiles -----------------------------------
 *
 * The radio inputs are visually hidden but NOT display:none — a hidden
 * input cannot receive keyboard focus, and the whole point of using
 * real radios here is that arrow keys move between families and the
 * browser's own :checked state drives which version list shows. Screen
 * readers get a proper radiogroup for free.
 */
.configure-field-block {
    margin: 0 0 1.25rem;
}
.configure-field-title {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin: 0 0 0.5rem;
    font-weight: 600;
    font-size: 0.92rem;
}
.configure-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
    gap: 0.5rem;
}
.configure-tile-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.configure-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 74px;
    padding: 0.65rem 0.4rem;
    text-align: center;
    font-size: 0.78rem;
    line-height: 1.25;
    color: var(--ca-muted, #6b7280);
    background: var(--ca-input-bg, #fff);
    border: 1px solid var(--ca-rule, #e6e8ec);
    border-radius: 7px;
    cursor: pointer;
    transition: border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}
.configure-tile:hover {
    border-color: var(--ca-muted, #9aa1ad);
    color: var(--ca-text, #1f2430);
}
/* Both marks — inline SVG and icon-font glyph — are sized and coloured
 * by the same rule, so the tile does not care which it was given. */
.configure-tile-icon {
    width: 22px;
    height: 22px;
    font-size: 22px;
    line-height: 1;
    fill: currentColor;
    flex: none;
}
/* The checked and focused states live in configure-server.php's own
 * <style>, not here: the radios are no longer adjacent to their labels
 * (they sit before the whole grid so `:checked ~` can also reach the
 * version lists), so each pair has to be named per family, and the
 * family list comes from the provider rather than from this file. */
.configure-versions {
    margin: 0.75rem 0 0;
}
.configure-version-label {
    display: block;
    margin: 0 0 0.3rem;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--ca-muted, #6b7280);
}
.configure-versions select,
.configure-region-select {
    width: 100%;
    max-width: 100%;
    padding: 0.55rem 0.65rem;
    font: inherit;
    color: inherit;
    background-color: var(--ca-input-bg, #fff);
    border: 1px solid var(--ca-rule, #e6e8ec);
    border-radius: 6px;
}
.configure-versions select:focus-visible,
.configure-region-select:focus-visible {
    outline: 2px solid var(--ca-accent, #2f6fed);
    outline-offset: 1px;
}

/* --- Configure step: sequence and the way out ------------------------
 *
 * The page was three panels with three save buttons and no forward
 * action — and if a domain was already assigned there was genuinely no
 * route to checkout from it at all. Numbering says these are steps in
 * an order; the sidebar carries the way out, where the running total
 * already is.
 */
.configure-step-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35rem;
    height: 1.35rem;
    margin-right: 0.4rem;
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    /* Fallback was #2f6fed, a BLUE, beside the brand's orange token —
       copied in from a component that had its own palette. It never
       rendered (the token is always defined) but it told every later
       reader that this control was blue. Matched to what actually
       paints. */
    background: var(--color-primary-background, #f09546);
    border-radius: 50%;
    vertical-align: middle;
}
.configure-step-heading {
    margin: 1.75rem 0 0.75rem;
    font-size: 1.05rem;
}
.configure-chosen-domain {
    margin: 0.75rem 0 0.6rem;
    font-size: 0.88rem;
    color: var(--ca-muted, #6b7280);
}
.configure-continue {
    display: block;
    width: 100%;
    text-align: center;
}
.configure-continue-hint {
    margin: 0.75rem 0 0.6rem;
    padding: 0.55rem 0.7rem;
    font-size: 0.84rem;
    color: var(--ca-muted, #6b7280);
    background: var(--ca-rule-soft, #f4f6f9);
    border-radius: 5px;
}

/* Save and "next" sit together: one is the fallback, the other is what
 * the customer actually wants next. */
.configure-step-actions {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}
.configure-step-next {
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--ca-accent, #2f6fed);
    text-decoration: none;
}
.configure-step-next:hover {
    text-decoration: underline;
}
/* Anchor jumps land the heading below the sticky step bar rather than
 * underneath it. */
#configure-domain {
    scroll-margin-top: 5rem;
}

/* A hero with no screenshot keeps the banner's proportions rather than
 * collapsing to a short band. Measured: 450px with a screenshot, 354px
 * without, before this — the page below jumped up by 96px and Site Whiz
 * vanished with the column he lived in. Ten products already ship with
 * no screenshot, so this is the normal state for a sixth of the
 * catalogue, not an edge case. */
.product-hero-visual--bare {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    min-height: 20rem;
}

/* --- Plan comparison: cards keep one width across rows ---------------
 *
 * The columns share one framed surface and divide it with `flex: 1 1 0`
 * (see body.template-nebulous .product-tables1-box above) — that is the
 * design, not an accident, so it stays. The consequence is that a row
 * with fewer cards divides the same frame among fewer columns and each
 * one grows: on a five-plan category, 283px cards in the first row and
 * 377px in the second, measured.
 *
 * So the short row's FRAME narrows instead, in proportion to how many
 * columns it carries. Its columns then compute to exactly the width the
 * full row's do, the hairline separators and shared surface are
 * untouched, and the second table simply ends where its last card ends.
 *
 * The width is set inline by plan-comparison.php because it depends on
 * the row's own column count against the widest row's — a number only
 * the template knows.
 */

/* --- Product hero: the text sits on its own card ---------------------
 *
 * It was reading directly off the photograph, which is legible on the
 * dark server-rack banner and stops being legible the moment a category
 * gets a lighter one — the eyebrow is #f09546 and the lead is white at
 * 88%, both chosen for this specific image. A panel makes the block
 * independent of whatever is behind it.
 *
 * Translucent with a blur rather than opaque, so the banner still reads
 * as the background rather than being covered by a box, and it matches
 * the chips inside it, which already use exactly this treatment.
 *
 * Qualified with body.template-nebulous because the theme's own
 * .product-hero-text rule is (0,1,0) and anything less specific loses
 * to whatever else the vendor sheet asserts on this element — the same
 * trap the info icon and the plan cards both hit today.
 */
body.template-nebulous .product-hero-text {
    padding: 2rem 2.25rem;
    background: rgba(12, 16, 23, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 14px;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.28);
}

/* The chips already carry their own translucent fill, which reads as a
   second layer of frosting once the panel is behind them. Lifting them
   slightly keeps them distinct from the card without going opaque. */
body.template-nebulous .product-hero-text .product-hero-chips li {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.22);
}

/* The card supplies the breathing room the last element used to add
   itself, so the bottom margin would otherwise double up as padding. */
body.template-nebulous .product-hero-text .product-hero-actions {
    margin-bottom: 0;
}

@media (max-width: 780px) {
    body.template-nebulous .product-hero-text {
        padding: 1.5rem 1.35rem;
    }
}

/* Comparison card specs — the one thing that differs between plans.
 * Two columns of label/value so five rows stay compact enough that the
 * cards sit level with each other. */
.product-compare-specs {
    margin: 0.75rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.1rem;
}
.product-compare-spec {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--ca-rule-soft, #f0f2f5);
    font-size: 0.88rem;
}
.product-compare-spec:last-child {
    border-bottom: 0;
}
.product-compare-spec dt {
    margin: 0;
    color: var(--ca-muted, #6b7280);
}
.product-compare-spec dd {
    margin: 0;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Comparison card: spec icons and the "also included" line. */
.product-compare-spec dt {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}
.product-compare-spec-icon {
    display: inline-flex;
    width: 15px;
    height: 15px;
    flex: none;
    color: var(--color-primary-background, #f09546);
}
.product-compare-spec-icon svg {
    width: 100%;
    height: 100%;
}
/* One quiet line rather than three repeated blocks — it is the same on
 * every card by design, so it must not compete with the specs above. */
.product-compare-included {
    margin: 0.7rem 0 0;
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--ca-muted, #6b7280);
}
.product-compare-included span {
    display: block;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ca-muted, #9aa1ad);
    margin-bottom: 0.15rem;
}

/* --- Plan specs in the hero, where the screenshot used to be ---------
 *
 * A light card on the dark banner, sized like the screenshot frame it
 * replaces so Site Whiz still lines up beside it. He was already
 * gesturing at whatever sat here; this is the thing worth pointing at,
 * being the only part of the banner that differs between plans.
 *
 * EVERY RULE CARRIES body.template-nebulous. The banner sets
 * `.category-hero-banner-content p { color: #e6e9ee }` — specificity
 * (0,1,1), meant for text sitting directly on the photograph — and this
 * card is inside that container, so the heading came out near-white on
 * white and vanished. Reported as "white on white font at the top,
 * can't see". Anything less specific than (0,2,1) loses here.
 */
body.template-nebulous .product-hero-specs {
    background: #fff;
    border-radius: 16px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.38);
    padding: 0;
    width: 100%;
    max-width: 28rem;
    /* Pushed to the right edge of its column and a touch beyond, so the
       card sits closer to the banner's edge and leaves Site Whiz room
       between it and the headline panel. */
    margin-left: auto;
    margin-right: -1.25rem;
    overflow: hidden;
}

/* A tinted header band rather than a hairline: it gives the card a top
   edge that reads against the photograph behind it. */
body.template-nebulous .product-hero-specs .product-hero-specs-title {
    margin: 0;
    padding: 0.85rem 1.5rem;
    background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%);
    border-bottom: 1px solid #e3e8ef;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #3d4757;
    text-align: left;
}

body.template-nebulous .product-hero-specs dl {
    margin: 0;
    padding: 0.35rem 1.5rem 1.1rem;
    display: grid;
    gap: 0;
}

body.template-nebulous .product-hero-spec {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1.25rem;
    padding: 0.72rem 0;
    border-bottom: 1px solid #eef1f5;
}

body.template-nebulous .product-hero-spec:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}

body.template-nebulous .product-hero-spec dt {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0;
    font-size: 0.94rem;
    color: #55606f;
}

body.template-nebulous .product-hero-spec dd {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 800;
    color: #1b2230;
    font-variant-numeric: tabular-nums;
    /* NOT nowrap. Most values are short — "4", "8GB", "160GB" — but
       Location reads "Your choice of 31 locations" on production, and
       nowrap would push that straight out of a 28rem card. Right
       alignment keeps the column edge even when one value does wrap. */
    text-align: right;
    min-width: 0;
}

body.template-nebulous .product-hero-spec-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex: none;
    border-radius: 7px;
    background: rgba(240, 149, 70, 0.12);
    color: var(--color-primary-background, #f09546);
}

body.template-nebulous .product-hero-spec-icon svg {
    width: 15px;
    height: 15px;
}

@media (max-width: 780px) {
    body.template-nebulous .product-hero-specs {
        max-width: none;
        margin-left: 0;
    }
}

/* The price in the banner, where a visitor looks for it before
 * scrolling. Sized between the headline and the lead so it reads as the
 * second most important thing on the panel, which it is. */
body.template-nebulous .product-hero-price {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: 0 0 1.6rem;
    color: rgba(255, 255, 255, 0.72);
    font-size: 0.95rem;
}
body.template-nebulous .product-hero-price strong {
    font-size: 2rem;
    font-weight: 800;
    line-height: 1;
    color: var(--color-primary-background, #f09546);
    font-variant-numeric: tabular-nums;
}

/* Beside the SPEC CARD he moves in, so the pen lands on it rather than
 * on empty banner. The PNG carries transparent margin on that side, so
 * at -7rem his box overlapped the card by 9px while the pen was still
 * ~70px clear; -3rem put the pen on the card and his shoulder across
 * the Storage and Bandwidth labels. -2.5rem with the narrower card
 * clears both — and applies ONLY here, because the screenshot needs the
 * opposite. */
body.template-nebulous .product-hero-visual--specs .product-hero-mascot {
    left: -2.5rem;
}

/* "/month" beside the price — smaller and quieter than the figure, so
 * the number still reads first but is never ambiguous about the period
 * it covers. */
.product-detail-price-cycle {
    margin-left: 0.3rem;
    font-size: 0.44em;
    font-weight: 600;
    color: var(--ca-muted, #6b7280);
    letter-spacing: 0.01em;
}

/* ==========================================================================
   pay.php — choosing a payment method
   ==========================================================================

   Was a heading, a total and four identical orange buttons stacked in a
   column: nothing to recognise at a glance, and a cheque looked exactly
   as immediate as a card. These are cards with the provider's own mark,
   what the method actually does, and — where it matters — whether it
   completes now or waits on a human.

   Scoped to body.template-nebulous throughout. A cart skin restyles
   generic classes like .order-summary and button, and a bare class
   loses to body.cart-theme-* at (0,2,1); this page is reached mid-
   checkout so whichever skin is active is loaded over the top. */
body.template-nebulous .pay-page {
    max-width: 640px;
    margin: 0 auto 3rem;
}

body.template-nebulous .pay-page h1 {
    text-align: center;
    margin-bottom: 1.25rem;
}

body.template-nebulous .pay-total-card {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    margin-bottom: 2rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
}

body.template-nebulous .pay-total-label {
    font-size: 0.9rem;
    color: var(--color-body);
}

body.template-nebulous .pay-total-amount {
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--color-heading);
}

body.template-nebulous .pay-choose-heading {
    font-size: 1.05rem;
    margin: 0 0 0.85rem;
}

body.template-nebulous .pay-methods {
    display: grid;
    gap: 0.75rem;
}

body.template-nebulous .pay-method-form {
    margin: 0;
}

/* The whole card is the submit control — one target, not a card you
   click and a button you then hunt for inside it. Every button
   property is reset explicitly because the base `button` rule and the
   active cart skin both style buttons, and inheriting either turns
   this into a solid orange block again. */
body.template-nebulous .pay-method {
    display: flex;
    align-items: center;
    gap: 1rem;
    width: 100%;
    padding: 1rem 1.15rem;
    text-align: left;
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    box-shadow: none;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

body.template-nebulous .pay-method:hover,
body.template-nebulous .pay-method:focus-visible {
    border-color: var(--color-primary-background);
    box-shadow: var(--shadow);
}

body.template-nebulous .pay-method-mark {
    flex: none;
    width: 3rem;
    font-size: 2.1rem;
    line-height: 1;
    text-align: center;
    color: var(--color-heading);
}

body.template-nebulous .pay-method-body {
    flex: 1;
    min-width: 0;
}

body.template-nebulous .pay-method-name {
    display: block;
    font-weight: 700;
    font-size: 1rem;
    color: var(--color-heading);
}

body.template-nebulous .pay-method-blurb {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.83rem;
    font-weight: 400;
    line-height: 1.45;
    color: var(--color-body);
}

body.template-nebulous .pay-method-accepts {
    display: block;
    margin-top: 0.4rem;
    font-size: 1.35rem;
    line-height: 1;
    color: var(--color-body);
    opacity: 0.7;
}

body.template-nebulous .pay-method-accepts i + i {
    margin-left: 0.3rem;
}

body.template-nebulous .pay-method-go {
    flex: none;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    color: var(--color-primary-background);
}

body.template-nebulous .pay-reassure {
    margin-top: 1.5rem;
    text-align: center;
    font-size: 0.82rem;
    color: var(--color-body);
}

body.template-nebulous .pay-reassure i {
    margin-right: 0.35rem;
}

@media (max-width: 560px) {
    body.template-nebulous .pay-method {
        flex-wrap: wrap;
    }

    body.template-nebulous .pay-method-go {
        width: 100%;
        padding-left: 4rem;
    }
}

/* ==========================================================================
   checkout.php — two columns instead of one long ribbon
   ==========================================================================

   The form and the order summary were stacked in a single narrow
   column, so on any desktop the summary scrolled away the moment you
   started typing and the right half of the screen sat empty. Wayne, of
   this page and the pay page: "so is the page before it..."

   The cart already solves this with a sticky summary beside the
   content; checkout now matches it. The summary is written FIRST in
   the markup — it is what a customer checks before committing — and
   ordered to the right visually, so the reading order on a phone stays
   summary-then-form while the desktop layout puts the form under the
   cursor. */
@media (min-width: 900px) {
    body.template-nebulous .checkout-form-wrap {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 21rem;
        align-items: start;
        gap: 1.75rem;
        max-width: 1000px;
        margin: 0 auto;
    }

    body.template-nebulous .checkout-form-wrap > .order-summary {
        grid-column: 2;
        grid-row: 1;
        position: sticky;
        top: 1.5rem;
        margin-bottom: 0;
    }

    body.template-nebulous .checkout-form-wrap > form {
        grid-column: 1;
        grid-row: 1;
        min-width: 0;
    }
}

/* The provider's own fields, expanded under the method that was chosen.
   Stripe's live inside iframes Stripe serves and PayPal's inside its
   popup, so nothing here styles a card field — this is only the frame
   around them. */
body.template-nebulous .pay-inline-panel {
    margin: -0.25rem 0 0.25rem;
    padding: 1.15rem;
    background: var(--color-box-background);
    border: 1px solid var(--color-primary-background);
    border-top: none;
    border-radius: 0 0 var(--border-radius-default) var(--border-radius-default);
}

body.template-nebulous .pay-inline-panel.is-busy {
    opacity: 0.6;
    pointer-events: none;
}

body.template-nebulous .pay-inline-mount {
    min-height: 1rem;
}

body.template-nebulous .pay-inline-status {
    margin: 0.75rem 0 0;
    font-size: 0.85rem;
    color: var(--color-body);
}

body.template-nebulous .pay-inline-status.error {
    color: #b91c1c;
}

body.template-nebulous .pay-inline-submit {
    margin-top: 1rem;
    width: 100%;
}

/* When the inline form could not load, the card above it still submits
   and still redirects — so the fallback is shown rather than hidden. */
body.template-nebulous .pay-inline-panel.pay-inline-failed {
    border-color: #b91c1c;
}

/* ==========================================================================
   My Account dropdown — the client area's own sidebar, in the header
   ==========================================================================

   Was a hand-written flat list of sixteen links, no icons, no groups,
   which had drifted ten pages behind the sidebar it was meant to
   mirror. Both now render from AccountNav::groups(), so a page added to
   the client area appears here on its own.

   SPECIFICITY IS DELIBERATE. layout.css styles every submenu link with
   `.toolbarmain .toolbarmain-menu ul li li a` — (0,2,4), carrying a
   gradient, a border and its own padding. A bare `.account-menu-panel a`
   would lose to it and the panel would render as the old vendor list
   with different markup. Leading with `body.template-nebulous` puts
   these at three classes, which wins on class count regardless of how
   many elements the vendor selector names. */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu ul.account-menu-panel {
    width: 605px;
    /* Deliberately NOT setting left/right here. layout.css hides every
       toolbar submenu by parking it at left:-999em, so overriding left
       in the base rule leaves the panel permanently on screen — which
       is exactly what happened. Position belongs to the .is-open rule
       alone. */
    /* Bottom padding only. The last item row used to sit hard against
       the panel edge now that the foot row carrying "Client area" and
       Sign out has gone — nothing was left to close the list off, so it
       read as cut short rather than finished. */
    padding: 0 0 .55rem;
    background: #fff;
    /* #e3e7eb was too faint to separate a white panel from the white
       page behind it; the shadow was doing all the work of saying where
       the edge is. Darkened one step to #cdd4dc, which still reads as a
       hairline and not a frame. */
    border: 1px solid #cdd4dc;
    border-top: 3px solid var(--color-primary-background);
    border-radius: 0 0 6px 6px;
    box-shadow: 0 12px 34px rgba(45, 62, 80, .18);

    /* 26 items in five groups is taller than a laptop viewport, and the
       old flat list was short enough that nobody had to think about it.
       Capped against the window rather than a fixed pixel height, so a
       tall screen shows the lot and a short one scrolls the last group
       into reach instead of hiding it below the fold. */
    max-height: calc(100vh - 125px);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Who you are signed in as — the same identity card the client-area
   sidebar opens with, so the two read as one place. */
body.template-nebulous .account-menu-panel li.account-menu-who {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .9rem 1.1rem;
    border-bottom: 1px solid #eef1f4;
    background: #fff;
}

body.template-nebulous .account-menu-avatar {
    width: 38px;
    height: 38px;
    flex: none;
    border-radius: 50%;
    background: var(--color-primary-background);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-family-headings, inherit);
    font-size: 1.05rem;
    font-weight: 700;
}

body.template-nebulous .account-menu-id { min-width: 0; }

body.template-nebulous .account-menu-name {
    display: block;
    font-weight: 600;
    font-size: .92rem;
    color: #2d3e50;
    line-height: 1.3;
}

body.template-nebulous .account-menu-mail {
    display: block;
    font-size: .76rem;
    color: #8b98a5;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

body.template-nebulous .account-menu-panel li.account-menu-group {
    padding: .75rem 1.1rem .25rem;
    font-family: var(--font-family-headings, inherit);
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: #8b98a5;
    background: #fff;
}

/* Two columns: 26 items in one is a wall, and the panel is wide
   enough that a single column would be mostly empty space. */
body.template-nebulous .account-menu-panel li.account-menu-items {
    display: grid;
    grid-template-columns: 1fr 1fr;
    background: #fff;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-items a {
    display: flex;
    align-items: center;
    gap: .55rem;
    /* 38px rows were generous for a 26-item menu — comfortable each,
       but multiplied across thirteen row-slots it cost most of the
       height that pushed the panel past a laptop viewport. Tightened
       again on Wayne's second pass. This is the floor: the menu opens
       on CLICK rather than hover, so a row can be tighter than the ~30px
       where mis-clicks start on a hover menu, but not by much. */
    padding: .1rem 1.1rem;
    font-size: .85rem;
    color: #5b6b7a;
    background: none;
    border: none;
    border-radius: 0;
    text-transform: none;
    letter-spacing: 0;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-items a:hover {
    background: #f2f5f8;
    color: #2d3e50;
}

body.template-nebulous .account-menu-items a i {
    width: 15px;
    text-align: center;
    font-size: .82rem;
    color: #8b98a5;
    flex: none;
    margin: 0;
}

body.template-nebulous .account-menu-items a:hover i { color: var(--color-primary-background); }

body.template-nebulous .account-menu-panel li.account-menu-foot {
    padding: 0;
    border-top: 1px solid #eef1f4;
    background: #f2f5f8;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-foot a {
    display: block;
    padding: .6rem 1.1rem;
    font-size: .8rem;
    color: #5b6b7a;
    background: none;
    border: none;
    text-transform: none;
    letter-spacing: 0;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-foot a:hover {
    background: #e7ecf1;
    color: #2d3e50;
}

/* --- dark mode -----------------------------------------------------
   Reuses the site's own colortoggle-dark class rather than adding a
   second switch. Only the surfaces change: the orange accent stays put
   so the brand does not shift between modes, and body text lifts to
   #b8c2cc rather than pure white, which on a near-black panel reads as
   glare at this size. */
body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu ul.account-menu-panel {
    background: #22262b;
    border-color: #333941;
    border-top-color: var(--color-primary-background);
    box-shadow: 0 12px 34px rgba(0, 0, 0, .55);
}

body.template-nebulous.colortoggle-dark .account-menu-panel li.account-menu-who,
body.template-nebulous.colortoggle-dark .account-menu-panel li.account-menu-group,
body.template-nebulous.colortoggle-dark .account-menu-panel li.account-menu-items { background: #22262b; }

body.template-nebulous.colortoggle-dark .account-menu-panel li.account-menu-who { border-bottom-color: #333941; }
body.template-nebulous.colortoggle-dark .account-menu-name { color: #f1f4f7; }
body.template-nebulous.colortoggle-dark .account-menu-mail,
body.template-nebulous.colortoggle-dark .account-menu-panel li.account-menu-group { color: #8894a0; }

body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-items a { color: #b8c2cc; }
body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-items a:hover { background: #2a2f36; color: #f1f4f7; }
body.template-nebulous.colortoggle-dark .account-menu-items a i { color: #8894a0; }

body.template-nebulous.colortoggle-dark .account-menu-panel li.account-menu-foot { background: #2a2f36; border-top-color: #333941; }
body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-foot a { color: #b8c2cc; }
body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-foot a:hover { background: #333941; color: #f1f4f7; }

/* The greeting that replaces the tagline once someone signs in. */
body.template-nebulous .toolbar-greeting strong { font-weight: 600; color: #fff; }

@media (max-width: 700px) {
    body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu ul.account-menu-panel { width: 100%; }
    body.template-nebulous .account-menu-panel li.account-menu-items { grid-template-columns: 1fr; }
}

/* --- sign out, in the menu footer and in the bar --------------------
   The footer is the deliberate route; the bar keeps a labelled control
   for people who expect one there. Both are POST forms carrying a CSRF
   token, so neither can be styled as a plain link without the button
   reset below — layout.css assumes an <a> in this slot. */
body.template-nebulous .account-menu-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

body.template-nebulous .account-menu-signout { margin: 0; }

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-signout button {
    display: flex;
    align-items: center;
    gap: .4rem;
    padding: .6rem 1.1rem;
    background: none;
    border: none;
    border-radius: 0;
    font-size: .8rem;
    font-weight: 600;
    /* Red, and only here. Sign out is the one destructive thing in this
       menu; everything else navigates. It is not an alarm — the colour
       is doing the same job a divider would, at no extra height. */
    color: #b91c1c;
    cursor: pointer;
    text-transform: none;
    letter-spacing: 0;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-signout button:hover {
    background: #fdecec;
    color: #911616;
}

body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-signout button { color: #f08a8a; }
body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-signout button:hover { background: #3a2626; color: #ffb4b4; }

/* The bar's own control: labelled, and deliberately NOT filled. It used
   to be a solid orange square beside the solid orange account tab —
   two identical blocks, one of which ended the session. */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-logout .nav-logout-form button {
    display: flex;
    align-items: center;
    gap: .35rem;
    padding: 5px 10px;
    background: none;
    border: none;
    font-size: inherit;
    color: #9aa3ab;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-logout .nav-logout-form button:hover {
    background: none;
    color: #fff;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-logout i { margin: 0; }

/* --- the My Account button ------------------------------------------
   layout.css paints this solid black on a #1f1f1f bar, which is very
   nearly invisible — the orange block people noticed beside it was the
   LOGOUT control, not this. An outline gives it an edge without adding
   a second filled block to a bar that now also carries Sign out.

   Padding drops by 1px each way to absorb the 1px border, so the button
   stays exactly the size it was rather than growing by two pixels.
   Wayne, on an earlier sizing: "Looks a little big." */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-highlight.account-menu > a {
    background: none;
    border: 1px solid #5a5a5a;
    border-radius: 3px;
    padding: 4px 9px;
    color: #e8e8e8;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-highlight.account-menu > a:hover {
    background: none;
    border-color: var(--color-primary-background);
    color: #fff;
}

body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.toolbarmain-menu-highlight.account-menu > a { border-color: #4a4a4a; }

/* --- what is owed, beside who you are -------------------------------
   Powder red on a soft outline — the conventional pairing for an alert
   that informs rather than warns. It sits in the menu's identity row,
   not the toolbar, because it belongs to the account rather than to the
   site chrome.

   Squared to 3px, matching the account button it hangs beneath, rather
   than a rounded badge: a pill reads as a count, a squared block reads
   as a state. Wayne's call on both the colour and the shape.

   Rendered only when something is actually owed. A permanent badge
   showing zero stops being read within a week, and then the one time it
   matters it is invisible. */
body.template-nebulous .account-menu-who { position: relative; }

/* THREE BADGES, not one. What is owed, what is open, and the way
   through to the client area — the last promoted out of the foot row
   that used to carry it. Wrapped so they travel as a set and sit hard
   right of the name.

   The alert lives in the COLOUR, not in whether the badge exists. The
   old one vanished at zero, which kept a settled account calm but also
   meant it could not be found when wanted, and a disappearing badge
   makes the two beside it jump. Now the count is always there and only
   turns red when it is above zero. */
body.template-nebulous .account-menu-badges {
    margin-left: auto;
    flex: none;
    display: flex;
    align-items: center;
    gap: .3rem;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .2rem .5rem;
    /* Quiet by default: this is a count at rest, not an alarm. */
    background: #f1f4f7;
    border: 1px solid #dfe4ea;
    border-radius: 3px;
    font-size: .74rem;
    font-weight: 600;
    line-height: 1.4;
    color: #5b6b7a;
    white-space: nowrap;
    text-transform: none;
    letter-spacing: 0;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge i {
    /* .72em of a .74rem badge came out around 8px, which is below the
       size these glyphs were drawn for — the life ring worst of all,
       being a thin outline where the invoice and grid icons are solid.
       Wayne: "a little small especially the support one." Held at
       .95em: legible, and still subordinate to the number beside it,
       which is the thing being read. */
    font-size: .95em;
    opacity: .85;
    /* Keeps the three glyphs on one optical baseline despite differing
       heights — the ring is round, the invoice tall, the grid square. */
    line-height: 1;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge:hover {
    background: #e6ebf1;
    border-color: #c9d2dc;
    color: #243141;
}

/* Powder red, the shade used for soft alerts — Wayne's own pick for
   this badge. Only ever applied when the number is above zero. */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge.is-alert {
    background: #fee2e2;
    border-color: #f7b9b9;
    color: #b91c1c;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge.is-alert:hover {
    background: #fdd0d0;
    border-color: #ef9a9a;
    color: #911616;
}

/* The way out to the client area: a destination, so it takes the brand
   accent rather than an alert colour or the resting grey. */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge.is-go {
    background: var(--color-primary-background);
    border-color: var(--color-primary-background);
    color: #fff;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge.is-go:hover {
    background: var(--color-primary-active-background);
    border-color: var(--color-primary-active-background);
    color: #fff;
}

/* Dark: the same relationships, not the same values. #fee2e2 on a dark
   panel glares; a translucent wash of the same hue keeps it soft. */
body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge {
    background: rgba(255, 255, 255, .06);
    border-color: rgba(255, 255, 255, .14);
    color: #c3ccd5;
}

body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge:hover {
    background: rgba(255, 255, 255, .12);
    color: #f1f4f7;
}

body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge.is-alert {
    background: rgba(185, 28, 28, .18);
    border-color: rgba(240, 138, 138, .45);
    color: #f3a3a3;
}

body.template-nebulous.colortoggle-dark .toolbarmain .toolbarmain-menu ul li.account-menu .account-menu-badge.is-alert:hover {
    background: rgba(185, 28, 28, .3);
    color: #ffbcbc;
}

/* --- the account menu opens on CLICK, not on hover ------------------
   layout.css shows every toolbar submenu on :hover by moving it back
   from left:-999em. Fine for a three-item dropdown; wrong for 605px of
   account navigation, which a pointer merely crossing the toolbar threw
   open — and which clicking the button could not dismiss, since the
   trigger is href="javascript:void(0)".

   So the hover rule is cancelled for this one menu and .is-open, set by
   account-menu.js, becomes the only thing that opens it. Both selectors
   carry body.template-nebulous to outrank the vendor's own. */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu:hover > ul.account-menu-panel {
    left: -999em;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu.is-open > ul.account-menu-panel {
    left: auto;
    right: 0;
}

/* A caret that turns over, so the button says which state it is in. */
body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu > a::after {
    content: "";
    display: inline-block;
    margin-left: .45rem;
    border: 4px solid transparent;
    border-top-color: currentColor;
    transform: translateY(2px);
    transition: transform .12s ease;
}

body.template-nebulous .toolbarmain .toolbarmain-menu ul li.account-menu.is-open > a::after {
    transform: translateY(-2px) rotate(180deg);
}

/* -----------------------------------------------------------------
   Domain search results  (.dsr-*)
   partials/domain-results-grid.php

   One design, every place a domain is searched. Two things carry
   availability, not one: a filled rectangular badge AND the colour of
   the card's left edge, so the answer survives being read by someone
   who cannot separate green from red.

   Colours are declared as tokens on the component itself and restated
   under body.colortoggle-dark, which is this theme's real dark mode.
   Nothing here paints a colour outside those two blocks, so neither
   mode can inherit half of the other's palette.

   Selectors are body.template-nebulous-scoped (0,2,1) deliberately:
   the cart templates' own sheets load after this file and style at
   that same weight, and a bare .dsr-card would lose every equal
   specificity tie to them on source order alone. Fourth time that trap
   has cost a visible bug in this app.
   ----------------------------------------------------------------- */

body.template-nebulous .dsr {
    --dsr-card: #ffffff;
    --dsr-line: #e3e7eb;
    --dsr-ink: #243141;
    --dsr-muted: #8b98a5;
    --dsr-green: #1f9d55;
    --dsr-green-soft: #e6f6ec;
    --dsr-green-line: #bfe3cd;
    --dsr-green-deep: #d5ecdf;
    --dsr-green-text: #2f7d52;
    --dsr-red: #b91c1c;
    --dsr-red-soft: #fdeaea;
    --dsr-red-line: #f2c4c4;
    --dsr-red-deep: #f7d6d6;
    --dsr-red-text: #a04141;
    --dsr-badge-yes-ink: #ffffff;

    /* Space BELOW as well as above. .section-heading carries no top
       margin at all — it takes its breathing room from whatever sits
       above it — so results running straight into "Shop by Category"
       left the heading touching the last row of cards. 2.5rem matches
       .nebulous-slider's own bottom margin, which is this theme's
       spacing between stacked blocks. */
    margin: 1.5rem 0 2.5rem;
}

body.colortoggle-dark.template-nebulous .dsr {
    --dsr-card: #22262b;
    --dsr-line: #333941;
    --dsr-ink: #f1f4f7;
    --dsr-muted: #8894a0;
    --dsr-green: #2ecc71;
    --dsr-green-soft: #1c2f24;
    --dsr-green-line: #2f5740;
    --dsr-green-deep: #24402f;
    --dsr-green-text: #7fd4a0;
    --dsr-red: #e05a5a;
    --dsr-red-soft: #331f1f;
    --dsr-red-line: #5a3535;
    --dsr-red-deep: #402626;
    --dsr-red-text: #e39898;
    /* Dark ink on the lifted green: white on #2ecc71 fails contrast. */
    --dsr-badge-yes-ink: #08240f;
}

/* ---- badges ---- */

body.template-nebulous .dsr-badge {
    display: inline-block;
    padding: 0.16rem 0.5rem;
    border-radius: 2px;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    line-height: 1.5;
}

body.template-nebulous .dsr-badge-yes { background: var(--dsr-green); color: var(--dsr-badge-yes-ink); }
body.template-nebulous .dsr-badge-no { background: var(--dsr-red); color: #ffffff; }
body.template-nebulous .dsr-badge-warn { background: var(--dsr-muted); color: #ffffff; }

/* ---- the orange add button ---- */

body.template-nebulous .dsr-add {
    display: inline-block;
    background: var(--color-primary-background);
    color: #ffffff;
    border: none;
    border-radius: 3px;
    padding: 0.24rem 0.58rem;
    font-size: 0.74rem;
    font-weight: 700;
    line-height: 1.5;
    cursor: pointer;
    white-space: nowrap;
    text-decoration: none;
}

body.template-nebulous .dsr-add:hover { background: var(--color-primary-active-background); color: #ffffff; }
body.template-nebulous .dsr-add-lg { padding: 0.4rem 0.9rem; font-size: 0.82rem; }
body.template-nebulous .dsr-add-alt { background: #5b6b7a; }
body.template-nebulous .dsr-add-alt:hover { background: #4b5967; }
body.template-nebulous .dsr-add[disabled] { opacity: 0.55; cursor: default; }

/* ---- the exact match: one row, two tones ---- */

body.template-nebulous .dsr-top {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--dsr-green-line);
    border-radius: 6px;
    overflow: hidden;
}

body.template-nebulous .dsr-top.is-taken { border-color: var(--dsr-red-line); }

body.template-nebulous .dsr-top-left {
    background: var(--dsr-green-soft);
    padding: 0.7rem 1rem;
    flex: 1 1 auto;
    /* min-width:0 lets a long name wrap instead of forcing the row
       wider than its container; flex-wrap keeps the sub-line with it. */
    min-width: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.85rem;
}

body.template-nebulous .dsr-top-right {
    background: var(--dsr-green-deep);
    padding: 0.7rem 1rem;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.85rem;
}

body.template-nebulous .dsr-top.is-taken .dsr-top-left { background: var(--dsr-red-soft); }
body.template-nebulous .dsr-top.is-taken .dsr-top-right { background: var(--dsr-red-deep); }

body.template-nebulous .dsr-top-name {
    font-family: var(--font-heading);
    font-size: 1.35rem;
    color: var(--dsr-ink);
    line-height: 1.15;
    /* anywhere, not break-all: break only when it genuinely cannot fit,
       rather than chopping mid-word while there is still room. */
    overflow-wrap: anywhere;
}

body.template-nebulous .dsr-top-sub { font-size: 0.78rem; color: var(--dsr-green-text); min-width: 0; }
body.template-nebulous .dsr-top.is-taken .dsr-top-sub { color: var(--dsr-red-text); }

body.template-nebulous .dsr-top-form { display: flex; align-items: center; gap: 0.85rem; margin: 0; }

body.template-nebulous .dsr-top-price {
    font-family: var(--font-heading);
    font-size: 1.3rem;
    color: var(--dsr-ink);
    white-space: nowrap;
}

body.template-nebulous .dsr-top-price small { font-size: 0.64rem; color: var(--dsr-muted); font-family: inherit; }

body.template-nebulous .dsr-years { display: flex; align-items: center; gap: 0.4rem; margin: 0; font-size: 0.74rem; color: var(--dsr-muted); }
body.template-nebulous .dsr-years select { padding: 0.2rem 0.35rem; font-size: 0.78rem; margin: 0; height: auto; width: auto; }

body.template-nebulous .dsr-top-stack { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
body.template-nebulous .dsr-top-hint { font-size: 0.68rem; color: var(--dsr-red-text); }

@media (max-width: 720px) {
    /* One row cannot hold both halves on a phone. */
    body.template-nebulous .dsr-top { flex-wrap: wrap; }
    body.template-nebulous .dsr-top-left,
    body.template-nebulous .dsr-top-right { flex: 1 1 100%; }
    body.template-nebulous .dsr-top-right { justify-content: space-between; }
}

/* ---- the grid ---- */

body.template-nebulous .dsr-rowlabel {
    font-size: 0.75rem;
    color: var(--dsr-muted);
    margin: 1.1rem 0 0.6rem;
    letter-spacing: 0.03em;
}

body.template-nebulous .dsr-grid {
    display: grid;
    /* minmax(0,...), NOT a bare 1fr. A bare 1fr is minmax(AUTO,1fr) and
       a track can never fall below its content's minimum, so a card
       holding a nowrap price beside a nowrap button forces its own
       track wide and the cards with less in them absorb the shortfall.
       Measured: taken cards squashed to 84px while the rest sat at
       136px, and their names broke across two lines. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.7rem;
}

@media (max-width: 900px) { body.template-nebulous .dsr-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 620px) { body.template-nebulous .dsr-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

body.template-nebulous .dsr-card {
    background: var(--dsr-card);
    border: 1px solid var(--dsr-line);
    border-left: 3px solid var(--dsr-green);
    border-radius: 6px;
    padding: 0.7rem 0.8rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

body.template-nebulous .dsr-card.is-taken { border-left-color: var(--dsr-red); opacity: 0.6; }

body.template-nebulous .dsr-card-name {
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--dsr-ink);
    line-height: 1.3;
    overflow-wrap: anywhere;
}

body.template-nebulous .dsr-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    /* wrap is the safety net: on the narrowest card the button drops
       under the price rather than pushing out through the card edge.
       margin-top:auto keeps the row on the floor of a taller card. */
    row-gap: 0.3rem;
    flex-wrap: wrap;
    margin-top: auto;
    padding-top: 0.15rem;
}

body.template-nebulous .dsr-card-foot form { margin: 0; }

body.template-nebulous .dsr-card-price { font-family: var(--font-heading); font-size: 1rem; color: var(--dsr-ink); }
body.template-nebulous .dsr-card-price small { font-size: 0.64rem; color: var(--dsr-muted); font-family: inherit; }
body.template-nebulous .dsr-card-price-none { color: var(--dsr-muted); }

/* ---- states ---- */

body.template-nebulous .dsr-live {
    /* outline:none is safe here only because this container is focused
       programmatically after a search, never reached by tabbing — it is
       a scroll target, not a control. */
    outline: none;
    /* Keeps the results off the very edge of the window when scrolled
       to. Nothing is sticky on this theme, so this is breathing room
       rather than clearance for a fixed header. */
    scroll-margin-top: 1rem;
}
body.template-nebulous .dsr-live.dsr-loading { opacity: 0.5; transition: opacity 0.12s ease; }

body.template-nebulous .dsr-empty,
body.template-nebulous .dsr-error { margin: 1.5rem 0 0; font-size: 0.9rem; }
body.template-nebulous .dsr-error { color: var(--dsr-red); }

/* ---- the name spinner ---- */

/* Sits in the checker's right-hand column, directly under the price
   chips — Wayne: "Put that under the chips". Full width of that column,
   so it lines up with the chip grid above it rather than floating. */
body.template-nebulous .dsr-spinner-panel { margin-top: 1rem; width: 100%; }
body.template-nebulous .dsr-spinner-toggle { width: 100%; text-align: left; }

/* SLATE TINT, NOT ORANGE. Orange is this storefront's BUY signal —
   every Add and Add to cart wears it — and this button buys nothing;
   dressing a helper in the purchase colour teaches people to trust
   that colour less. Slate is already the palette's heading ink, so a
   wash of it reads as part of the system rather than a new accent.
   Tinted rather than solid so it settles under ten pale chips without
   outweighing them: enough fill to be a different kind of thing than
   the cards above, not so much that it becomes the loudest object in
   the column. Wayne chose this over the solid version, both modes. */
body.template-nebulous .dsr-spinner-toggle {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    background: #eef1f5;
    border: 1px solid #c3ccd6;
    border-radius: 4px;
    padding: 0.62rem 0.9rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: #243141;
    cursor: pointer;
}

/* Pushes the arrow to the far edge. The button spans the column, so
   "the end" of it is the right edge — that is where a disclosure arrow
   is looked for, and tucking it against the last word would leave it
   stranded mid-button. */
body.template-nebulous .dsr-spinner-label-text { margin-right: auto; text-align: left; }

body.template-nebulous .dsr-spinner-chevron {
    flex: none;
    transition: transform 0.15s ease;
}

/* Driven by aria-expanded, so the arrow cannot disagree with what a
   screen reader is being told. */
body.template-nebulous .dsr-spinner-toggle[aria-expanded="true"] .dsr-spinner-chevron {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .dsr-spinner-chevron { transition: none; }
}

/* THE BACKGROUND HAS TO BE SET HERE TOO. The theme paints every
   button's hover with `body.template-nebulous button:hover { background:
   var(--color-primary-active-background) }` — orange. This rule used to
   set only the border and the text, so hovering gave orange text on an
   orange ground and the label vanished. Wayne: "Cant see on hover."
   Filling it deliberately, with white text, rather than fighting the
   theme for a transparent background it is going to paint anyway. */
/* Deepens on hover; it does NOT go orange. The theme paints every
   button:hover with var(--color-primary-active-background), and this
   rule carries more classes so it wins — which is the only reason the
   colour above survives being hovered at all. Setting the background
   explicitly is also what fixes the original fault: the previous rule
   changed only the text, so the theme's orange landed underneath
   orange ink and the label vanished. */
body.template-nebulous .dsr-spinner-toggle:hover,
body.template-nebulous .dsr-spinner-toggle:focus-visible {
    background: #e2e7ee;
    border-color: #9aa5b1;
    color: #243141;
}

body.template-nebulous .dsr-spinner-toggle:focus-visible {
    outline: 2px solid var(--color-primary-background);
    outline-offset: 2px;
}

/* Dark mode inverts the relationship rather than the colour: on a dark
   ground the tint has to sit ABOVE the surface, not below it. A light
   wash here would glare, and the light border would be the brightest
   thing on the page. */
body.colortoggle-dark.template-nebulous .dsr-spinner-toggle {
    background: #262f3a;
    border-color: #44596f;
    color: #f1f4f7;
}

body.colortoggle-dark.template-nebulous .dsr-spinner-toggle:hover,
body.colortoggle-dark.template-nebulous .dsr-spinner-toggle:focus-visible {
    background: #33465c;
    border-color: #56697f;
    color: #f1f4f7;
}

body.template-nebulous .dsr-spinner-body {
    margin-top: 0.7rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--color-border);
    border-radius: 6px;
}

body.template-nebulous .dsr-spinner-label { display: block; font-size: 0.82rem; margin-bottom: 0.6rem; }
body.template-nebulous .dsr-spinner-body textarea { width: 100%; margin-top: 0.3rem; }

/* -----------------------------------------------------------------
   Domain pricing page  (.tldp-*)
   public/domain-pricing.php

   A price list for several hundred extensions, which is a hard thing
   to make anyone want to read. The idea carrying the design: PRICE IS
   THE COLOUR. Every card wears a band on its top edge whose hue says
   what tier it is in, so the cheap end of a wall of names is visible
   before a single figure is read, and the filter chips use the same
   four hues so the control and the thing it controls are obviously the
   same system.

   That is also why there are no invented categories. Price is data we
   hold; "this is a creative TLD" would be a guess dressed up as
   structure. See the note at the top of domain-pricing.php.

   Scoped to body.template-nebulous (0,2,1) for the same reason
   everything else here is: the cart template sheets load after this
   file and style at that same weight, so a bare class loses every
   equal-specificity tie on source order alone.
   ----------------------------------------------------------------- */

body.template-nebulous .tldp-hero,
body.template-nebulous .tldp-controls,
body.template-nebulous .tldp-grid,
body.template-nebulous .tldp-pop,
body.template-nebulous .tldp-foot {
    --tldp-a: #1f9d55;   /* under $20  */
    --tldp-b: #2f7fb5;   /* $20 to $40 */
    --tldp-c: #d98324;   /* $40 to $100 */
    --tldp-d: #a8447a;   /* $100 and up */
    --tldp-card: #ffffff;
    --tldp-line: #e3e7eb;
    --tldp-ink: #243141;
    --tldp-muted: #7d8894;
}

body.colortoggle-dark.template-nebulous .tldp-hero,
body.colortoggle-dark.template-nebulous .tldp-controls,
body.colortoggle-dark.template-nebulous .tldp-grid,
body.colortoggle-dark.template-nebulous .tldp-pop,
body.colortoggle-dark.template-nebulous .tldp-foot {
    /* Lifted, not inverted: the same four hues have to stay
       distinguishable from each other on a dark ground, which the light
       versions do not. */
    --tldp-a: #35c47c;
    --tldp-b: #56a8dd;
    --tldp-c: #eda44a;
    --tldp-d: #d97ab0;
    --tldp-card: #22262b;
    --tldp-line: #333941;
    --tldp-ink: #f1f4f7;
    --tldp-muted: #8894a0;
}

/* ---- hero ---- */

body.template-nebulous .tldp-hero {
    /* A quiet band rather than a picture. The page's own subject is a
       list of numbers; a stock photograph of a laptop would be saying
       nothing and taking a screenful to say it. */
    background: linear-gradient(180deg, #243141 0%, #2d3e50 100%);
    border-radius: 10px;
    padding: 2.4rem 2rem;
    margin: 1.5rem 0 2.5rem;
}

body.template-nebulous .tldp-hero-inner { max-width: 62ch; }

body.template-nebulous .tldp-eyebrow {
    font-family: var(--font-heading);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-primary-background);
    margin: 0 0 0.5rem;
}

body.template-nebulous .tldp-hero h1 {
    color: #ffffff;
    margin: 0 0 0.6rem;
    text-wrap: balance;
    /* The theme centres bare h1s. Everything else in this band is
       ragged-left, and a centred heading over left-aligned body copy
       reads as a mistake rather than a choice. */
    text-align: left;
}

body.template-nebulous .tldp-lede { color: #c3ccd5; margin: 0; line-height: 1.65; }
body.template-nebulous .tldp-lede strong { color: #ffffff; }

body.template-nebulous .tldp-hero-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 1.75rem;
    margin-top: 1.5rem;
    font-size: 0.85rem;
    color: #9dabb8;
}

body.template-nebulous .tldp-stat-n {
    display: block;
    font-family: var(--font-heading);
    font-size: 1.7rem;
    font-weight: 700;
    color: #ffffff;
    line-height: 1.1;
}

/* ---- the popular row ---- */

body.template-nebulous .tldp-section-note {
    color: var(--tldp-muted);
    font-size: 0.9rem;
    max-width: 70ch;
    margin: 0 0 1.1rem;
}

body.template-nebulous .tldp-pop {
    display: grid;
    /* minmax(0,...) throughout this file, never a bare 1fr: 1fr is
       minmax(AUTO,1fr) and a track can never fall below its content's
       minimum, so one long name drags its whole column wide and the
       rest of the row absorbs the loss. */
    grid-template-columns: repeat(auto-fill, minmax(min(9.5rem, 100%), 1fr));
    gap: 0.8rem;
    margin-bottom: 3rem;
}

body.template-nebulous .tldp-pop-card {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 1.1rem 1rem;
    background: var(--tldp-card);
    border: 1px solid var(--tldp-line);
    border-radius: 8px;
    text-decoration: none;
    transition: transform 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease;
}

body.template-nebulous .tldp-pop-card:hover {
    transform: translateY(-2px);
    border-color: var(--color-primary-background);
    box-shadow: 0 6px 18px rgba(36, 49, 65, 0.12);
    text-decoration: none;
}

body.template-nebulous .tldp-pop-tld {
    font-family: var(--font-heading);
    font-size: 1.7rem;
    font-weight: 700;
    color: var(--tldp-ink);
    line-height: 1.1;
    overflow-wrap: anywhere;
}

body.template-nebulous .tldp-pop-price { font-size: 0.9rem; color: var(--tldp-muted); }
body.template-nebulous .tldp-pop-price small { font-size: 0.8em; }

/* ---- controls ---- */

body.template-nebulous .tldp-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.8rem;
    margin-bottom: 1rem;
}

body.template-nebulous .tldp-search { flex: 1 1 20rem; min-width: 0; }

body.template-nebulous .tldp-search input {
    width: 100%;
    margin: 0;
    padding: 0.65rem 0.85rem;
    font-size: 0.95rem;
    border: 1px solid var(--tldp-line);
    border-radius: 6px;
    background: var(--tldp-card);
    color: var(--tldp-ink);
}

body.template-nebulous .tldp-search input:focus {
    outline: 2px solid var(--color-primary-background);
    outline-offset: 1px;
    border-color: transparent;
}

body.template-nebulous .tldp-bands { display: flex; flex-wrap: wrap; gap: 0.4rem; }

body.template-nebulous .tldp-band {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.8rem;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--tldp-ink);
    background: var(--tldp-card);
    border: 1px solid var(--tldp-line);
    border-radius: 999px;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}

body.template-nebulous .tldp-band:hover { border-color: var(--color-primary-background); }

body.template-nebulous .tldp-band.is-on {
    background: var(--color-primary-background);
    border-color: var(--color-primary-background);
    color: #ffffff;
}

body.template-nebulous .tldp-band-n {
    font-size: 0.72rem;
    color: var(--tldp-muted);
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .tldp-band.is-on .tldp-band-n { color: rgba(255, 255, 255, 0.85); }

/* Each chip wears the hue of the band it selects, so the control and
   the cards it filters read as one system rather than two. */
body.template-nebulous .tldp-band[data-band="a"] { border-left: 3px solid var(--tldp-a); }
body.template-nebulous .tldp-band[data-band="b"] { border-left: 3px solid var(--tldp-b); }
body.template-nebulous .tldp-band[data-band="c"] { border-left: 3px solid var(--tldp-c); }
body.template-nebulous .tldp-band[data-band="d"] { border-left: 3px solid var(--tldp-d); }

body.template-nebulous .tldp-count {
    font-size: 0.82rem;
    color: var(--tldp-muted);
    margin: 0 0 1rem;
    font-variant-numeric: tabular-nums;
}

/* ---- the grid ---- */

body.template-nebulous .tldp-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(7.5rem, 100%), 1fr));
    gap: 0.55rem;

    /* NO BOTTOM MARGIN. It had 2.5rem for one commit, to clear the
       "What You Get" heading below it (Wayne: "What you get is too
       close to the domain chips") — correct while the grid was the
       last thing on the page ground, and wrong the moment .tldp-panel
       framed it, because the gap then fell INSIDE the frame: measured
       65px of empty panel under the last row of chips. The panel's own
       24px padding is the inner gap now, and the panel carries the
       outer one. */
    margin-bottom: 0;
}

body.template-nebulous .tldp-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: 0.85rem 0.75rem 0.7rem;
    background: var(--tldp-card);
    border: 1px solid var(--tldp-line);
    border-top: 3px solid var(--tldp-band-hue, var(--tldp-line));
    border-radius: 6px;
    text-decoration: none;
    overflow: hidden;
    transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}

body.template-nebulous .tldp-band-a { --tldp-band-hue: var(--tldp-a); }
body.template-nebulous .tldp-band-b { --tldp-band-hue: var(--tldp-b); }
body.template-nebulous .tldp-band-c { --tldp-band-hue: var(--tldp-c); }
body.template-nebulous .tldp-band-d { --tldp-band-hue: var(--tldp-d); }

body.template-nebulous .tldp-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(36, 49, 65, 0.13);
    border-left-color: var(--tldp-band-hue);
    border-right-color: var(--tldp-band-hue);
    text-decoration: none;
}

body.template-nebulous .tldp-tld {
    font-family: var(--font-heading);
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--tldp-ink);
    line-height: 1.15;
    overflow-wrap: anywhere;
}

body.template-nebulous .tldp-price {
    font-size: 0.85rem;
    color: var(--tldp-muted);
    font-variant-numeric: tabular-nums;
}

/* "Search" appears on hover only — 580 copies of the word is noise,
   but the affordance should be there when a card is the one being
   considered. Hidden from assistive tech, which already has the link. */
body.template-nebulous .tldp-go {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-primary-background);
    opacity: 0;
    transform: translateY(3px);
    transition: opacity 0.12s ease, transform 0.12s ease;
}

body.template-nebulous .tldp-card:hover .tldp-go,
body.template-nebulous .tldp-card:focus-visible .tldp-go { opacity: 1; transform: none; }

body.template-nebulous .tldp-card:focus-visible {
    outline: 2px solid var(--color-primary-background);
    outline-offset: 2px;
}

body.template-nebulous .tldp-empty { margin: 2rem 0; color: var(--tldp-muted); }

/* ---- footer block ---- */

body.template-nebulous .tldp-foot { margin-top: 3.5rem; }
body.template-nebulous .tldp-foot h3 { margin-bottom: 1.2rem; }

body.template-nebulous .tldp-foot-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
    gap: 1.5rem;
}

body.template-nebulous .tldp-foot-grid h4 {
    font-size: 0.98rem;
    margin: 0 0 0.35rem;
    color: var(--tldp-ink);
}

body.template-nebulous .tldp-foot-grid p { margin: 0; font-size: 0.9rem; line-height: 1.6; color: var(--tldp-muted); }

@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .tldp-card,
    body.template-nebulous .tldp-pop-card,
    body.template-nebulous .tldp-go { transition: none; }
    body.template-nebulous .tldp-card:hover,
    body.template-nebulous .tldp-pop-card:hover { transform: none; }
}

/* The way from the featured chips beside the search box to the full
   price list. Small and quiet — it is a door, not a call to action. */
body.template-nebulous .domainchecker-allprices {
    margin: 0.9rem 0 0;
    font-size: 0.85rem;
}

body.template-nebulous .domainchecker-allprices a {
    color: var(--color-primary-background);
    font-weight: 600;
    text-decoration: none;
}

body.template-nebulous .domainchecker-allprices a:hover { text-decoration: underline; }

/* -----------------------------------------------------------------
   Domain checker — the heading column as a card, and holding still
   ----------------------------------------------------------------- */

/* TOP, NOT CENTRE. The vendor lays this row out with
   align-items: center, so the heading column is vertically centred
   against the search column beside it — and every time the right side
   grows the heading slides down to re-centre. Expanding "Suggest names
   for my business" did exactly that: the copy visibly dropped. Wayne:
   "The text is also dropping down when the Suggest names for my
   business button is clicked."

   Aligning to the top makes the heading independent of anything that
   happens on the right, which is what it should always have been —
   the two columns are not a pair to be balanced, they are a label and
   a control. */
body.template-nebulous .domainchecker-columns {
    align-items: flex-start;
}

/* Matches the .domaintld price chips beside it: the SAME token they
   use, same 10px radius, so it reads as part of the same set rather
   than a second idea. --color-box-background-border, not --color-border:
   the two coincide in light mode and drift apart under colortoggle-dark,
   where the theme redefines them separately. Borrowing the chips' own
   token means the card tracks them in both modes without a second
   declaration here. */
body.template-nebulous .domainchecker-columns-left {
    /* THE CHIPS' WHITE IS A GRADIENT, NOT A BACKGROUND-COLOUR. Matching
       the border alone left this card transparent over the page grey
       while the chips beside it read white — Wayne: "was supposed to
       match the domain chips but it['s] grey."

       Reading getComputedStyle().backgroundColor said "transparent" for
       both and hid the difference, which is the same mistake that once
       made a delete button look invisible here: the paint was in
       background-image the whole time. Copied from
       .domainchecker .domainpricing .domaintld verbatim, tokens and
       all, so the two track each other through any theme change. */
    background: linear-gradient(var(--gradient-degrees),
        var(--color-box-background-gradient-top) 0,
        var(--color-box-background-gradient-bottom) 100%);
    border: 1px solid var(--color-box-background-border);
    border-radius: 10px;
    padding: 1.6rem;
}

@media (max-width: 900px) {
    /* Stacked, the card would be a box around a heading with the search
       box floating beneath it — the framing only means something while
       the two columns sit side by side. */
    body.template-nebulous .domainchecker-columns-left {
        border: 0;
        padding: 0;
    }
}

/* -----------------------------------------------------------------
   Cart line item — the optional-extra block, and the remove button
   partials/cart-body.php

   Wayne: "on cart.php under the nebulous cart theme we need to clean
   up and create a better look. It looks unorganized. A radio button
   just sitting by no text[,] the delete button on the right works but
   is tiny."

   THE STRAY CHECKBOX WAS 729px WIDE. Not a styling preference — a
   measurement. This theme carries a blanket

       body.template-nebulous input, textarea, select
           { display: block; width: 100%; }

   which is right for text fields and wrong for every checkbox in the
   app: a block-level checkbox fills its container, renders its glyph
   centred in all that width, and pushes its own label text onto the
   next line. That is the "radio button just sitting by no text" —
   the text was there, a line below, orphaned from the box that
   controls it. The same fault was patched locally once before at
   .auth-trust-check, so this is the second sighting of one blanket
   rule, not two bugs.

   Everything else here is the clean-up that becomes possible once the
   control and its label are on the same line: the add-on reads as an
   optional extra attached to the domain rather than as loose text
   sitting under the title.
   ----------------------------------------------------------------- */

/* The line item's own title. The theme centres bare h3s, which put the
   domain name in the middle of the card with everything below it
   ragged-left — the single loudest reason the card read as unorganised. */
body.template-nebulous .cart-item-card-info > h3 {
    text-align: left;
    margin: 0 0 0.75rem;
    font-size: 1.05rem;
    line-height: 1.35;
}

/* ---- the optional extra ---- */

body.template-nebulous .cart-item-addon {
    margin: 0.9rem 0 0;
    padding: 0.85rem 1rem;
    background: #f6f8fa;
    border: 1px solid var(--color-box-background-border);
    border-left: 3px solid var(--color-primary-background);
    border-radius: 6px;
}

body.colortoggle-dark.template-nebulous .cart-item-addon {
    background: rgba(255, 255, 255, 0.04);
}

/* THE FIX. A flex row so the box and its words are one object, and the
   checkbox is put back to its intrinsic size — the blanket rule above
   is beaten on specificity (0,3,2 against 0,1,2) rather than with
   !important, so a future author can still override it normally. */
body.template-nebulous .cart-addon-label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0;
    cursor: pointer;
    font-size: 0.92rem;
    color: var(--color-heading);
}

body.template-nebulous .cart-addon-label input[type="checkbox"] {
    display: inline-block;
    flex: 0 0 auto;
    margin: 0;
    padding: 0;
    /* 16px so it is a real target beside 0.92rem text, and so the row
       does not jump once the theme's own padding is removed. */
    width: 16px;
    height: 16px;
    accent-color: var(--color-primary-background);
    cursor: pointer;
}

body.template-nebulous .cart-addon-label > span {
    /* Lets the price sit hard against the label rather than wrapping
       away from it. */
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    flex-wrap: wrap;
}

body.template-nebulous .cart-addon-label strong {
    color: var(--color-primary-background);
    font-weight: 700;
}

/* The explanation, indented to the label's text rather than the card
   edge — it belongs to the checkbox above it, and lining it up with
   the words instead of the box is what says so. */
body.template-nebulous .cart-addon-note {
    margin: 0.4rem 0 0 calc(16px + 0.6rem);
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--color-body);
}

/* ---- remove ---- */

/* 23x32 and lost in the corner. Square, sized to a real target, and
   given a resting border so it reads as a control before it is
   hovered. !important throughout because the rule it overrides uses it
   on every declaration. */
body.template-nebulous .cart-item-remove-icon {
    width: 34px !important;
    height: 34px !important;
    padding: 0 !important;
    border-radius: 4px !important;
    background: #f6f8fa !important;
    border: 1px solid var(--color-box-background-border) !important;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

body.colortoggle-dark.template-nebulous .cart-item-remove-icon {
    background: rgba(255, 255, 255, 0.05) !important;
}

@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .cart-item-remove-icon { transition: none; }
}

/* ------------------------------------------------------------------
   /web-app-development.php — the eleven services as stacked,
   anchored sections.

   Storefront CSS, so it lives here in nebulous.css and NOT in app.css,
   which the storefront never loads. Twenty rules had accumulated in the
   wrong file before anyone noticed, because a rule in the wrong sheet
   fails silently and reads correctly in the source.
   ------------------------------------------------------------------ */

/* Contents list. Navigation, not decoration — eleven long sections is
   too many to scroll blind, and this is the only place the whole range
   is visible at once. */
.dev-toc {
    /* A FOUR-COLUMN GRID, not wrapping flex. Eleven items packed
       greedily came out 5/4/2, and shrinking them only made that worse
       — smaller items pack MORE per row, heading for 5/5/1. Four fixed
       columns give 4/4/3 by construction, and equal-width cells stop
       "Web Design" and "Billing & Subscription Automation" reading as
       two different kinds of control. */
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.5rem;
    max-width: 1180px;
    margin: 2.5rem auto 0;
    padding: 0 1.25rem;
}

.dev-toc-item {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    /* A touch smaller, per Wayne. The text drops 1px (0.88rem -> 0.82rem,
       roughly 14px to 13px) and the padding comes in with it, so the
       proportions hold rather than leaving fat chips with small text. */
    padding: 0.42rem 0.7rem;
    border: 1px solid var(--color-border, #e2e5ea);
    /* Rectangular, not pills (Wayne, 2026-09-01). The theme's own
       radius token rather than a made-up value, so these match the
       cards and panels around them instead of introducing a third
       corner size to the page. */
    border-radius: var(--border-radius-default, 6px);
    font-size: 0.82rem;
    line-height: 1.3;
    /* Long labels centre rather than hugging the icon when a cell is
       wider than its text. */
    justify-content: flex-start;
    text-decoration: none;
    color: var(--color-body, #43494f);
    background: var(--background-body, #fff);
    transition: border-color 0.15s ease, color 0.15s ease;
}

.dev-toc-item:hover,
.dev-toc-item:focus-visible {
    border-color: var(--btn-grad-to, #e07b2a);
    color: var(--color-heading, #1c1f23);
}

.dev-toc-icon { font-size: 1rem; line-height: 1; }

.dev-service {
    /* Was 3.25rem top and bottom, which was the gap BETWEEN eleven
       full-width bands. The card below carries its own edge now, so
       this only has to space the cards apart. */
    padding: 1rem 1.25rem;
    /* The anchor lands under the sticky header without this. */
    scroll-margin-top: 90px;
}

/* EVERY SERVICE IS A CARD, not every other one (Wayne 2026-09-02:
   "Some of the categories on the page are missing their card frame
   ... every second one is missing the outline").

   The separation used to come from .dev-service-alt tinting alternate
   sections #f7f8fa. That gives you a frame on the odd ones and nothing
   at all on the even ones — the even sections simply sat on the page
   ground with no edge anywhere, which is exactly what he is seeing.
   Alternating bands read as separation only when the page is a stack
   of full-bleed bands; once each service became a bordered panel with
   a strip at its top, the tint was doing half a job on half the list.

   So the card moves to .dev-service-inner, which is the content column
   and already centred, and every section gets the same one. Lighter
   too, as asked: white rather than the #f7f8fa the tinted half had. */
.dev-service-inner {
    max-width: 1180px;
    margin: 0 auto;
    /* One column since the art came out. The grid and its 150px track
       are gone rather than left at 1fr, because a grid with a single
       track is a layout waiting to be misread. */
    display: block;
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-box-background-border, #e6e8ec);
    border-radius: 12px;
    padding: 1.5rem 1.6rem 1.9rem;
}

/* NO ALTERNATING SIDES. Swapping the art left and right down the page
   was meant to break up eleven stacked sections; what it actually did
   was move the anchor point on every scroll, so the eye had to find the
   heading again each time. One column edge, eleven headings on the same
   line.

   NO ALTERNATING BACKGROUND EITHER, as of 2026-09-02. .dev-service-alt
   is still on every other section in the markup and now paints nothing:
   the card above separates them, and doing both meant half the list had
   a frame and half had none. Left in the HTML rather than stripped out,
   because the class is harmless and removing it touches a template to
   no effect. */

/* .dev-service-art is gone — the art was removed from these
   sections on 2026-09-01. */

.dev-service-text h2 {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 0.75rem;
    font-size: 1.65rem;
    text-wrap: balance;
}

.dev-service-icon { font-size: 1.5rem; line-height: 1; }

.dev-service-lead {
    font-size: 1.08rem;
    font-weight: 600;
    color: var(--color-heading, #1c1f23);
    margin: 0 0 1rem;
}

.dev-service-text p { line-height: 1.62; margin: 0 0 1rem; }

.dev-service-text h3.dev-service-sub {
    /* vendor layout.css centres EVERY heading globally (h1..h6, no
       !important). A bare .dev-service-sub is (0,1,0) and outranks that
       (0,0,1) on paper — but this is the third time in this codebase a
       heading has stayed centred anyway, so it is written at (0,2,1)
       where nothing in the theme can reach it. Cheaper than being right
       and wrong at the same time. */
    text-align: left;
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-body, #6b7280);
    margin: 1.5rem 0 0.6rem;
}

.dev-service-check {
    list-style: none;
    margin: 0 0 1.25rem;
    padding: 0;
    display: grid;
    gap: 0.45rem;
}

.dev-service-check li {
    position: relative;
    padding-left: 1.6rem;
    line-height: 1.5;
}

.dev-service-check li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.45em;
    width: 0.62rem;
    height: 0.34rem;
    /* Green, 2026-09-03. Wayne asked for green check marks on the home
       page's service panels; these are the same marks on the same
       eleven services and were orange, so the two pages disagreed about
       what a tick looks like.

       The colour is written out rather than taken from --btn-grad-to:
       that token is the BUTTON gradient, and a tick borrowing it would
       silently change colour the day somebody restyles a button.

       #177245 measures 5.95:1 on white against the orange's 2.98:1 —
       comfortably past the 3:1 a meaningful graphic needs, which the
       orange did not actually meet. */
    border-left: 2px solid #177245;
    border-bottom: 2px solid #177245;
    transform: rotate(-45deg);
}

/* TINTED, because the card behind it is now white.
   It used to be white on the tinted sections and page-ground grey on
   the untinted ones — which on those sections was the same colour as
   what was behind it, so the box vanished and left a bare orange bar.
   One colour now, and it is the one that contrasts with the card. */
.dev-service-note {
    padding: 0.85rem 1rem;
    border-left: 3px solid var(--btn-grad-to, #e07b2a);
    background: var(--background-body, #f2f3f5);
    border-radius: 0 8px 8px 0;
    font-size: 0.94rem;
}

.dev-service-cta { margin: 1.5rem 0 0; }

/* The strip at the top of each service section. Reaches the section's
   own edges, so it reads as a band across the section rather than a
   box floating inside it. .dev-service pads 3.25rem 1.25rem. */
.dev-service-strip {
    /* Aligned to the content column, NOT bled to the section edges.
       The negative-margin version measured 1183px inside a 1162px
       section and overflowed sideways: .dev-service-inner is
       max-width 1180 and centred, so it is narrower than the section's
       padded box on a wide screen and pulling outward escapes the wrong
       box. Sitting flush with the text beneath it is both correct and
       what the eye expects from a heading band. */
    margin: 0 0 1.5rem;
    padding: 0.85rem 1.1rem;
    border-radius: 10px;
}

/* WHITE on the service page, the same --color-box-background the
   ruled section heading's box uses, so the two read as one system.
   (Wayne, 2026-09-01.)

   SCOPED, not changed in the shared rule. That rule also dresses the
   home page's panel strip, where the pane behind it is already white —
   a white strip there would simply vanish. The grey it uses is doing
   real work in that context.

   Two classes (0,2,0) so it beats the shared rule's (0,1,0) whatever
   the source order. The last time these two rules met, order decided
   it and the wrong one won. */
.dev-service .dev-service-strip {
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-border, #e2e5ea);
    border-radius: 10px;
}

.dev-service-strip h2 {
    margin: 0;
    letter-spacing: -0.01em;
    /* The theme centres every heading globally. Inside a flex row that
       leaves the text drifting in its own box rather than sitting
       beside the icon. */
    text-align: left;
}

/* The contents list, in a card. */
.dev-toc-card {
    /* No max-width. It used to be capped at 1180px while the content
       around it runs to the container's full 1250px, so the card sat
       visibly inset from the cards below it — Wayne: "make it as wide
       as the boxes below". Letting it fill the container is what makes
       the two line up, rather than guessing a matching number that
       would drift the next time the container changed. */
    margin: 1.75rem auto 2.5rem;
    padding: 1rem 1.1rem;
    /* #fff explicitly, not --background-body: that token resolves to
       the same light grey as the page behind it here, so the card had
       no edge and read as loose pills on a background. A card has to be
       lighter than what it sits on. */
    background: #fff;
    border: 1px solid var(--color-border, #e2e5ea);
    border-radius: 14px;
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
}

.dev-toc-card .dev-toc {
    margin: 0;
    padding: 0;
}

/* The .dev-toc-card + .section-heading rule that sat here is gone. It
   spaced the contents card from a heading BELOW it, and the card moved
   to sit UNDER "What Each Service Involves" on 2026-09-03 — so the
   adjacent-sibling selector stopped matching anything. Deleted rather
   than left in place: a rule that matches nothing is indistinguishable
   from a rule that is broken, and the next person has to work out
   which. The card's own margin now carries the spacing. */

/* ---- closing block ---- */
/* The FAQ rules that used to sit here are gone: this page now uses the
   site's own .faq-section / .faq-list component, so styling it twice
   was the reason it looked like a different site's widget. */
.dev-closing {
    max-width: 820px;
    margin: 0 auto;
    padding: 3rem 1.25rem;
}

.dev-closing h2 { text-align: center; margin: 0 0 1.5rem; }

.dev-closing { text-align: center; }
.dev-closing p { line-height: 1.62; }
.dev-closing .button1 { margin: 0.35rem 0.3rem 0; }

@media (max-width: 860px) {
    .dev-service-strip {
        margin: 0 0 1.1rem;
        padding: 0.7rem 0.9rem;
    }

    /* Art above the words on a narrow screen regardless of which side
       it takes on desktop — an alternating order that survives the
       collapse just moves the picture around unpredictably. */
    .dev-service { padding: 2.25rem 1.25rem; }
    .dev-service-text h2 { font-size: 1.4rem; }
}

/* ------------------------------------------------------------------
   The service panel's fact strip (variant E, chosen 2026-09-01).

   Icon and name on the left, and on the right one checkable claim plus
   the typical engagement. It ADDS to the panel: the lead, every body
   paragraph, the full checklist and the note below are untouched.

   Light, at Wayne's request. The first version was dark (#1c2129) and
   read as a title bar bolted onto the panel rather than part of it.
   ------------------------------------------------------------------ */
/* SHARED WITH .dev-service-strip on /web-app-development.php. One rule
   with two selectors rather than two copies — the strip is the same
   component in both places and must not drift into looking like two. */
.dev-pane-head.dev-pane-strip,
.dev-service-strip {
    /* Full-bleed to the panel edges, which the panel's own padding
       otherwise prevents. Matching the padding here rather than
       removing it from the panel, because every other pane relies on
       that padding. */
    /* NO MARGIN HERE. This rule is shared with .dev-service-strip, and
       the negative margin below belongs only to the panel — it exists
       to cancel .dev-pane's padding. Left in the shared rule it also
       applied to the service page, where the container is narrower
       than the section, so the strip measured 1183px inside a 1162px
       section and hung over the edge. Shared rules carry what is
       shared; geometry that cancels one container's padding is not. */
    /* THE LAYOUT LIVES HERE, not on .dev-pane-head.
       flex-wrap and row-gap were in this shared rule while `display:
       flex` sat on .dev-pane-head — which the service page's <header>
       does not have. So on that page the strip computed to display:
       block: flex-wrap did nothing, margin-left:auto on the facts did
       nothing, and the theme's global heading centring took over. Icon
       on one line, title centred, facts stranded underneath.
       A shared rule has to carry everything the component needs, not
       the half that happened to be missing from one consumer. */
    display: flex;
    align-items: center;
    gap: 0.7rem;
    flex-wrap: wrap;
    row-gap: 0.4rem;
    padding: 0.85rem 1.9rem;
    background: var(--background-alt, #f4f6f8);
    border-bottom: 1px solid var(--color-border, #e3e7ec);
}

/* The panel's own bleed: exactly .dev-pane's padding (1.7rem 1.9rem),
   negated, so the strip reaches the panel edges. */
.dev-pane-head.dev-pane-strip {
    margin: -1.7rem -1.9rem 1.4rem;

    /* WHITE HERE TOO (Wayne 2026-09-02: "the grey background is still
       dark behind the top banner ... it's supposed to be light").

       The shared rule's grey was kept here on purpose a day earlier,
       reasoning that a white strip on an already-white pane would
       vanish. That reasoning was wrong in the way that matters: it
       optimised for the strip being VISIBLE as a band, when the band
       itself was the complaint. Measured against the page it read as
       244,246,248 sitting inside a white panel — a grey bar across the
       top of every service.

       It does not vanish, because the shared rule's border-bottom
       still separates it from the body. A header row with a hairline
       under it, which is what the service page already does with its
       own white strip — the two now agree, which is what the shared
       rule's comment asked for and did not get. */
    background: var(--color-box-background, #fff);
}

.dev-pane-strip h3,
.dev-service-strip h2 {
    /* Smaller than the bare heading it replaces: inside a strip it is a
       label with facts beside it, not the page's loudest thing. */
    font-size: 1.15rem;
}

.dev-pane-facts {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.9rem;
    /* Never squeezed to the point of wrapping mid-phrase — the row-gap
       above lets the whole group drop to its own line instead. */
    flex: none;
}

.dev-pane-claim {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.82rem;
    color: var(--color-body, #4b5563);
    white-space: nowrap;
}

/* A tick, drawn rather than a glyph — the storefront is Font Awesome 5
   and a v6-only name renders nothing at all, silently. */
.dev-pane-claim::before {
    content: "";
    width: 0.55rem;
    height: 0.3rem;
    border-left: 2px solid #2f9e5f;
    border-bottom: 2px solid #2f9e5f;
    transform: rotate(-45deg);
    flex: none;
}

.dev-pane-when {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.28rem 0.7rem;
    border-radius: 999px;
    background: #fff;
    border: 1px solid #e6d5c2;
    font-size: 0.8rem;
    font-weight: 600;
    color: #8a5a24;
    white-space: nowrap;
}

.dev-pane-when::before {
    content: "";
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: var(--btn-grad-from, #f5a45f);
    flex: none;
}

@media (max-width: 720px) {
    /* On a narrow panel the claim is the first thing to go — the
       timeline is the fact somebody came for, and two lines of strip
       above the copy defeats the point of a strip. */
    .dev-pane-claim { display: none; }

    .dev-pane-head.dev-pane-strip {
        margin: -1.7rem -1.2rem 1.1rem;
        padding: 0.7rem 1.2rem;
    }

    .dev-pane-strip h3 { font-size: 1.05rem; }
}

/* ---- Bulk domain transfer / registration (domain-transfer.php) ----
   Wayne 2026-09-02: "Domain transfer page should have the option for
   bulk domain transfers and registers".

   Built on .cart-table above rather than a second table implementation,
   so the two agree about padding and rules by construction. Only the
   parts .cart-table has no opinion about are set here.

   In nebulous.css and NOT app.css: app.css is never loaded while a
   discovered template is active (see the .cart-table note above), so a
   rule written there would be right, present, and completely inert. */
.bulk-domains-example {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 0.85rem 1rem;
    margin: 0 0 1.5rem;
    overflow-x: auto;
    font-size: 0.9rem;
    line-height: 1.6;
}
.bulk-domains-form { margin-bottom: 2rem; }
.bulk-domains-form textarea {
    width: 100%;
    font-family: monospace;
    /* A pasted list is columnar data — proportional type makes a
       mistyped extension genuinely hard to spot. */
    font-size: 0.9rem;
    line-height: 1.6;
}
/* The two modes read as one choice, side by side, rather than as two
   unrelated tickboxes stacked above the box they govern. */
.bulk-domains-modes {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 2rem;
    margin-bottom: 1rem;
}
.bulk-domains-modes label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
}
.bulk-domains-modes input[type="radio"] { width: auto; margin: 0; }
.bulk-domains-years { display: block; margin-bottom: 1rem; max-width: 16rem; }
.bulk-domains-notice { font-weight: 600; }

/* Wide on a narrow screen: the row has up to five columns and one of
   them is a text input, so it scrolls in its own box rather than
   pushing the whole page sideways.

   THE SCROLL GOES ON A WRAPPER, not on the table. `display: block` on
   a <table> does give you overflow, and it also throws away the table
   layout that `width: 100%` depends on — the box stayed 1162px wide
   while the columns collapsed to 706px and sat in the left half of an
   empty row. Measured, not guessed. */
.bulk-domains-scroll { overflow-x: auto; margin-bottom: 1.5rem; }
.bulk-domains-scroll .cart-table { margin-bottom: 0; }
.bulk-domains-table { min-width: 34rem; }
.bulk-domains-code { width: 12rem; display: inline-block; margin: 0; }
.bulk-domains-status { font-weight: 600; }
.bulk-domains-message {
    display: block;
    font-size: 0.85rem;
    opacity: 0.75;
    white-space: normal;
}
.bulk-domains-total { font-weight: 600; margin-bottom: 1rem; }

/* Status is carried by the row's own tint as well as its words, so a
   list of thirty can be skimmed. Deliberately faint: these sit behind
   the .cart-table hover rule, which must still read as a hover. */
.bulk-domains-row.bulk-domains-ok { background: rgba(64, 160, 96, 0.07); }
.bulk-domains-row.bulk-domains-needs_code { background: rgba(240, 149, 70, 0.10); }
.bulk-domains-row.bulk-domains-invalid,
.bulk-domains-row.bulk-domains-unsupported,
.bulk-domains-row.bulk-domains-duplicate,
.bulk-domains-row.bulk-domains-in_cart,
.bulk-domains-row.bulk-domains-taken,
.bulk-domains-row.bulk-domains-error { background: rgba(150, 150, 150, 0.10); }

/* ---- [hidden] MEANS HIDDEN. ONE RULE, NOT ONE PER COMPONENT. ----
   Added 2026-09-02, after the domain price filter shipped broken.

   The browser's `[hidden] { display: none }` lives in the UA
   stylesheet, so ANY author rule setting `display` beats it. Give a
   component `display: flex` and `el.hidden = true` sets the attribute,
   passes every check you can make in JavaScript, and changes nothing on
   screen.

   This had already been patched FIVE separate times here — .configure-
   ns-panel, .ai-namespin-body, .chat-widget-panel twice and
   .client-disclose-form — each with its own comment explaining the same
   trap. .tldp-card was the sixth and was simply missed: it is
   `display: flex`, so every price-band filter set `hidden` on the cards
   it wanted gone and all 474 stayed on screen. The counter above the
   grid updated correctly the whole time, because that text is written
   by the same function that "hid" them, which is what made it look like
   the buttons were dead rather than the CSS.

   Six patches for one rule is the signal. !important because that is
   the only thing that reliably outranks an arbitrary future
   `display` declaration, and because nothing should ever want an
   element marked hidden to be visible. The per-component rules above
   are now redundant and left alone rather than churned out. */
[hidden] { display: none !important; }

/* ---------------------------------------------------------------
   Deploy configurator (partials/plan-configurator.php)

   Wayne asked to copy Vultr's deploy configurator. Built on this
   theme's own tokens rather than fresh colours, so it inherits a
   restyle instead of fighting one — the same reason the storefront
   card language sets tokens and never `background` directly.

   The table scrolls inside its own wrapper. A spec table with five
   columns will not fit a phone, and letting the page scroll
   sideways instead breaks every other section on it.
   --------------------------------------------------------------- */

.deploy-config {
    background: var(--color-box-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    padding: 24px;
    margin-bottom: 30px;
}

.deploy-config-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 18px;
}

.deploy-config-title {
    color: var(--color-heading);
    font-family: var(--font-heading);
    margin: 0;
}

.deploy-config-region {
    color: var(--color-body);
    margin: 0;
    opacity: .85;
    font-size: 14px;
}

.deploy-config-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 18px;
}

.deploy-config-tab {
    background: var(--color-tertiary-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    color: var(--color-body);
    cursor: pointer;
    font: inherit;
    padding: 8px 16px;
}

.deploy-config-tab:hover {
    background: var(--color-tertiary-active-background);
}

.deploy-config-tab.is-active {
    background: var(--color-primary-background);
    border-color: var(--color-primary-background);
    color: var(--color-primary-foreground);
}

/* Keyboard focus stays visible. A configurator is operated, and an
   invisible focus ring makes it unusable without a mouse. */
.deploy-config-tab:focus-visible,
.deploy-config-sort:focus-visible,
.deploy-config-row input:focus-visible {
    outline: 2px solid var(--color-primary-background);
    outline-offset: 2px;
}

.deploy-config-table-wrap {
    overflow-x: auto;
}

.deploy-config-table {
    border-collapse: collapse;
    width: 100%;
}

.deploy-config-table th,
.deploy-config-table td {
    border-bottom: 1px solid var(--color-box-background-divider);
    padding: 12px 14px;
    text-align: left;
}

.deploy-config-table thead th {
    color: var(--color-heading);
    font-size: 13px;
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.deploy-config-sort {
    background: none;
    border: 0;
    color: inherit;
    cursor: pointer;
    font: inherit;
    letter-spacing: inherit;
    padding: 0;
    text-transform: inherit;
}

.deploy-config-row {
    cursor: pointer;
}

.deploy-config-row:hover {
    background: var(--color-tertiary-background);
}

.deploy-config-row.is-selected {
    background: var(--color-tertiary-active-background);
}

/* The selected row is marked by more than colour, so it still reads
   for anyone who cannot distinguish the two backgrounds. */
.deploy-config-row.is-selected .deploy-config-name {
    box-shadow: inset 3px 0 0 0 var(--color-primary-background);
}

.deploy-config-name {
    color: var(--color-heading);
    font-weight: 600;
}

.deploy-config-name label {
    cursor: pointer;
}

.deploy-config-pick {
    width: 1%;
}

.deploy-config-price {
    text-align: right;
    white-space: nowrap;
    /* Digits line up column-wise; without this the prices ripple. */
    font-variant-numeric: tabular-nums;
}

.deploy-config-price-col {
    text-align: right;
}

.deploy-config-term {
    font-size: 13px;
    opacity: .8;
}

.deploy-config-empty {
    color: var(--color-body);
    padding: 24px 0;
    text-align: center;
}

.deploy-config-summary {
    align-items: center;
    background: var(--color-tertiary-background);
    border: 1px solid var(--color-box-background-border);
    border-radius: var(--border-radius-default);
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    justify-content: space-between;
    margin-top: 20px;
    padding: 16px 20px;
    position: sticky;
    bottom: 12px;
}

.deploy-config-summary-text {
    display: flex;
    flex-direction: column;
    /* Without this a long plan name refuses to shrink and pushes the
       deploy button off the end of a narrow summary bar. */
    min-width: 0;
}

.deploy-config-summary-name {
    color: var(--color-heading);
    font-weight: 600;
}

.deploy-config-summary-specs {
    font-size: 14px;
    opacity: .85;
}

.deploy-config-summary-price {
    color: var(--color-heading);
    font-size: 20px;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

@media (max-width: 640px) {
    .deploy-config {
        padding: 16px;
    }

    .deploy-config-summary {
        align-items: stretch;
        flex-direction: column;
        position: static;
    }

    .deploy-config-deploy {
        text-align: center;
    }
}

@media (prefers-reduced-motion: reduce) {
    .deploy-config-row,
    .deploy-config-tab {
        transition: none;
    }
}

/* The region note sits under the badge on a narrow screen rather than
   pushing the heading off the row. */
.deploy-config-region-note {
    display: block;
    font-size: 13px;
    opacity: .75;
}

@media (min-width: 900px) {
    .deploy-config-region-note {
        display: inline;
    }
}

/* ---------------------------------------------------------------
   Family tabs — the card, its picture and its label.

   Chosen by Wayne from five options rendered side by side. The
   shape that won: a white card, a flat vector drawing of the
   hardware on white, a rule, and the family name beneath. Only the
   SELECTED tab carries a filled bar, so one colour appears in the
   row and the eye goes to it.

   THE ACCENT FOLLOWS THE HARDWARE, set by .is-nvidia / .is-amd on
   the tab strip. Green where the cards really are NVIDIA, AMD red
   where the machines really are AMD — read from Vultr's own
   cpu_vendor field rather than picked. The ink colour differs with
   it because it has to: white on #76B900 measures about 2.4:1 and
   fails, dark on #ED1C24 fails the other way.
   --------------------------------------------------------------- */

.deploy-config-tab.has-image {
    display: flex;
    flex-direction: column;
    gap: 0;
    overflow: hidden;
    padding: 0;
    width: 168px;
}

.deploy-config-tab.has-image img {
    background: #fff;
    display: block;
    height: auto;
    width: 100%;
}

/* The rule between picture and label. It used to exist only as the
   edge where a grey label met the art; once both went white it
   vanished and the card read as one undivided block. */
.deploy-config-tab-label {
    background: #fff;
    border-top: 1px solid #e6e9ee;
    display: block;
    font-size: 13px;
    line-height: 1.25;
    padding: 9px 8px;
    text-align: center;
    width: 100%;
}

.deploy-config-tab-all {
    align-items: center;
    display: flex;
    justify-content: center;
    min-width: 92px;
}

.deploy-config-tab-all em {
    font-size: 12px;
    font-style: normal;
    opacity: .7;
}

/* --- NVIDIA green --- */
.deploy-config .deploy-config-tabs.is-nvidia .deploy-config-tab.is-active {
    background: #fff;
    border-color: #76b900;
}

.deploy-config-tabs.is-nvidia .deploy-config-tab.is-active .deploy-config-tab-label {
    background: #76b900;
    border-top-color: #69a600;
    color: #101410;
    font-weight: 700;
}

/* --- AMD red --- */
.deploy-config .deploy-config-tabs.is-amd .deploy-config-tab.is-active {
    background: #fff;
    border-color: #ed1c24;
}

.deploy-config-tabs.is-amd .deploy-config-tab.is-active .deploy-config-tab-label {
    background: #ed1c24;
    border-top-color: #b8141b;
    color: #fff;
    font-weight: 700;
}

@media (prefers-reduced-motion: reduce) {
    .deploy-config-tab {
        transition: none;
    }
}

@media (max-width: 640px) {
    .deploy-config-tab.has-image {
        width: 132px;
    }
}

/* ---------------------------------------------------------------
   Tab colours, scoped through .deploy-config so they win.

   `.deploy-config-tab` alone is specificity 0,1,0 and the theme
   styles buttons inside content areas with a descendant selector
   at 0,1,1 — so every tab rendered in the active orange and the
   selected one was indistinguishable from the rest. Measured in
   the browser rather than guessed: an inline background won, which
   ruled out !important and left specificity as the answer.

   Adding the section class takes these to 0,2,0. No !important —
   a licensee restyling the theme should still be able to override
   this the ordinary way.
   --------------------------------------------------------------- */

.deploy-config .deploy-config-tab {
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-box-background-border, #e6e8ec);
    color: var(--color-body, #696969);
}

.deploy-config .deploy-config-tab.has-image {
    border-color: #dfe3e9;
}

.deploy-config .deploy-config-tab:hover {
    background: var(--color-tertiary-background, #f5f5f5);
    border-color: var(--color-primary-background, #f09546);
}

.deploy-config .deploy-config-tab.is-active {
    background: var(--color-primary-background, #f09546);
    border-color: var(--color-primary-background, #f09546);
    color: var(--color-primary-foreground, #fff);
}

/* An image tab keeps a white card — only its LABEL fills. Without
   this the rule above painted the whole tile orange behind the
   drawing, which is the slab that started this redesign. */
.deploy-config .deploy-config-tab.has-image.is-active {
    background: #fff;
    color: var(--color-body, #696969);
}

/* Every token above carries a literal fallback. An undefined
   variable invalidates the whole declaration rather than falling
   back to the previous rule, which would put us straight back to
   the theme's button colour. */

/* ---------------------------------------------------------------
   Mega-menu cards, 10% tighter.

   Wayne, 2026-09-02: the Web Hosting dropdown now runs past the
   bottom of the browser. That is my doing — it went from six items
   to ten this session, which is five rows instead of three.

   DONE BY SETTING NEBULOUS'S OWN TOKENS, which is the house rule
   here and which I reached for second rather than first. My first
   attempt wrote `.navmain-dropdown-megamenu .navmain-link > a
   { padding: ... }` and half of it silently lost the cascade — the
   icon shrank, the padding did not. The vendor drives all of this
   from variables, so setting the variable wins everywhere the
   vendor uses it, needs no specificity war, and keeps working if
   the vendor's selectors change.

   Every value is 90% of the stock one, rounded to whole pixels:
   padding 25→22, button padding 15→13, margin 15→13, icon spacing
   75→67, icon size 33→30.

   The two design variants are both set. This install uses
   headerdesign1, but a licensee switching to 2 should not find the
   menu silently back at full size.
   --------------------------------------------------------------- */

:root {
    --navmain-headerdesign1-megamenu-padding: 22px;
    --navmain-headerdesign1-megamenu-button-padding: 13px;
    --navmain-headerdesign1-megamenu-button-margin: 13px 0;
    --navmain-headerdesign1-megamenu-icon-spacing: 67px;
    --navmain-headerdesign1-megamenu-icon-size: 30px;

    --navmain-headerdesign2-megamenu-padding: 22px;
    --navmain-headerdesign2-megamenu-button-padding: 13px;
    --navmain-headerdesign2-megamenu-button-margin: 13px 0;
    --navmain-headerdesign2-megamenu-icon-spacing: 67px;
    --navmain-headerdesign2-megamenu-icon-size: 30px;
}

/* THE TYPE SCALES FROM ONE DECLARATION, because the vendor sizes
   the heading and the blurb in EM — 1.15em and 0.9em of the card's
   own 16px. Reducing that base to 14.4px carries both down by the
   same 10% as the box, and keeps their relationship intact.

   My first attempt set the two sizes individually at 0,2,0 against
   a vendor selector of 0,5,0, and neither applied. Matching the
   vendor's selector shape here rather than adding !important, so a
   licensee restyling the theme can still override it normally. */
.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link {
    /* WHOLE PIXELS, and this is the point of the rule rather than a
       detail of it.

       The first version set 14.4px here and let the vendor's em-based
       children scale from it — which gave a 16.56px heading, 12.96px
       body and 20.16px leading. Every one fractional, and Wayne saw it
       immediately: "the text in the mega menu cards has changed, the
       style looks rough, it used to be smooth." He was right, and the
       guess in his message was right too. A fractional font-size makes
       the browser rasterise glyphs at a size that does not land on the
       pixel grid, and on Windows that reads as slightly fuzzy type.

       The vendor's own sizes were 16 / 18.4 / 14.4 / 22.4 — whole or
       half pixels throughout. So the sizes below are set explicitly at
       whole pixels rather than derived, and the children are pinned
       too so nothing multiplies its way back to a fraction. */
    font-size: 14px;
    line-height: 20px;
}

.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link .navmain-link-heading {
    font-size: 17px;
    line-height: 22px;
    padding-bottom: 4px;
}

.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-link .navmain-link-text {
    font-size: 13px;
    line-height: 19px;
}

/* The icon chip is sized here rather than by the vendor's
   icon-size token, because this file already restyles it (the
   amber chip treatment above) with width and height of its own. */
body.template-nebulous .navmain-dropdown-megamenu .navmain-link.navmain-icon i {
    height: 36px;
    line-height: 36px;
    width: 36px;
}

/* ---------------------------------------------------------------
   Category intro card (partials/category-intro.php)

   Wayne asked for "a paragraph in a card with some more info,
   light background like the card below it" — so it takes the same
   surface, border and radius as .deploy-config rather than a
   colour of its own. Two panels that are nearly the same read as
   a mistake; two that are identical read as a set.

   Measure is capped at 68 characters. The card is full width and
   prose set edge to edge across 1100px is hard to track back to
   the next line, which is the whole reason this is a paragraph
   and not another row of specs.
   --------------------------------------------------------------- */

.category-intro {
    background: var(--color-box-background, #fff);
    border: 1px solid var(--color-box-background-border, #e6e8ec);
    border-radius: var(--border-radius-default, 10px);
    margin-bottom: 30px;
    padding: 24px;
}

.category-intro p {
    color: var(--color-body, #696969);
    margin: 0 0 14px;
    /* NO max-width. It was 68ch, which is the right measure for a
       narrow column and wrong here — the card is the full content
       width, so capping the text left half the panel empty and the
       card looked broken rather than considered. Wayne: "the 3
       paragraphs are only half the card width". The card's own
       padding provides the margin. */
}

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

/* The opening paragraph carries the argument, so it is set a
   little larger — a lede rather than body copy. */
.category-intro p:first-child {
    color: var(--color-heading, #243141);
    font-size: 1.05em;
}

@media (max-width: 640px) {
    .category-intro {
        padding: 16px;
    }
}

/* ---------------------------------------------------------------
   Mega menu: three cards per row, and nothing outside them.

   Wayne, 2026-09-02: "Can we just make the dropdown cards all 3
   cards per row?" and "No text outside of the cards." The second
   answered his earlier question about why the Web Hosting panel
   read "E-commerce, hosting & domain automation..." — that was the
   SITE tagline being passed as the panel blurb, and Web Hosting
   was the only menu passing it rather than a sentence of its own.
   The blurb column is gone instead of reworded.

   :only-child is doing real work here. The Company menu's second
   column holds CARDS rather than loose text, so it stays a 70/30
   split; every other menu now has one column and takes the full
   width. A blanket rule would have squashed Company's card group
   into a third of the panel.
   --------------------------------------------------------------- */

.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-columns .navmain-columns-col1:only-child {
    padding-right: 0;
    width: 100%;
}

.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-links {
    display: flex;
    flex-wrap: wrap;
    /* PACK LEFT, with a real gap. The vendor spreads these with
       space-between, which looked deliberate at two per row and broke
       the moment there were three: the Domains menu has two items, and
       they were flung to opposite edges of the panel with a void
       between them. Only visible by opening each menu — the ten-item
       one looked perfect throughout. */
    gap: 0 18px;
    justify-content: flex-start;
}

/* Three across. The gap is subtracted from each card's basis so the
   row still fits — a flat 33.333% with a gap between them overflows
   and drops the third card onto the next line, which is exactly the
   two-per-row layout this replaces. */
.navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-links > .navmain-link {
    flex: 0 0 calc(33.333% - 12px);
    max-width: calc(33.333% - 12px);
}

@media (max-width: 1100px) {
    .navmain .navmain-dropdown-megamenu .navmain-subcontainer .navmain-links > .navmain-link {
        flex-basis: calc(50% - 12px);
        max-width: calc(50% - 12px);
    }
}

/* ---------------------------------------------------------------
   ONE BANNER HEIGHT, EVERYWHERE EXCEPT THE HOMEPAGE.

   Wayne, after three attempts that each fixed only part of it:
   "make all banners except the ones on index.php the same size.
   Exactly the same size... All exactly the same size as
   VPS-Hosting."

   391px is vps-hosting's own rendered height, measured, and it is
   the reference he named. Three different things render a banner
   on this site and each had its own height:

     ul.nebulous-slider          391px  categories with a slide
     .category-hero-banner       388px  categories without one
     .category-hero-banner-strip 317px  every static page

   .page-banner is set by all three EXCEPT the homepage carousel,
   which shares partials/hero-slides.php with the category slider
   and was therefore unreachable by any selector until the class
   existed. That is why earlier attempts either missed the sliders
   or would have shrunk the homepage with them.

   height rather than min-height, because min-height alone left the
   strip's aspect-ratio free to make it taller on a wide window.
   aspect-ratio is explicitly cleared for the same reason.
   --------------------------------------------------------------- */

:root {
    --page-banner-height: 391px;
}

/*
 * THE BOARD'S BANNER GETS 4px MORE, and nothing else does.
 *
 * The split-flap board sits in a 249px slot and its content came to
 * 251px — trimmed from 66px over, one measurement at a time. The last
 * two could have come out of the type, but the foot line had already
 * been shrunk to 8.8px and about 2.3:1 once for exactly this reason,
 * and squeezing legibility to protect a round number is the wrong
 * trade twice.
 *
 * :has() scopes the extra height to a banner that actually contains a
 * board, so the fifteen other pages using this component do not move by
 * a pixel. A browser without :has() support simply keeps 391px and
 * clips the last 2px of a decorative border — the failure is invisible
 * rather than broken.
 *
 * 4px rather than 2px: the extra gives the board a little air instead
 * of resting exactly on its own limit, so the next small copy change
 * does not immediately clip again.
 */
.category-hero-banner.page-banner:has(.flapboard) {
    --page-banner-height: 395px;
}

/*
 * min-height on the frame was tried here and did NOT raise the row: the
 * content row is sized by the headline CARD beside the art, and the
 * frame stretches to it rather than driving it. Measured — banner went
 * 391 -> 395 and the frame stayed at 249, so the 4px landed in the
 * banner's padding instead.
 *
 * The remaining 2px came out of the board's own vertical padding, which
 * costs nothing legible. The taller banner stays because it gives the
 * board air rather than resting it exactly on its limit.
 */

.nebulous-slider.page-banner,
.category-hero-banner.page-banner,
.category-hero-banner.page-banner.category-hero-banner-strip {
    aspect-ratio: auto;
    height: var(--page-banner-height);
    max-height: var(--page-banner-height);
    min-height: var(--page-banner-height);
}

/* The slide inside a category carousel has to fill the fixed box
   rather than set its own height, or the <ul> is 391px around a
   taller child. */
.nebulous-slider.page-banner > li,
.nebulous-slider.page-banner .banner {
    height: 100%;
    min-height: 0;
}

/*
 * AND THE CAROUSEL TOO — NOTHING IN A BANNER GETS CUT OFF.
 *
 * The rule below leaves `.nebulous-slider.page-banner` fixed, on the
 * grounds that its content is artwork which should fill a known box.
 * It is not: these slides carry a heading, a paragraph, a feature list
 * and a BUTTON. Wayne, on /category.php?slug=high-frequency: "the card
 * is being cut off in mobile", and it was every page in the Web
 * Hosting group.
 *
 * The slide is `overflow: hidden`, so this never overflowed the way the
 * text banner below did. It CLIPPED, silently — 530px of content inside
 * 391px, and what fell outside was not the tail of a list, it was
 * "Features" and "Get Started Now!". The homepage lost "Start a quote"
 * the same way: 492px at 400 wide, 544px at 500, 638px at 768, and
 * still 148px at 1014, which is a desktop. Nothing looked broken on any
 * of them, which is how it survived.
 *
 * Wayne, asked which way to trade it: "Show everything, fix the gap if
 * you can."
 *
 * So: no ceiling anywhere, and min-height keeps the uniform 391px on
 * every banner whose content still fits — which is most of them, so the
 * thing he asked for originally still holds.
 *
 * THE GAP is the other half and it is NOT solvable in CSS. bxSlider
 * positions its slides absolutely and writes a fixed pixel height onto
 * `.bx-viewport`, sized once to the TALLEST slide — so a short slide
 * sat above the difference, 232-444px of empty page. `adaptiveHeight:
 * true` does not fix it either: bxSlider applies that on TRANSITION, so
 * the first paint still uses the tallest. nebulous.js now sizes the
 * viewport to the slide that is actually showing; see
 * `fitSliderViewport()` there. Category heroes never reach that code —
 * a single-slide banner gets no carousel at all — so this CSS is what
 * serves them, alone.
 */
.nebulous-slider.page-banner {
    height: auto;
    max-height: none;
    min-height: var(--page-banner-height);
}

/* The `height: 100%` these carry is what passed the fixed box down to
   the slide; against an auto-height parent it has to become a floor
   instead, or the slide keeps clipping inside a frame that has already
   grown to fit it. */
.nebulous-slider.page-banner > li,
.nebulous-slider.page-banner .banner {
    height: auto;
    min-height: var(--page-banner-height);
    overflow: visible;
}

/*
 * A UNIFORM HEIGHT IS A FLOOR, NOT A CEILING — for the banners that
 * carry TEXT (2026-09-07).
 *
 * The rule above locks height, max-height AND min-height so every
 * banner is exactly the reference height, which is what Wayne asked
 * for. It is also a fixed box in `flex-direction: column` with
 * `justify-content: center`, so copy taller than the box does not clip
 * or scroll — it CENTRES, and overflows equally in BOTH directions.
 * Half of it lands on top of the site header.
 *
 * Measured on /web-app-development.php, whose lead is a long one:
 *
 *     1014px wide — content 406px in 395px, spills   5px over the header
 *      884px wide — content 521px in 395px, spills  63px
 *      500px wide — content 1113px in 395px, spills 359px
 *
 * 1014px is Wayne's own window. There is no breakpoint to hide behind:
 * it is already wrong on the desktop it was designed on, and the next
 * sentence anybody adds makes it worse. So this is not scoped to a
 * media query.
 *
 * min-height alone keeps the uniform height everywhere the content
 * fits — which is every other page, so the thing he asked for still
 * holds — and lets only the pages that would otherwise paint over the
 * header grow to fit their own words.
 *
 * The CAROUSEL is deliberately left fixed. Its children are sized at
 * `height: 100%` against it, and its content is artwork, which should
 * fill a known box rather than set one.
 */
.category-hero-banner.page-banner,
.category-hero-banner.page-banner.category-hero-banner-strip {
    height: auto;
    max-height: none;
    min-height: var(--page-banner-height);
}

/* ---------------------------------------------------------------
   The rule behind a section heading, darkened.

   Wayne: "why did your heading line change to grey everywhere? It
   was black before." I could not find a change — the vendor's
   heading-background-nebulous.png is a 1x1 pixel of rgb(210,210,210),
   byte-identical to HEAD and last touched in the Stellar->Nebulous
   rename; the .section-heading rule is untouched; no colour token
   was redefined. The line has been #d2d2d2 the whole time.

   Black, per Wayne — #1f2430, the theme's near-black rather than
   pure #000, so it sits with the heading type beside it instead of
   being the darkest thing on the page. Done
   as a gradient rather than by editing the PNG: a vendor asset
   stays as shipped, the colour is greppable instead of hidden in
   a binary, and a licensee can override it like any other rule.

   linear-gradient with two identical stops is the standard way to
   paint a flat colour into background-image — it has to be an
   IMAGE to replace an image, since background-color would sit
   behind the h2's own white fill rather than forming the rule.
   --------------------------------------------------------------- */

.section-heading {
    background-image: linear-gradient(#1f2430, #1f2430);
    /* One pixel tall, repeated across — the same geometry the tiled
       PNG produced, so nothing about the band's spacing moves. */
    background-size: 100% 1px;
}

/* THE FRAME TOO, not just the rule — Wayne: "frame and line i mean".

   The heading's box was outlined in var(--color-border), which is
   #e6e8ec here: a hairline barely distinguishable from the white fill
   inside it, so the heading read as floating text rather than a plate
   sitting on the rule. Matching it to the rule's own colour makes the
   two read as one piece of furniture.

   Its own declaration rather than a token override, because
   --color-border is used across the whole theme — darkening it there
   would repaint every panel, table and input on the site. */
.section-heading h2,
.section-heading h1 {
    border-color: #1f2430;
}

/* ---------------------------------------------------------------
   Success text, made readable.

   Found by sweeping every `var(--token, #fallback)` in this file
   against the token's REAL resolved value — the trap Wayne asked
   me to look for: a fallback only fires when a variable is
   MISSING, never when its value is unhelpful, so an author who
   writes a sensible fallback beside a bad token gets the bad one
   and no warning.

   Twenty-five tokens carry colour fallbacks here. Most differ
   harmlessly. One did not:

     .success { color: var(--color-green, #1f8a4c) }

   --color-green is #43de04 in the vendor's variables.css — a neon
   green measuring 1.80:1 against white. That is not a stylistic
   preference, it is unreadable: AA needs 4.5:1 for body text. The
   author's own fallback, #1f8a4c, was reaching for a real green
   and could never apply.

   #1a7a43 at 5.37:1 passes, and is the same hue the fallback
   intended rather than a new colour introduced to the palette.

   Set as the TOKEN rather than on the three call sites, because
   one of them is in the vendor's own layout.css and cannot be
   edited without forking a vendor file.
   --------------------------------------------------------------- */

:root {
    --color-green: #1a7a43;
}

/* ---- LIGHT-CARD BANNER -----------------------------------------------
   The hero treatment Wayne chose out of the options preview, applied
   per page via $heroLightCard rather than sitewide. shop.php is first;
   the rest follow as their artwork is redrawn, because every other
   banner's art has a dark background baked into its pixels and would
   sit on a white band looking like a mistake.

   Everything here is a deliberate reversal of the dark treatment above:
   the band stops carrying a photograph, the copy panel stops being a
   60%-black scrim and becomes a real cream card, and the type flips
   from near-white to ink. The scrim only ever existed because the
   artwork behind it was too busy to hold text — with no artwork behind
   the copy there is nothing left for it to do.

   The 35px nudges are Wayne's: card left, artwork right, so the pair
   sits 70px further apart than the gap alone gives. transform rather
   than margin so neither reflows the band or eats its padding. */
.category-hero-banner.hero-lightcard {
    background-image: none !important;
    background-color: #ffffff;
}

/* THE SCRIM HAS TO GO WITH THE PHOTOGRAPH.
   .category-hero-banner::before paints a 38% black gradient across the
   whole band so white copy stays legible on artwork. With no artwork
   behind the text and ink instead of white, it does nothing but turn a
   white band grey — which is exactly how it looked: computed style
   reported #ffffff while the eye saw mid-grey, because the colour was
   right and a layer on top of it was not. */
.category-hero-banner.hero-lightcard::before {
    content: none;
}

/* ALIGN THE BANNER TO THE CARD GRID BELOW IT.
   Wayne 2026-09-02: "Move the top left banner card so it lines up to the
   cards below... move the Everything We Sell snapshot so it lines up to
   the right of the cards below."

   THE EARLIER ±35px NUDGES WERE THE WRONG TOOL and are removed here.
   They pushed the pair outward from a container that was already
   misaligned, which is why the card overhung the grid on the left while
   the artwork overhung it on the right — 75px out one way and 45px the
   other, measured.

   The real cause is that the two containers do not share a box.
   main.site-main carries asymmetric padding — 64px left, 24px right —
   and the grid inside it adds 20px more, while this banner's content
   used a symmetric 40px. Matching those numbers here makes the card's
   left edge and the artwork's right edge land on the outer cards below,
   and keeps doing so as the page resizes, which a fixed nudge cannot.

   Desktop only: below 900px the split collapses to one column and the
   artwork is hidden, so the wide asymmetric padding would just waste
   space. */
@media (min-width: 901px) {
    /* A WIDER CARD, VIA EXPLICIT COLUMNS.
       Setting flex on the two children was tried first and broke the
       layout outright — the card grew to its 672px max-width, the art
       column collapsed, and the two overlapped. The theme's own split
       is a flex row whose sizing this was fighting rather than setting.

       A two-column grid states the answer instead of negotiating for
       it: the artwork gets a fixed 480px, which still exceeds the 438px
       it actually paints, and the card takes everything else. */
    .category-hero-banner.hero-lightcard .category-hero-banner-content--split {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 450px;
        /* STRETCH, so the artwork can be tied to the card's height.

           Wayne 2026-09-03: "Same height as the text card." A fixed
           max-height cannot do that — the card grows and shrinks with
           its copy, and any number picked today is wrong the next time
           the wording changes. Stretching both cells makes the row as
           tall as the card, and the rule below caps the artwork at 100%
           of that, so the two track each other for good.

           Centring is preserved by the flex rule under it rather than
           by align-items here; the pair still sits centred in the band,
           which is what was asked for earlier and still holds. */
        align-items: stretch;
    }

    /* THE ARTWORK MUST NOT SET THE ROW HEIGHT, or capping it at 100% of
       that row is circular — the image sizes itself, the row grows to
       fit it, and 100% is then whatever the image already was. That is
       why a first attempt at "same height as the card" left the art
       still running 60px past the card's bottom.

       Taking it out of flow settles it: the cell stretches to whatever
       height the CARD gives the row, and the picture fills that cell
       absolutely, contributing nothing of its own. */
    .category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare {
        position: relative;

        /* 100% OF A ROW THAT CAN BE SHORTER THAN THE BOARD IS NOT A
           FLOOR, IT IS A CEILING. Wayne 2026-09-03, from production:
           the X-ray board was clipped, with the domain field and the
           X-RAY IT button cut off along the bottom edge, while the same
           page rendered correctly locally.

           The cause was not CSS drift — the deployed stylesheet was
           byte-identical. The board is absolutely positioned inside
           this cell, so it cannot size its own parent; the cell takes
           its height from the TEXT COLUMN beside it, and that column is
           a paragraph whose length differs per install:

               hero lead 552 chars (local)       cell 249px   fits
               hero lead 245 chars (production)  cell 178px   clipped

           The board needs 212px of content plus padding. A layout that
           depends on the length of an unrelated sentence is not a
           layout, and an admin editing that paragraph in Site Whiz
           would have broken it again with no way of knowing why.

           max() keeps the original intent — stretch to the card's row
           whenever that row is taller — and adds the floor underneath
           it. */
        min-height: max(100%, var(--flapboard-content-height, 232px));
    }

    .category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare img {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        max-height: none;
        object-fit: contain;
        object-position: right center;
    }

    /* The card must be allowed to use the column it was just given —
       the theme caps this element at 672px. */
    .category-hero-banner.hero-lightcard .category-hero-banner-headline {
        max-width: none;
    }

    .category-hero-banner.hero-lightcard .category-hero-banner-content {
        /* ZERO, AND THE COMMENT THAT USED TO SIT HERE PREDICTED IT.

           This carried 64px/24px, copied from main.site-main's own
           asymmetric padding so the card and the artwork would land on
           the ends of the rule beneath them. It ended with: "Change
           these only if main's padding changes, and then to whatever
           main's padding becomes."

           On 2026-09-03 main's padding became NOTHING — it was made a
           real .contentcontainer, which is 90%/1250px centred with no
           horizontal padding at all. So these two numbers went on adding
           an inset that no longer exists anywhere else on the page.
           Measured at a 1905px viewport: header and main both sat at
           328, this banner's card at 392 and its artwork's right edge at
           352. Sixty-four and twenty-four, exactly.

           It was wrong on shop.php too, which uses this same partial and
           has been off by the same amounts since the treatment shipped.
           Neither page was noticeably broken, which is why it survived —
           a banner inset by 64px still looks like a banner. */
        padding-left: 0;
        padding-right: 0;
    }
}

.category-hero-banner.hero-lightcard .category-hero-banner-headline {
    background: #fdf3ea;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 0;
    border-left: 4px solid #f09546;
    border-radius: var(--border-radius-default);
    /* HEIGHT, PULLED DOWN FROM THREE DIRECTIONS.
       At 1.9rem padding the card stood 268px in a 391px band — 69% of
       it, for one heading and a paragraph. Padding alone could not fix
       that, because most of the height is line boxes, not space: six
       lines at 25.28px is 152px on its own.

       So all three levers move together — tighter vertical padding, a
       tighter line-height, and a wider column (the art column dropped to
       450px, still comfortably above the 438px the picture paints) so
       the paragraph wraps to fewer lines in the first place. */
    padding: 1.15rem 2.1rem;
    box-shadow: 0 2px 4px rgba(36, 49, 65, 0.04), 0 18px 44px rgba(36, 49, 65, 0.13);
}

/* Ink, not near-white. #243141 on #fdf3ea is 12.06:1; the orange this
   heading used to use is 2.11:1 on the same cream and cannot be kept. */
/* !important, and not by preference. The vendor paints this heading with
   `.category-hero-banner-headline h1 { color: ... !important }`, so no
   selector of mine outranks it — the rule below without !important
   resolved to white on cream, which is 1.06:1 and was reported as the
   heading having vanished. */
body.template-nebulous .category-hero-banner.hero-lightcard .category-hero-banner-headline h1 {
    color: #243141 !important;
    text-shadow: none !important;
}
body.template-nebulous .category-hero-banner.hero-lightcard .category-hero-banner-headline p {
    color: #55606c !important;
    text-shadow: none !important;
    /* 15.8px, one down from the inherited 16.8. Line-height follows it
       proportionally rather than staying at the old 26.88px, or the
       measure would open up as the type came down. */
    font-size: 15.8px;
    line-height: 1.5;
}

/* The snapshot supplies its own browser chrome, so the frame, bar and
   shadow this component normally draws around a screenshot would be a
   second window drawn around the first. */
.category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
}

/* THE ART HAS TO FIT THE BAND. Unconstrained it rendered 482px tall
   inside a 391px banner and overflowed upward into the header — the
   snapshot appeared to sit on top of the toolbar. object-fit keeps its
   proportions while the max-height keeps it inside the band, and the
   band's own padding is subtracted so it never touches the edges. */
.category-hero-banner.hero-lightcard .category-hero-banner-screenshot--bare img {
    border-radius: 0;
    box-shadow: none;
    width: 100%;
    height: auto;
    /* 100% of the cell, which the grid has just made equal to the card.
       The band-height cap stays as a floor-of-last-resort for a page
       whose card is somehow taller than the banner itself. */
    max-height: min(100%, calc(var(--page-banner-height, 391px) - 3rem));
    object-fit: contain;
    /* RIGHT, NOT CENTRE — and the difference is the whole bug.
       object-fit: contain letterboxes the artwork inside its box, so
       the box's right edge and the PAINTED artwork's right edge are not
       the same thing. The box was correctly aligned to the card grid
       while the picture inside it stopped ~180px short, which is what
       Wayne could see and my measurement could not: I had measured the
       container, not the pixels.
       Anchoring the painted content to the right of the box puts the
       artwork itself on the grid's edge, and lets the slack fall in the
       gap between card and art where it belongs. */
    object-position: right center;
}

/* The tagline now sits above a feature list rather than alone, so it
   needs a gap under it — it is the summary, the bullets are the
   substance. */
.pricing-card-body .pricing-card-tagline {
    margin: 0 0 0.85rem;
}

/* NO POOLED EMPTY SPACE AT THE BOTTOM OF A CARD.
   Wayne 2026-09-02: "I want them all to be filled the same amount with
   no empty space."

   Sixteen cards describing sixteen different products cannot have
   identical content heights, and adding filler lines to force it would
   be writing copy to fill a box. Every card now carries a tagline and
   five real features; what remained was 24px to 126px of slack, pooled
   under the last bullet, because each card stretches to the tallest in
   its row.

   So the slack is DISTRIBUTED rather than removed: the list grows to
   fill whatever height the row gives it and spaces its own items
   through that height. A short card reads as airy and a tall one as
   dense, and neither has a hole above the price. */
.pricing-card-body {
    display: flex;
    flex-direction: column;
}

.pricing-card-body ul {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

/* ---- LIGHT-CARD HOMEPAGE SLIDER --------------------------------------
   The same treatment as the static-page light-card banner, applied to
   the carousel. Opt-in via $heroLightCard so category sliders, whose
   artwork still has a dark ground baked into it, are untouched.

   Everything here reverses the dark treatment: the band stops carrying
   a photograph, the copy panel stops being a 60%-black scrim and
   becomes a cream card, and the type flips from near-white to ink. */
.nebulous-slider.hero-lightcard-slider .banner .background {
    background-image: none !important;
    background-color: #ffffff;
    /* CENTRE THE COPY IN THE BAND. Wayne: "bring the left card and
       buttons horizontally even in the banner height."

       The container's own 16px top padding left the group 16px from the
       top and 33px from the bottom — off-centre by 8px. Centring with
       flex rather than trimming that padding matters because the eleven
       slides differ in height: a fixed padding is only correct for the
       slide it was measured on, whereas this holds for all of them. */
    display: flex;
    align-items: center;
}

.nebulous-slider.hero-lightcard-slider .banner .contentcontainer {
    /* NO width:100% HERE — it used to be, and it was the second half of
       the misalignment. .contentcontainer is width:90%/max-width:1250px;
       overriding the width to 100% left the max-width in place, so above
       ~1390px the cap made it look correctly aligned and below that it
       silently went full-bleed. At a 1165px viewport the hero column ran
       edge to edge while the header beside it sat at 58px. Inheriting the
       shared width is what makes the agreement hold at every size. */
    /* Centred, then nudged down 14px on Wayne's eye — the optical centre
       of this band sits slightly below its geometric one, because the
       carousel's pager dots hang beneath the band and pull the apparent
       middle upward. transform rather than padding so the centring above
       still does the work and this only offsets the result. */
    transform: translateY(14px);
}

/* The scrim goes with the photograph — with ink on a light card it does
   nothing but turn a white band grey. */
.nebulous-slider.hero-lightcard-slider .banner .background::before {
    content: none;
}

/* ALIGN THE SLIDE TO THE PAGE, not to the edge of the band.
   Wayne 2026-09-02: "Making it go too far left?" It was — by 64px.

   .contentcontainer carries `padding: 16px 0` — no horizontal padding at
   all — so the card began at the band's own content edge (150px) while
   everything below it starts at 214px, where main.site-main's 64px left
   padding puts the section rule and the lede.

   Repeating main's padding here lines the card's left edge and the
   artwork's right edge up with the rule under them, and keeps doing so
   at any width, because both boxes are the same 1250px and start at the
   same x. Same reasoning, same numbers as the shop.php banner. */
@media (min-width: 901px) {
    .nebulous-slider.hero-lightcard-slider .banner .contentcontainer {
        /* NO HORIZONTAL PADDING, deliberately.

           This used to be 84px/45px — numbers reached by nudging the
           card and the artwork until they sat on the row of cards below
           AT ONE PARTICULAR WIDTH. They were measured honestly and they
           were still wrong, because a fixed pixel inset cannot track a
           percentage container: the moment the window changed size the
           hero and the page below drifted apart, which is exactly the
           mess Wayne reported.

           Zero here means this column IS .contentcontainer — the same
           90%/1250px box as the header, the toolbar and main. The card,
           the buttons and the artwork now line up with everything else
           by construction rather than by coincidence, at every width. */
        padding-left: 0;
        padding-right: 0;
        box-sizing: border-box;
    }

    /* The artwork has to HUG that edge, not merely sit inside it.
       Wayne: "Even with the cards below." The picture was ending 17px
       short of the cards while its column ended in the right place —
       the column was aligned and the image inside it was not, which is
       the same distinction that caught me on the shop banner. */
    .nebulous-slider.hero-lightcard-slider .banner-twocolumn-col2,
    .nebulous-slider.hero-lightcard-slider .banner picture {
        display: block;
        margin-left: auto;
        margin-right: 0;
        text-align: right;
    }

    /* LIFTED. Once the artwork grew to 330px it sat 60px from the top of
       the band and 1px from the bottom — hanging off the floor rather
       than centred, because the column it lives in aligns to the bottom
       and the container carries the 14px downward nudge meant for the
       card. Wayne settled on 23px rather than the 30 that would have
       centred it geometrically — the artwork reads better sitting a
       little below the middle, because its own content is top-weighted
       (the browser chrome and title bar) while the phone hangs lower. */
    .nebulous-slider.hero-lightcard-slider .banner picture {
        transform: translateY(-23px);
    }

    .nebulous-slider.hero-lightcard-slider .banner picture img {
        display: block;
        margin-left: auto;
        margin-right: 0;
        /* BIGGER, AND REACHING FURTHER LEFT. Wayne: "snapshot a little
           bigger so it fills a bit more space and fills to the left a bit
           more."

           It was 350px wide and 272 tall inside a 448px column — not even
           filling the column it already had, leaving a 154px gap to the
           card. Driving it from HEIGHT rather than width is what moves the
           left edge: at 330px tall its own 1.29 ratio makes it 425 wide,
           so it grows toward the card while its right edge stays pinned to
           the cards below.

           330 rather than more because the band is 391: taller than this
           and the artwork starts colliding with the band's own edges once
           the copy is centred beside it. */
        height: 330px !important;
        width: auto;
        max-width: 100%;
        /* !important, and it is required rather than lazy. The category
           slider caps this image at `max-height: 17rem` — 272px, which is
           exactly the size it was stuck at — through
           `#category-hero-slider .banner.banner-designscreenshot picture
           img`. That is an ID selector, so no number of classes on my side
           outranks it, and matching the ID here would tie this rule to one
           particular slider id. The cap is deliberate for the dark
           category banners and simply wrong for this one. */
        max-height: calc(var(--page-banner-height, 391px) - 3.5rem) !important;
    }
}

.nebulous-slider.hero-lightcard-slider .banner-headline-panel {
    /* 41rem rather than the inherited 46 — the panel was 688px wide and
       the artwork beside it only 350px, which read as one big card with a
       picture tacked on rather than a pair. */
    max-width: 41rem;
    background: #fdf3ea;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 0;
    border-left: 4px solid #f09546;
    border-radius: var(--border-radius-default);
    padding: 1.15rem 2.1rem;
    box-shadow: 0 2px 4px rgba(36, 49, 65, 0.04), 0 18px 44px rgba(36, 49, 65, 0.13);
}

/* Ink, not orange. #243141 on #fdf3ea is 12.06:1; the orange this
   heading uses on the dark band is 2.11:1 on cream and cannot stay. */
/* SCALED DOWN — Wayne 2026-09-02: "maybe the left card text and card are
   little too big?" It was: 48px heading, 21px subhead and 19.2px features
   put the panel at 72% of a 391px band, against 58% for the same
   treatment on shop.php. These sit between the two — still larger than an
   inner page, because this is the homepage hero, but no longer crowding
   the band it lives in. */
body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner-heading {
    color: #243141 !important;
    text-shadow: none !important;
    font-size: 2.25rem;
    line-height: 1.15;
}

body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner-text,
body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner .list ul li {
    color: #55606c !important;
    text-shadow: none !important;
}

body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner-text {
    font-size: 1.0625rem;
    line-height: 1.5;
}

body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner .list ul li {
    font-size: 1rem;
}

/* GREEN CHECK MARKS, NOT ORANGE DOTS — Wayne 2026-09-03, at the same
   time the lists went from four items to six.

   The glyph is Font Awesome's own fa-check (\f00c) rather than a "✓"
   character, because that character's shape and weight change with
   whatever font resolves it. The family is "Font Awesome 5 PRO" — the
   webfont bundled with the purchased WizardPanel addon is 5.10.1 Pro,
   and its @font-face families are literally named that. Writing
   "Font Awesome 6 Pro" here, which the vendor CSS does elsewhere,
   renders nothing at all and does so silently. See the note at the top
   of this file about the black-arrow nav bug, which was this exact
   mistake.

   #177245 sits at 5.44:1 on the card's #fdf3ea — measured, not picked.
   That is deliberately close to the body text's own 5.86:1, so the
   ticks read as part of the list. The orange they replace was 2.11:1,
   which is why it worked as a small dot and would not have worked as a
   glyph with detail in it. */
body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner .list ul li {
    position: relative;
    padding-left: 26px;
    background-image: none;
    list-style: none;
    /* TIGHTER, because there are now three rows per column rather than
       two and the panel has to stay inside a fixed-height band. Wayne:
       "reduce the padding on those items so the lines are a bit closer
       together." */
    margin-bottom: 0.1rem;
    line-height: 1.38;
}

body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner .list ul.list-marker-check li::before {
    content: "\f00c";
    font-family: "Font Awesome 5 Pro";
    font-weight: 900;
    font-size: 0.86em;
    position: absolute;
    left: 0;
    top: 0;
    line-height: inherit;
    color: #177245;
}

/* ---- THE DARK BANNER GETS THE SAME LIST TREATMENT (2026-09-03) ----

   Wayne, looking at VX Compute: "The bullet check mark features are in
   bold and too large", and the buttons were "pushing down below the
   banner border".

   BOTH ARE THE SAME CAUSE, AND IT IS A CONSEQUENCE OF MOVING THESE
   CATEGORIES OFF THE LIGHT CARD. Everything above — 1rem text, tight
   rows, green ticks — is scoped to .hero-lightcard-slider. The six
   hosting categories wore that treatment until their screenshots were
   hidden today, which drops them to the dark banner, where nothing
   overrode the vendor's own `.banner .list ul li { font-size: 1.2em }`.
   So the features jumped from 16px to 19.2px and their ticks vanished,
   and neither change was written anywhere: they fell out of a rule
   that no longer matched.

   THE SIZE IS ALSO WHY THE BUTTONS FELL OUT OF THE BANNER. Measured on
   VX Compute: the content column was 447px inside a band fixed at
   391px with overflow hidden, so the buttons sat 40px below the edge
   and were clipped. At 19.2px "Sized for memory-hungry workloads"
   wraps to a second line, which adds a whole 36px row to a list that
   had no room for one. Making the text 1rem removes the wrap, and the
   row is recovered rather than the band being grown — the band is
   fixed on purpose ("make all banners except the ones on index.php the
   same size. Exactly the same size.").

   THE GREEN IS NOT THE LIGHT CARD'S GREEN. #177245 was solved against
   the cream card and measures 3.29:1 on this panel's rgba(10,12,16,.6)
   — under AA, and visibly muddy. #2ea55c measures 6.20:1 there and is
   already this stylesheet's dark-mode green, so the ticks match what
   dark mode uses elsewhere rather than introducing a third green.

   Font Awesome 5 PRO, not 6: same trap as the nav arrows and the ticks
   above. Naming 6 renders nothing, silently.
   ------------------------------------------------------------------ */
body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner .list ul li {
    font-size: 1rem;
    position: relative;
    padding-left: 26px;
    background-image: none;
    list-style: none;
    margin-bottom: 0.1rem;
    line-height: 1.38;
}

/* THE PANEL STILL RAN 15px PAST THE BAND after the type came down, so
   the spacing gives the rest back.

   Measured on VX Compute, the worst case in the catalogue: 447px of
   content in a 391px band before any of this, 406px after the features
   went from 19.2px to 1rem, and 385px after the three numbers below.
   Every other dark banner has slack, so this is sized for the tightest
   one rather than the average.

   Only the SPACING moves. The heading stays at 48px and the subhead at
   21px, because Wayne asked for the features to be smaller than the
   description and not for the description to shrink — the light-card
   treatment scaled its whole panel down on 2026-09-02 and that was a
   separate decision about the homepage hero, not a house rule to copy
   here uncommanded. */
body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner-headline-panel {
    padding: 0.75rem 1.375rem;
    margin-bottom: 0.5rem;
}

body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner-headline-panel .banner-text {
    margin-bottom: 0.625rem;
}

body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner-headline-panel .banner-heading {
    margin-bottom: 0.375rem;
}

/* The vendor gives every feature row 5px of padding top AND bottom, on
   top of its own line box. At three rows per column that is 30px of
   pure air inside a band with none to spare — the single largest piece
   of slack in the panel, and invisible because padding on a row with
   no background looks like nothing at all. 2px keeps the rows from
   touching without paying for the other 18. */
body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner .list ul li {
    padding-block: 0.125rem;
}

body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner .list ul.list-marker-check li::before {
    content: "\f00c";
    font-family: "Font Awesome 5 Pro";
    font-weight: 900;
    font-size: 0.86em;
    position: absolute;
    left: 0;
    top: 0;
    line-height: inherit;
    color: #2ea55c;
}


/* ---- THE OTHER TWO MARKERS (migration 146) ---------------------------

   Wayne asked for "a bullet or green checkmark to the left of each
   feature". The tick above was a stylesheet rule applied to every row,
   so there was nothing to choose between; these give the other two
   answers, and the class comes from the slide's own feature_marker.

   The indent is set by the LIST, not by each marker, so switching
   between them cannot shift the text sideways — the whole point of a
   choice is that only the glyph changes.

   'none' still keeps its padding-left. A list that re-flows to the
   margin when you turn the ticks off looks like a different component,
   and lining up with the rows above and below it is worth 26px of
   space that is doing nothing else. */
body.template-nebulous .banner .list ul.list-marker-bullet li,
body.template-nebulous .banner .list ul.list-marker-none li {
    position: relative;
    padding-left: 26px;
    background-image: none;
    list-style: none;
}

body.template-nebulous .banner .list ul.list-marker-bullet li::before {
    /* A real bullet character, not a disc list-style: the row is
       position:relative with its own padding, so a list marker would
       sit outside it and ignore the indent everything else uses. */
    content: "\2022";
    position: absolute;
    left: 6px;
    top: 0;
    line-height: inherit;
    font-size: 1.1em;
}

/* Inherit the surrounding text colour rather than naming one: this
   list renders on a cream card and on a near-black panel, and a bullet
   is the same ink as the words it belongs to in both. The tick is the
   exception, and says so with its own colour. */
body.template-nebulous .nebulous-slider.hero-lightcard-slider .banner .list ul.list-marker-bullet li::before {
    color: #55606c;
}

body.template-nebulous .nebulous-slider:not(.hero-lightcard-slider) .banner .list ul.list-marker-bullet li::before {
    color: #f1f1f1;
}

body.template-nebulous .banner .list ul.list-marker-none li::before {
    content: none;
}

/* ---- THE SLIDE KEEPS A STALE PIXEL WIDTH ON RESIZE (2026-09-03) ----

   Wayne: "When i shrink my browser the page elements shift! ... It
   turns into a complete mess."

   bxSlider writes an INLINE width in pixels onto every <li> at init
   (jquery.bxslider's redrawSlider -> outerWidth(getSlideWidth())) and
   only recomputes it from its own window-resize handler. Measured live:
   at a 1481px viewport the slides still carried width:1513px, and after
   another resize they sat at 1249px inside a 1617px viewport and stayed
   there. Everything inside a slide is then laid out against a width the
   window no longer has, which is exactly the shifting Wayne describes —
   the card and the artwork move because their container is the wrong
   size, not because their own rules changed.

   BOTH SLIDERS ON THIS SITE RUN mode:'fade' (nebulous.js has a single
   bxSlider init, for #nebulous-hero-slider and #category-hero-slider),
   and a fade slide is absolutely positioned and stacked — its pixel
   width carries no layout meaning at all, unlike a horizontal track
   where the widths add up to the strip being translated. So CSS can own
   it outright.

   !important is required and is not laziness: it is beating an inline
   style, which is the only thing that can. If a slider is ever added in
   'horizontal' mode this rule must be scoped away from it. ---- */
.bx-viewport > ul.nebulous-slider > li {
    width: 100% !important;
}

/* The snapshot carries its own browser chrome, so this component's own
   frame and bar would draw a second window around the first. */
.nebulous-slider.hero-lightcard-slider .banner picture::before {
    content: none !important;
}

.nebulous-slider.hero-lightcard-slider .banner.banner-design2column.banner-designscreenshot picture img {
    border-radius: 0;
    box-shadow: none;
}

/* ==================================================================
   THE CONTACT FORM on /contact-us.php.

   PAGE-SCOPED, NOT A CHANGE TO .qr-form. Those .qr-* rules above are
   shared with /quote.php, so every rule here is nested under
   .contact-panel — the quote form keeps exactly the look it has, and
   this one can be as elaborate as it likes without a second page
   quietly changing behind our back.

   Colour comes from the theme's own tokens, not hex. A licensee who
   rebrands gets their accent here for free; hardcoding the orange
   would leave one permanently orange form on an otherwise blue site.
   Hex values are fallbacks only.
   ================================================================== */

.contact-panel {
    position: relative;
    /* .qr-page caps itself at 56rem for a nine-field quote form. Here
       the panel sits directly under .product-overview-card, which
       takes the container's full width — a narrower form below a
       full-width card reads as a mistake, so match it. */
    max-width: none;
    margin: 0 0 3rem;
    padding: 2.4rem 2.6rem 2.2rem;
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: var(--border-radius-default, 10px);
    /* Warm at the top-left, settling to the ordinary box background —
       the tint of the banner card carried a little way into the panel
       so the two read as one page. */
    background:
        radial-gradient(120% 90% at 0% 0%, rgba(240, 149, 70, .09), transparent 58%),
        var(--color-box-background, #fff);
    box-shadow:
        0 1px 2px rgba(17, 24, 39, .04),
        0 26px 55px -34px rgba(17, 24, 39, .38);
    overflow: hidden;
}

/* The accent rail, down the LEFT edge. Wayne 2026-09-03: "put the
   orange highlight on the left side of the form instead of the top."

   It was across the top, on the reasoning that a rotated rail would
   still rhyme with the card above. Matching that card's edge exactly
   is the better call: .product-overview-card sits directly above this
   panel with 4px of accent down its own left side, so the two now
   share one continuous vertical line rather than an L. */
.contact-panel::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 4px;
    background: var(--color-primary-background, #f09546);
}

/* A large, very faint ring in the corner. At this opacity it is
   texture rather than decoration — visible as depth, not as a shape
   competing with the fields. */
.contact-panel::after {
    content: "";
    position: absolute;
    right: -3.5rem;
    bottom: -4.5rem;
    width: 17rem;
    height: 17rem;
    border-radius: 50%;
    border: 2.5rem solid rgba(240, 149, 70, .05);
    pointer-events: none;
}

.contact-panel > * { position: relative; z-index: 1; }

/* .qr-step is itself a bordered card on /quote.php, where it separates
   one step of nine fields from the next. Inside this panel there is
   only one step, so its border would draw a card inside a card. */
.contact-panel .qr-step {
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
}

/* ---- heading ---- */

.contact-panel .qr-head {
    margin: 0 0 1.6rem;
    padding-bottom: 1.1rem;
    border-bottom: 1px solid var(--color-box-background-border, #e5e7eb);
}

.contact-panel .qr-head h2 { font-size: 1.45rem; }
.contact-panel .qr-head p {
    font-size: .98rem;
    /* The theme caps this at 58ch, which is right for the 56rem quote
       page. In a full-width panel it strands the last two words on a
       line of their own. */
    max-width: 82ch;
}

/* ---- fields ----
   The .contact-panel prefix is what carries these past the theme's own
   `body.template-nebulous input` rules. Those are (0,1,2) and beat any
   single-class selector — the trap that has bitten five other
   components on this site. Never fixed with !important. */

.contact-panel .qr-field { margin: 0 0 1.15rem; }

.contact-panel .qr-field-row {
    /* auto-fit at 17rem drops to one column early on a two-field row.
       Name and email belong side by side at any desktop width. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 1.1rem;
}

.contact-panel .qr-label {
    margin: 0 0 .4rem;
    font-size: .76rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--color-body, #6b7280);
}

.contact-panel .qr-optional {
    font-size: .72rem;
    letter-spacing: .03em;
    text-transform: none;
}

/* THE ATTRIBUTE IS LOAD-BEARING, not decoration. The theme's own
   `body.template-nebulous input[type="text"]` block is (0,2,2); a
   plain `.contact-panel .qr-field input` is only (0,2,1) and loses,
   which is how the radius here silently stayed 6px on the first pass.
   Naming the type takes this to (0,3,1) and wins honestly. Sixth time
   this exact trap has come up on this site — never !important. */
.contact-panel .qr-field input[type="text"],
.contact-panel .qr-field input[type="email"],
.contact-panel .qr-field textarea {
    padding: .8rem .95rem;
    font-family: inherit;
    font-size: .95rem;
    line-height: 1.5;
    color: var(--color-heading, #111827);
    border: 1px solid var(--color-box-background-border, #e5e7eb);
    border-radius: 10px;
    background: #fff;
    /* An inset hairline instead of a heavier border — the field reads
       as a well to write in rather than an outlined box. */
    box-shadow: inset 0 1px 2px rgba(17, 24, 39, .05);
    transition: border-color .14s ease, box-shadow .14s ease;
}

.contact-panel .qr-field textarea { min-height: 9.5rem; }

.contact-panel .qr-field input[type="text"]::placeholder,
.contact-panel .qr-field input[type="email"]::placeholder,
.contact-panel .qr-field textarea::placeholder { color: #9ca3af; }

.contact-panel .qr-field input[type="text"]:hover,
.contact-panel .qr-field input[type="email"]:hover,
.contact-panel .qr-field textarea:hover { border-color: #d1d5db; }

/* Focus is a real ring. It is the one state a keyboard user depends
   on entirely, and it is the state most often deleted in the name of
   tidiness. */
.contact-panel .qr-field input[type="text"]:focus,
.contact-panel .qr-field input[type="email"]:focus,
.contact-panel .qr-field textarea:focus {
    outline: none;
    border-color: var(--color-primary-background, #f09546);
    box-shadow:
        inset 0 1px 2px rgba(17, 24, 39, .03),
        0 0 0 3px rgba(240, 149, 70, .22);
}

/* ---- send ----
   Button first, captcha to its right. Wayne 2026-09-03.

   align-items:center rather than baseline: the Turnstile widget is an
   iframe of a fixed 65px height that this stylesheet does not control,
   so the two are centred against each other instead of being asked to
   share a text baseline the iframe does not have.

   The row degrades to just the button when the captcha is switched off
   or unconfigured, because widgetFor() returns an empty string then. */
.contact-panel .qr-send-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1rem;
    margin-top: .4rem;
}

/* Turnstile renders its own iframe at a fixed size; stop the theme's
   generic form spacing from adding a margin that breaks the alignment. */
.contact-panel .qr-send-row .cf-turnstile {
    margin: 0;
    line-height: 0;
}

.contact-panel .qr-submit {
    min-width: 13rem;
    /* The row owns the top margin now; leaving it here too would double
       the gap above the button. */
    margin-top: 0;
    transition: transform .14s ease, box-shadow .14s ease, filter .14s ease;
}

.contact-panel .qr-submit:hover {
    filter: brightness(1.04);
    transform: translateY(-1px);
    box-shadow: 0 12px 26px -10px rgba(224, 123, 42, .8);
}

.contact-panel .qr-submit:active { transform: translateY(1px); }

/* ---- the confirmation ----
   It replaces the form entirely, so it has to feel like an arrival
   rather than a panel someone emptied. */
.contact-panel .qr-done { padding: 1.5rem 0 1rem; }

@media (prefers-reduced-motion: reduce) {
    .contact-panel .qr-field input[type="text"],
    .contact-panel .qr-field input[type="email"],
    .contact-panel .qr-field textarea,
    .contact-panel .qr-submit { transition: none; }

    .contact-panel .qr-submit:hover,
    .contact-panel .qr-submit:active { transform: none; }
}

@media (max-width: 760px) {
    .contact-panel { padding: 1.5rem 1.3rem 1.4rem; }
    .contact-panel .qr-field-row { grid-template-columns: minmax(0, 1fr); }
    .contact-panel .qr-send-row { gap: .9rem; }
    .contact-panel .qr-submit { width: 100%; }
    .contact-panel::after { display: none; }
}


/* ==================================================================
   FAQ, wide variant — .faq-section.faq-wide

   Wayne 2026-09-03: "make the FAQ box 2 columns instead of one, it's
   very tall and needs to be wider and shorter in height."

   OPT-IN, because .faq-section and .faq-list are shared with four
   other pages. Only /web-app-development.php carries .faq-wide, so
   product.php, quote.php and the marketing partials keep the layout
   they have.

   EVERY SELECTOR HERE IS PREFIXED body.template-nebulous ON PURPOSE.
   The theme already styles .faq-list at (0,2,1) in two places — the
   card-border sweep and a background rule with a dark-mode partner.
   A plain .faq-wide .faq-list is (0,2,0) and loses to both, so the
   container would have kept its border and fill while the items grew
   their own: a card drawn inside a card. Same trap that has cost this
   site six components. Never !important.
   ================================================================== */

body.template-nebulous .faq-section.faq-wide {
    /* One column: the intro sits above the questions rather than in a
       narrow rail beside them, which is what freed the width. */
    grid-template-columns: minmax(0, 1fr);
    gap: 1.75rem;
}

/* The intro reads as a line, not a sidebar, now that it is full width. */
body.template-nebulous .faq-wide .faq-panel {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1.5rem;
}

body.template-nebulous .faq-wide .faq-panel h3 { margin: 0; }
body.template-nebulous .faq-wide .faq-panel p { margin: 0; flex: 1 1 22rem; }

/* The LIST stops being the card and becomes the grid; each question
   becomes its own card. Done this way round because the stock look is
   one bordered box with hairline dividers, and hairlines do not
   survive being split into two columns — the bottom row would meet the
   container edge and draw a double line, and details:last-child would
   strip the rule from one column only. Per-item borders have no edge
   cases. */
body.template-nebulous .faq-wide .faq-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* No ROW gap, a column gap only. Wayne 2026-09-03: "remove the
       padding in between the rows." The two columns still need air
       between them or the questions read as one run-on block. */
    gap: 0 1rem;
    /* start, not stretch: opening one question must not stretch the
       one beside it to match. */
    align-items: start;
    border: 0;
    border-radius: 0;
    background: none;
    overflow: visible;
}

body.template-nebulous .faq-wide .faq-list details,
body.template-nebulous .faq-wide .faq-list details:last-child {
    border: 1px solid var(--color-border);
    /* Square, and pulled up by exactly the border width. With no row
       gap two adjacent 1px borders would meet and draw a 2px line,
       which reads as a mistake next to every other hairline on the
       page; -1px collapses the pair into one. Rounded corners would
       also stop making sense once the rows sit flush. */
    border-radius: 0;
    margin-top: -1px;
    background: var(--color-box-background);
    overflow: hidden;
}

/* The top and bottom of each column keep a corner, so the pair still
   reads as two panels rather than two cut-off strips. */
body.template-nebulous .faq-wide .faq-list details:nth-child(1),
body.template-nebulous .faq-wide .faq-list details:nth-child(2) {
    margin-top: 0;
    border-top-left-radius: var(--border-radius-default);
    border-top-right-radius: var(--border-radius-default);
}

body.template-nebulous .faq-wide .faq-list details:nth-last-child(1),
body.template-nebulous .faq-wide .faq-list details:nth-last-child(2) {
    border-bottom-left-radius: var(--border-radius-default);
    border-bottom-right-radius: var(--border-radius-default);
}

/* The dark-mode fill the shared component sets on the container has to
   follow the fill down to the item, or every question turns white on a
   dark page. */
body.colortoggle-dark.template-nebulous .faq-wide .faq-list details,
body.colortoggle-dark.template-nebulous .faq-wide .faq-list details:last-child {
    background: #2f2f2f;
}

@media (max-width: 780px) {
    /* Two columns of accordion on a phone is worse than one long one. */
    body.template-nebulous .faq-wide .faq-list {
        grid-template-columns: minmax(0, 1fr);
        gap: 0;
    }

    body.template-nebulous .faq-wide .faq-panel { display: block; }
    body.template-nebulous .faq-wide .faq-panel p { margin: 0 0 1.25rem; }
}


/* ==================================================================
   THE CHECKOUT PROGRESS METER

   Wayne 2026-09-03: "redesign the progress meter to something that
   looks higher tech." Four directions were built and compared on a
   scratch page; he chose the schematic, and the page and the three
   unchosen variants were deleted rather than left as CSS nothing can
   reach.

   IT IS DRAWN ON li::after AND .cart-step-inner, AND THAT IS
   NOT AN AESTHETIC CHOICE. Nine cart-template stylesheets restyle this
   component under `body.cart-theme-{key} .cart-steps li` — (0,3,2),
   and header.php loads them AFTER nebulous.css on purpose, so they win
   on source order as well as specificity. That is why every rule in
   the shipped block carries !important, and why Nexus's pill once beat
   this component live.

   Checked rather than assumed: 0 of those 9 files touch li::after and
   0 touch .cart-step-inner. Building on those two surfaces means none
   of the options below needs a single !important, and no cart theme
   can take them.

   THE RAIL IS ANCHORED TO THE li, NEVER TO .cart-step-inner. The li is
   flex:1 so all four are exactly equal; the inner is inline-flex and
   shrink-wraps its text, so "Cart" and "Confirmation" produce very
   different widths. A connector anchored to the inner is ragged by
   construction — visible the moment the label is long.

   NO KEYFRAMES IN ANY OF THE FOUR. This bar is on the page while
   someone types card details. An infinite animation keeps the
   compositor awake for that entire time, contends with Stripe.js on a
   low-end phone, and pulls the eye to the chrome and away from the
   total. There are also three separate kill switches in this app that
   would each have to be honoured — prefers-reduced-motion,
   body.whiz-animations-off, and dark mode — and missing any one is a
   bug. One-shot transitions only.
   ================================================================== */

/* THE FILL HAD TO BE BEATEN, NOT JUST OVERWRITTEN, and this is why the
   rule below names li.active when it looks like it does not need to.

   The pale-pill rule this treatment replaces is still in the file:
   `.cart-steps li.active .cart-step-inner`, which is (0,3,1). Written
   the obvious way — `.cart-steps.cart-steps-schematic .cart-step-inner`
   — this would be (0,3,0) and would LOSE to it, even carrying
   !important and sitting later in the file, because specificity is
   resolved before source order within one origin. Naming li.active
   takes it to (0,4,1).

   Caught by looking at the rendered page, not the CSS: the rail drew
   correctly with the old pale pill still sitting on top of it. Eighth
   time this stylesheet has produced this exact class of bug.

   Below: a positioning context and the overflow guard.
   .cart-steps itself is overflow:visible !important, so nothing can be
   clipped there; the li is free and uncontested. */
.cart-steps.cart-steps-schematic li { position: relative; min-width: 0; }

/* Fixes a latent bug present today, independent of which option wins:
   between 640px and ~800px each step gets ~165px while
   "STEP 4 CONFIRMATION" needs more, so it wraps to two lines and the
   bar's height jumps on the confirmation page only. */
/* position:relative HERE IS LOAD-BEARING. The pads and the trace are
   absolutely positioned, so without it they anchor to the nearest
   positioned ancestor — the page — and the whole component flies to
   the top of the viewport and sits over the header. That happened for
   real while this was being built: the rule was scoped to the scratch
   page's class and never reached the live one, which looked fine on
   the comparison page and broke instantly on the real cart. */
.cart-steps.cart-steps-schematic .cart-step-inner {
    padding-inline: clamp(0.4rem, 2.2vw, 1.5rem);
    min-width: 0;
    /* VERTICAL PADDING GOES TO ZERO, and this is what "still too high"
       actually was. The base .cart-step-inner carries padding:
       0.7rem 1.5rem from when the active step was a filled pill — the
       padding existed to give that fill some body. There is no fill
       here any more, so all it does is add 11.2px of invisible space
       under the label.
     *
       That is why the first attempt measured as centred and still
       looked wrong: the BOX was balanced (5px above, 6px below) while
       the visible TEXT had 5px above and 17px below. Measuring the
       element instead of the ink is how you get a layout that is
       correct on paper and top-heavy on screen. */
    padding-block: 0;
}

/* ---- THE STEP METER: bus and node schematic ---------------------
   Chosen by Wayne on 2026-09-03 from four options reviewed by a
   design, front-end and conversion pass. The other three lived
   alongside this one until the comparison page was deleted; they are
   gone with it rather than left as CSS nothing can reach.
   ------------------------------------------------------------------
   Square pads on a trace. Solid trace behind what is done, dotted
   ahead — the routed/unrouted convention, borrowed rather than
   invented. Squares read fabricated where circles read friendly. */
/* CENTRED, AND THE ARITHMETIC IS WORTH KEEPING because three numbers
   depend on each other.
 *
   Measured before changing anything: 38px of space above the group and
   11px below — the whole thing sat high with dead space underneath.
   Wayne 2026-09-03: "needs to be centered", and "put the text a little
   closer to the icons, 5px closer?".
 *
   The li's padding-top does NOT move the pad. For an absolutely
   positioned box the containing block is the padding box of the
   positioned ancestor, and top:0 is that box's top edge — so the pad
   is moved by its own `top`, and the trace has to move with it or the
   two part company.
 *
       pad     top 11px, 28px tall -> occupies 11-39px
       trace   top 24px            -> 11 + 14 (pad half) - 1 (half rule)
       label   starts at padding-top 2.775rem = 44.4px
       gap     44.4 - 39 = 5.4px   -> the 5px Wayne asked for, kept
       above 11px, below 3px  -> the bottom padding was trimmed by 2px
                                 on 2026-09-03 so the bar closes up
                                 under the label; it only ever held
                                 space, nothing sits in it

   DROPPED 6px FROM MATHEMATICAL CENTRE, deliberately, and that is not
   a mistake to tidy up later. Wayne called it "still too high" when it
   measured as balanced at 5 and 6, and he is reading it correctly: the
   pad is a solid 28px block and the label is small grey text, so the
   optical weight sits at the top. Equal gaps put a heavy element and a
   light one in the same space and let the heavy one dominate. Moving
   everything down 4px balances what the eye sees rather than what the
   ruler says.

   All three numbers move together or the pads come off the trace. */
.cart-steps.cart-steps-schematic li { padding: 2.775rem 8px 3px !important; }
.cart-steps.cart-steps-schematic li.active .cart-step-inner,
.cart-steps.cart-steps-schematic .cart-step-inner { background: none !important; color: inherit !important; }

/* The trace, on the li so label length cannot move it. */
.cart-steps.cart-steps-schematic li::after {
    content: "";
    position: absolute; top: 24px; left: 0; right: 0; height: 2px;
    /* SOLID, NOT DOTTED. The first version used a repeating gradient
       for the not-yet-reached trace, borrowing the routed/unrouted
       convention from circuit layout. Wayne 2026-09-03: "and they are
       dotted lines." He is right that it does not survive the size —
       at 2px tall with 4px dashes it reads as a dashed placeholder or
       a rendering artefact rather than a deliberate state, and on a
       non-retina screen the dashes alias into grey mush.
     *
       The done/upcoming distinction is carried by colour and weight
       instead, which is legible at any density: a pale rule ahead, the
       solid green behind. */
    background: #dfe4ea;
    /* BEHIND THE PAD, and this needs saying because the default is the
       opposite of what you would guess. li::after is generated content
       of the li, so it paints AFTER everything inside the li —
       including the pad, which is a pseudo-element of a descendant.
       Without a z-index the trace draws over the pad, and the FA
       glyph's transparent counters let the line show straight through
       the icon. Wayne 2026-09-03: "it looks like the line is sitting on
       top of the icon."

       Both layers are positioned, so z-index applies and no stacking
       context has to be invented. */
    z-index: 0;
}
.cart-steps.cart-steps-schematic li.complete::after,
.cart-steps.cart-steps-schematic li.active::after { background: #197a43; }

/* The pad. FA5 codepoints, each verified present in the loaded sheet —
   an FA6 name renders nothing at all, silently. */
.cart-steps.cart-steps-schematic li .cart-step-inner::before {
    font-family: "Font Awesome 5 Pro"; font-weight: 900;
    position: absolute; top: 11px; left: 50%; margin-left: -14px;
    /* 28px, up from 22px. Wayne: "the icon should be a little bigger."
       The trace sits at top:13px so it meets this pad's centre line
       (28 / 2 = 14, less the 1px half-height of a 2px rule); move one
       and the other has to move with it. */
    width: 28px; height: 28px; line-height: 24px;
    font-size: 13px; text-align: center;
    border-radius: 2px;
    /* OPAQUE, deliberately. The glyph has transparent counters, so
       anything behind the pad reads through them. A token is used
       rather than #fff so the pad still covers the trace on a dark
       ground. */
    background: var(--color-box-background, #fff);
    border: 2px solid #c6ccd4; color: #8b95a1;
    box-sizing: border-box;
    /* Above the trace. See the note on li::after. */
    z-index: 1;
}
/* ICON CHOICES, and why these four. Wayne 2026-09-03: "I'd like to use
   better looking icons."
 *
   The constraint that decides it is SIZE. These render at 13px inside a
   28px pad, and at that size an icon is a silhouette — internal detail
   does not survive, it just turns to grey mush. Two of the originals
   failed on exactly that:
 *
     fa-credit-card     a rectangle with a stripe and a chip. At 13px
                        the detail collapses and it reads as a plain
                        box. Replaced with fa-lock-alt, which is
                        instantly legible small AND says the right
                        thing on the payment step.
     fa-clipboard-check a clipboard, a clip, and a tick inside it —
                        three shapes competing in 13 pixels. Replaced
                        with fa-badge-check, one shape with one mark.
 *
   The other two were already right: a cart is a cart, and sliders is
   genuinely the best "configure" glyph and reads as technical rather
   than decorative.
 *
   This install has FA5 PRO — light, regular, solid, duotone and brands
   webfonts are all present — so fa-lock-alt and fa-badge-check are
   available where they would not be in the free set. Every codepoint
   below was read out of the loaded stylesheet, not looked up: a name
   that is not there renders nothing at all and an empty pad looks
   broken rather than minimal. */
/* FIVE, NOT FOUR. The funnel gained a Payment step on 2026-09-03 when
   pay.php and authorise.php were finally given the bar. These rules are
   nth-child, so a fifth li with no rule renders a pad with NO GLYPH —
   an empty square, which reads as broken rather than minimal. Caught by
   counting the rules against the new step list, not by looking at the
   page.
 *
   The meanings shifted with the insert: step 3 is now entering details
   and step 4 is paying, so the lock moved from 3 to 4 and step 3 took
   an invoice. */
.cart-steps.cart-steps-schematic li:nth-child(1) .cart-step-inner::before { content: "\f07a"; } /* shopping-cart */
.cart-steps.cart-steps-schematic li:nth-child(2) .cart-step-inner::before { content: "\f1de"; } /* sliders-h     */
.cart-steps.cart-steps-schematic li:nth-child(3) .cart-step-inner::before { content: "\f570"; } /* file-invoice  */
.cart-steps.cart-steps-schematic li:nth-child(4) .cart-step-inner::before { content: "\f30d"; } /* lock-alt      */
.cart-steps.cart-steps-schematic li:nth-child(5) .cart-step-inner::before { content: "\f336"; } /* badge-check   */

/* A LAST-RESORT GLYPH for any step beyond the fifth. If someone adds a
   sixth step later this draws a dot rather than an empty pad — the
   failure mode above, made harmless. */
.cart-steps.cart-steps-schematic li:nth-child(n+6) .cart-step-inner::before { content: "\f111"; font-size: 7px; } /* circle */
.cart-steps.cart-steps-schematic li.complete .cart-step-inner::before { border-color: #197a43; color: #197a43; }
.cart-steps.cart-steps-schematic li.active .cart-step-inner::before {
    border-color: #197a43; background: #197a43; color: #fff;
    /* outline, not a second border: it costs no layout and cannot
       nudge the pad off the trace's centre line. */
    outline: 1px solid #197a43; outline-offset: 3px;
    z-index: 2;
}
.cart-steps.cart-steps-schematic li:not(.active):not(.complete) { opacity: 0.6; }

/* ---- dark mode -----------------------------------------
   Before this the component had NO dark rules at all — the pale active
   fill became a light island on a dark page. Legible, but never
   designed. */
body.colortoggle-dark .cart-steps.cart-steps-schematic li::after { background: #3a4048; }
body.colortoggle-dark .cart-steps.cart-steps-schematic li.complete::after,
body.colortoggle-dark .cart-steps.cart-steps-schematic li.active::after { background: #2ea55c; }


/* ---- CALLING CARD MANAGEMENT (2026-09-04) -------------------------

   In nebulous.css, NOT app.css. The client-area shell loads only the
   theme's sheets, so a rule written into app.css never reaches this
   page and the class renders bare — a trap this codebase has fallen
   into twenty times, thirteen of them on one page.

   The dial number and PIN are the two things a customer came here for,
   so they are set larger and in a mono face: these get read aloud off
   a screen and typed into a keypad, and a proportional font makes
   1/l/7 harder than it needs to be.
   ------------------------------------------------------------------ */
body.template-nebulous .voip-dial,
body.template-nebulous .voip-pin,
body.template-nebulous .voip-balance {
    display: inline-block;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 1.5rem;
    letter-spacing: 0.04em;
    line-height: 1.3;
}

/* Wide enough that revealing the PIN does not shove the button sideways. */
body.template-nebulous .voip-pin {
    min-width: 5ch;
}

body.template-nebulous .voip-pin-toggle {
    margin-left: 0.6rem;
    vertical-align: 4px;
}

body.template-nebulous .voip-balance {
    font-size: 1.9rem;
}

/* ------------------------------------------------------------------
   Voicemail list
   ------------------------------------------------------------------
   Two small things the CI CSS check caught before they shipped, which
   is what it is for: both were written straight into a page and neither
   existed in any theme stylesheet, so both would have rendered as bare
   text on a storefront that never loads app.css.
   ------------------------------------------------------------------ */

/* An unheard message. A left border rather than a background: the row
   still has to be readable, and a tinted row in a list of tinted rows
   stops meaning anything. */
body.template-nebulous tr.is-unread td:first-child {
    box-shadow: inset 3px 0 0 0 var(--color-primary, #f09546);
}

body.template-nebulous tr.is-unread td {
    font-weight: 600;
}

/* "New". Small, and the same accent as the unread bar so the two read
   as one signal rather than two competing ones. */
body.template-nebulous .badge {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    background: var(--color-primary, #f09546);
    color: #ffffff;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 2px;
}

/* ------------------------------------------------------------------
   Calls / talk time / spent, one per row.
   ------------------------------------------------------------------
   A grid rather than a flex row per pair, so the three values line up
   with each other however long the labels get — a translated "Talk
   time" would otherwise push its own value out of column while the
   other two stayed put.

   auto/1fr with the value right-aligned keeps the numbers on the card
   edge, where the balance above them already sits.
   ------------------------------------------------------------------ */
body.template-nebulous .voip-usage {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.18rem 1rem;
    margin: 0.55rem 0 0;
    font-size: 0.85rem;
    line-height: 1.35;
    color: #5b6470;
}

body.template-nebulous .voip-usage dt {
    margin: 0;
}

body.template-nebulous .voip-usage dd {
    margin: 0;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: #2f3742;
}

/* .ca-muted sets no margin, so a <p> keeps the browser's 1em top AND
   bottom — on a 0.82em face that is about 13px above and below a
   two-line sentence, which is what made this card read as mostly gap.
   Scoped to the credit block rather than .ca-muted globally: that
   class is on hundreds of paragraphs across the client area, and
   respacing all of them is a different decision from tidying this
   card. */
body.template-nebulous [data-voip-credit] p {
    margin: 0.45rem 0 0;
    line-height: 1.45;
}

body.template-nebulous [data-voip-credit] .ca-figure {
    margin: 0 0 0.15rem;
}

/* The top-up form, spaced as one block rather than three.
   The label belongs to the box directly beneath it, so it gets a
   hairline gap; the note beneath gets a real one. */
body.template-nebulous .voip-topup {
    margin-top: 0.9rem;
}

body.template-nebulous .voip-topup-label {
    display: block;
    margin: 0 0 0.2rem;
}

body.template-nebulous .voip-topup p {
    margin: 0.4rem 0 0;
    line-height: 1.45;
}

/* The top-up amount sits inline with its currency symbol and button,
   so the whole thing reads as one control rather than three. */
body.template-nebulous .voip-topup-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
}

body.template-nebulous .voip-topup-currency {
    font-size: 1.1rem;
    font-weight: 600;
}

body.template-nebulous .voip-topup-row input[type="number"] {
    flex: 0 1 7rem;
    min-width: 0;
}

/* ---------------------------------------------------------------
 * Calling card — the printable artefact
 *
 * VoipCardArt carries its own inline styles so it can survive a mail
 * client, so there is deliberately almost nothing to do here. What is
 * left is the two things inline styles cannot express: how the card
 * sits in the page, and what happens when it is printed.
 * --------------------------------------------------------------- */
body.template-nebulous .voip-card-wrap {
    margin: 4px 0 18px;
}

body.template-nebulous .voip-card-actions {
    gap: 8px;
}

/*
 * PRINT. Everything but the card goes, so a customer gets the card on
 * a sheet rather than a web page with a card somewhere in it.
 *
 * The chain of `visibility` rather than `display:none` on the page is
 * deliberate: hiding ancestors outright would take the card with them,
 * since it lives inside the shell it needs to escape.
 */
@media print {
    /*
     * COLLAPSE EVERY BRANCH THAT DOES NOT CONTAIN THE CARD, level by
     * level, so the document ends up exactly one card tall.
     *
     * This used to be `visibility: hidden` on everything with the card
     * turned back on. Hiding that way still RESERVES the space: the
     * sidebar is 1561px, the footer 1180px, and the sheet ran to a
     * second, blank page. display:none removes them from layout
     * entirely.
     *
     * :has() walks the chain generically instead of naming the panels
     * this page happens to have, so a new panel does not reintroduce
     * the blank page by being forgotten here.
     */
    body.template-nebulous > *:not(:has(.voip-card-real)),
    body.template-nebulous .ca-shell > *:not(:has(.voip-card-real)),
    body.template-nebulous .ca-shell-main > *:not(:has(.voip-card-real)),
    body.template-nebulous .ca-shell-main > div > *:not(:has(.voip-card-real)),
    body.template-nebulous .ca-grid > *:not(:has(.voip-card-real)),
    body.template-nebulous .ca-card > *:not(.voip-card-wrap) {
        display: none !important;
    }

    /*
     * The masked card never prints, belt and braces with the script
     * that swaps them. A printed card showing dots where the PIN goes
     * cannot make a call, which is worse than not offering print at all.
     */
    body.template-nebulous .voip-card-masked {
        display: none !important;
    }

    /*
     * The panel around the card is chrome, not the card. On paper its
     * border and accent bar read as part of the thing being cut out.
     */
    body.template-nebulous .ca-card {
        border: 0;
        box-shadow: none;
        padding: 0;
        background: none;
    }

    body.template-nebulous .ca-card::before {
        display: none;
    }

    /*
     * NOTHING BUT THE CARD PAINTS.
     *
     * .ca-card was already cleared above, but it is not the only thing
     * carrying a colour: the page ground, the shell and the grid all
     * have one, and with "Background graphics" ticked — which the card
     * needs, or its orange band is dropped — every one of them reaches
     * the paper. Wayne got the card sitting on a grey slab.
     *
     * The card's own colours are deliberately absent from this list.
     * .voip-card, .voip-card-head and .voip-card-band keep theirs.
     */
    html,
    body.template-nebulous,
    body.template-nebulous .ca-shell,
    body.template-nebulous .ca-shell-main,
    body.template-nebulous .ca-shell-main > div,
    body.template-nebulous .ca-grid,
    body.template-nebulous .voip-card-wrap,
    body.template-nebulous .voip-card-real {
        background: none !important;
        box-shadow: none !important;
        border: 0 !important;
    }

    /*
     * STATIC, NOT ABSOLUTE — the absolute positioning is why the bottom
     * of the card did not print. Out of flow, .voip-card-wrap measured
     * zero pixels tall, so the browser paginated against an empty box:
     * the header, number and PIN reached paper and the steps below were
     * not part of any laid-out region. Nothing was clipping it, which is
     * why hunting for an overflow:hidden ancestor found nothing.
     */
    body.template-nebulous .voip-card-real {
        position: static;
        margin: 0;
    }

    /* Screen-only magnification, undone: a printed card must come out
       at ID-1 so it fits a wallet. */
    body.template-nebulous .voip-card-wrap {
        zoom: 1;
        height: auto;
    }

    body.template-nebulous .voip-card,
    body.template-nebulous .voip-card tr,
    body.template-nebulous .voip-card td {
        page-break-inside: avoid;
        break-inside: avoid;
    }

    /*
     * Backgrounds are dropped by default when printing, which would
     * leave the branded header as white-on-white — the one part of the
     * card that carries the branding, gone. Both properties are needed:
     * browsers are split on which they honour.
     */
    /*
     * ON THE TABLE *AND* ON THE CELL THAT CARRIES THE COLOUR.
     *
     * print-color-adjust on an ancestor is not reliably inherited by a
     * <td> background: the orange band printed but stopped short of the
     * card's right edge, leaving white against a border that ran the
     * full width. table-layout:fixed makes the cell exactly the table's
     * width rather than its content's, and the property is repeated on
     * the header cell itself so the fill is painted across all of it.
     */
    body.template-nebulous .voip-card-real .voip-card,
    body.template-nebulous .voip-card-real .voip-card *,
    body.template-nebulous .voip-card-head {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /*
     * NO TABLE LAYOUT IN PRINT, measured off a real PDF rather than
     * inferred from a screenshot.
     *
     * Chrome's print output had the white card box at 323 units — the
     * correct 324 — while the orange band came out 278 and the divider
     * rule under the number 242. Both are short by EXACTLY 44, which is
     * the tell: the band is not narrow, everything INSIDE the card is
     * being laid out for a content width of 278 while the table paints
     * its own box at the full 323.
     *
     * Nothing on screen reproduces it. With the print rules forced on,
     * getBoundingClientRect reports the cell at 322 inside 324 and
     * scrollWidth agrees, so five rounds of measuring in the DOM found
     * a layout that was already correct. Only the PDF disagreed.
     *
     * The outer table is three rows of one cell — a vertical stack
     * wearing a table's clothes, and a table only because this markup
     * is shared with the email version. Print does not need that, so
     * here it becomes ordinary blocks and the ambiguity goes with it.
     * The INNER tables stay tables: they lay the number out beside the
     * PIN, and that is real column work.
     */
    body.template-nebulous .voip-card {
        display: block;
        width: 100%;
    }

    body.template-nebulous .voip-card > tbody,
    body.template-nebulous .voip-card > tbody > tr,
    body.template-nebulous .voip-card > tbody > tr > td,
    body.template-nebulous .voip-card > tr,
    body.template-nebulous .voip-card > tr > td {
        display: block;
        width: auto;
    }

    body.template-nebulous .voip-card-head {
        width: 100%;
    }

    /* The band is a block child of the cell rather than the cell's own
       background — see VoipCardArt::cardHtml(). Belt and braces: the
       colour is stated here too, so it survives even if the inline
       style is stripped by whatever is doing the printing. */
    /* Belt and braces with the inline style — see VoipCardArt. The
       clip is what stops the table painting its box wider than its
       layout, which is what made the band look short. */
    body.template-nebulous .voip-card-clip {
        overflow: hidden;
        max-width: 100%;
    }

    body.template-nebulous .voip-card-band {
        display: block;
        width: 100%;
        box-sizing: border-box;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /*
     * CTRL+P PRINTED A BLANK PAGE, and had since this card existed.
     *
     * The real card — the one carrying the PIN — is hidden behind BOTH
     * the hidden attribute and .is-hidden, which is display:none
     * !important. Only the Print card button revealed it, by swapping
     * the two copies in JavaScript before calling window.print(). Any
     * other route to the printer — Ctrl+P, the browser menu, print
     * preview — left every copy of the card hidden, and the print
     * stylesheet's own "hide everything that is not the card" rules
     * then had nothing left to show.
     *
     * :has(.voip-card-real) still matched, because a display:none
     * element is very much still in the DOM. So the page correctly
     * hid the client area and printed the blank space where the card
     * would have been.
     *
     * Doing it here rather than in a beforeprint handler means it does
     * not depend on JavaScript running at all. The double class beats
     * .is-hidden's !important on specificity as well as its own, and
     * an author display beats the hidden attribute's UA rule outright.
     */
    body.template-nebulous .voip-card-real,
    body.template-nebulous .voip-card-real.is-hidden {
        display: block !important;
    }

    body.template-nebulous .voip-card-masked,
    body.template-nebulous .voip-card-masked.is-hidden {
        display: none !important;
    }
}

/* ---------------------------------------------------------------
 * Softphone credentials
 *
 * A definition list because that is what this is: three labelled
 * values that get copied into another program's settings form. The
 * grid keeps labels and values aligned so someone reading them into a
 * softphone does not lose their place.
 * --------------------------------------------------------------- */
body.template-nebulous .voip-sip {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 8px 16px;
    align-items: baseline;
    margin: 14px 0;
}

body.template-nebulous .voip-sip dt {
    font-size: 13px;
    color: var(--ca-muted, #6b7280);
}

body.template-nebulous .voip-sip dd {
    margin: 0;
    /*
     * min-width:0 because a long value in a grid track refuses to
     * shrink below its content and pushes the card wider than its
     * column — the same trap .card-grid rows hit with bare inputs.
     */
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

body.template-nebulous .voip-sip code {
    font-family: 'DejaVu Sans Mono', 'Courier New', Courier, monospace;
    font-size: 14px;
    word-break: break-all;
}

/* ---------------------------------------------------------------
 * "Added to cart" — the answer, and the way forward
 *
 * The toast acknowledges and leaves; the View cart button stays. Two
 * pieces on purpose: a confirmation that lingers becomes clutter, and
 * a route to checkout that vanishes on a timer strands anyone who
 * looked away for four seconds.
 * --------------------------------------------------------------- */
body.template-nebulous .cart-toast {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 1200;
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-width: min(320px, calc(100vw - 40px));
    padding: 13px 17px;
    border-radius: 10px;
    background: #243141;
    color: #ffffff;
    box-shadow: 0 10px 30px rgba(15, 23, 42, .28);

    /*
     * Hidden by OPACITY plus pointer-events, not display:none — a
     * display change cannot transition, and an aria-live region that
     * is display:none is not announced at all, which would silently
     * cost screen-reader users the confirmation.
     */
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
    transition: opacity .22s ease, transform .22s ease;
}

body.template-nebulous .cart-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

body.template-nebulous .cart-toast-title {
    font-size: 14px;
    font-weight: 700;
    line-height: 1.3;
}

body.template-nebulous .cart-toast-name {
    font-size: 13px;
    line-height: 1.4;
    opacity: .82;
}

body.template-nebulous .cart-added {
    display: block;
    margin-top: 12px;
}

body.template-nebulous .cart-added-link {
    display: inline-block;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-primary, #f09546);
    text-decoration: none;
}

body.template-nebulous .cart-added-link:hover,
body.template-nebulous .cart-added-link:focus-visible {
    text-decoration: underline;
}

/*
 * A toast that slides in is decoration; the words are the content. For
 * anyone who has asked for less motion, it appears without moving.
 */
@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .cart-toast {
        transition: opacity .01ms linear;
        transform: none;
    }

    body.template-nebulous .cart-toast.is-visible {
        transform: none;
    }
}

/* ---------------------------------------------------------------
 * Payment button, mid-submit
 *
 * Account credit and any redirecting gateway are ordinary form posts,
 * and the server work behind them is not instant — settling credit,
 * marking the order paid, then provisioning, which for a calling card
 * means several live API calls. Without this the click produced no
 * visible change at all and invited a second one.
 * --------------------------------------------------------------- */
body.template-nebulous .pay-method.is-busy {
    cursor: progress;
    opacity: .75;
}

body.template-nebulous .pay-method.is-busy .pay-method-go::after {
    content: "";
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-left: 8px;
    vertical-align: -1px;
    border: 2px solid currentColor;
    /*
     * One transparent side is what makes it read as spinning. A full
     * ring rotating looks static.
     */
    border-top-color: transparent;
    border-radius: 50%;
    animation: hca-pay-spin .7s linear infinite;
}

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

/*
 * With motion reduced the spinner would sit as a broken circle, which
 * reads as an error rather than progress. The wording already says
 * "Working…", so the ring simply goes.
 */
@media (prefers-reduced-motion: reduce) {
    body.template-nebulous .pay-method.is-busy .pay-method-go::after {
        display: none;
    }
}

/* ---------------------------------------------------------------
 * Calling card actions — a button row, not a grid of cards
 *
 * These previously carried .ca-actions, which is a grid of auto-fit
 * 210px columns meant for action CARDS. Three buttons in it stretched
 * to fill their columns, and the reissue form — a second grid
 * immediately after — sat flush against the row above, so the whole
 * thing read as one orange block with overlapping labels.
 * --------------------------------------------------------------- */
body.template-nebulous .voip-card-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 4px 0 12px;
}

/*
 * The form is a flex ITEM here, not a layout of its own — margin
 * stripped so it lines up with the buttons beside it rather than
 * carrying a block element's spacing into the row.
 */
body.template-nebulous .voip-card-reissue {
    display: inline-flex;
    margin: 0;
}

/*
 * Values still arriving from VoIP.ms. Muted rather than a skeleton
 * block: the wording already says what is happening, and a grey bar
 * where a balance goes reads as a broken figure.
 */
body.template-nebulous .voip-loading {
    opacity: .65;
}

/*
 * Paired with the hidden attribute in voip-card.js. The attribute
 * alone has been defeated repeatedly in this app by a rule setting
 * display on the element; this cannot be.
 */
body.template-nebulous .is-hidden {
    display: none !important;
}

/* =================================================================
 * Phone number catalogue (/phone-services.php)
 *
 * A catalogue of PLACES, not a search box. The flags do real work
 * here: 54 regions is too many to scan by name alone, and a flag is
 * recognised faster than a two-letter code. They are sized to be
 * legible — a US state flag is usually a seal on a blue field, which
 * is mush below about 28px, so the region tiles give them 44px.
 * ================================================================= */

/*
 * THE BACKGROUND MOVES TO THE INNER ELEMENT so the navy box lines up
 * with the cards below it.
 *
 * It used to paint the full-bleed outer section while only its
 * CONTENT was capped at 1140px, so the navy ran 20px wider than every
 * card underneath and squared off against their rounded corners. The
 * outer element now supplies the same 1140px + 1.25rem gutter that
 * .numbers-wrap gives the cards, and the inner one carries the
 * colour, the radius and the padding &mdash; so both edges resolve to the
 * same x position by construction rather than by matching numbers in
 * two places.
 */
body.template-nebulous .numbers-hero {
    max-width: 1140px;
    margin: 0 auto;
    padding: 2.25rem 1.25rem 0;
    color: #fff;
}

body.template-nebulous .numbers-hero-inner {
    padding: 3rem 2.25rem 2.75rem;
    /*
     * The same accent bar the cards below carry. No 1px grey border to
     * go with it, unlike those: on the dark gradient a light hairline
     * reads as a seam rather than an edge, and the navy already
     * separates itself from the page.
     */
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 14px;
    background: linear-gradient(160deg, #1d2836 0%, #2c3a4d 100%);
}

body.template-nebulous .numbers-eyebrow {
    margin: 0 0 .5rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--color-primary-background, #f09546);
}

body.template-nebulous .numbers-hero h1 {
    margin: 0 0 .75rem;
    color: #fff;
    font-size: clamp(1.9rem, 4vw, 2.75rem);
    line-height: 1.12;
    text-wrap: balance;
}

body.template-nebulous .numbers-lede {
    margin: 0;
    max-width: 44rem;
    font-size: 1.05rem;
    line-height: 1.6;
    color: rgba(255, 255, 255, .82);
}

body.template-nebulous .numbers-lede strong { color: #fff; }

/* ---- the product / billing-mode switch ---- */

body.template-nebulous .numbers-modes {
    display: flex;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1.75rem;
}

body.template-nebulous .numbers-mode {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    min-width: 13rem;
    padding: .85rem 1.15rem;
    text-align: left;
    border: 1px solid rgba(255, 255, 255, .25);
    border-radius: 10px;
    background: rgba(255, 255, 255, .06);
    color: #fff;
    cursor: pointer;
    transition: background .15s ease, border-color .15s ease;
}

body.template-nebulous .numbers-mode:hover {
    background: rgba(255, 255, 255, .12);
}

/*
 * The selected state carries a BORDER and a background, not just the
 * accent colour: #f09546 on this dark ground is about 2.3:1, which is
 * not enough to be the only signal of which product is selected.
 */
body.template-nebulous .numbers-mode.is-active {
    background: var(--color-primary-background, #f09546);
    border-color: var(--color-primary-background, #f09546);
    color: #1d2836;
}

body.template-nebulous .numbers-mode-name { font-weight: 700; font-size: .98rem; }
body.template-nebulous .numbers-mode-price { font-size: 1.15rem; font-weight: 700; }
body.template-nebulous .numbers-mode-note { font-size: .82rem; opacity: .8; }

/* ---- toolbar ---- */

body.template-nebulous .numbers-wrap {
    max-width: 1140px;
    margin: 0 auto;
    padding: 2.25rem 1.25rem 4rem;
}

body.template-nebulous .numbers-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 1.75rem;
}

body.template-nebulous .numbers-search { display: block; flex: 1 1 20rem; }

/*
 * "Find a city" — bold and a step up, per Wayne. It labels the one
 * control on this card somebody types into, and at .8rem/600 in the
 * muted grey it was quieter than the six buttons beside it.
 *
 * The colour goes to the heading token with the weight: 700 in the
 * body grey is heavy without being legible, which is the worst of the
 * two.
 */
body.template-nebulous .numbers-search-label {
    display: block;
    margin-bottom: .35rem;
    font-size: .9rem;
    font-weight: 700;
    color: var(--color-heading, #243141);
}

body.template-nebulous .numbers-search input {
    width: 100%;
    padding: .7rem .9rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 8px;
    font-size: 1rem;
}

body.template-nebulous .numbers-filters { display: flex; gap: .4rem; flex-wrap: wrap; }

body.template-nebulous .numbers-filter {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .55rem .9rem;
    border: 1px solid var(--color-border, #d2d2d2);
    /* Rectangular with soft corners, not a pill (Wayne, 2026-09-05). */
    border-radius: 6px;
    background: #fff;
    font-size: .9rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
    cursor: pointer;
}

/* Selected reads as filled, not merely tinted — see the note above. */
body.template-nebulous .numbers-filter.is-active {
    background: var(--color-primary-background, #f09546);
    border-color: var(--color-primary-background, #f09546);
    color: #1d2836;
}

body.template-nebulous .numbers-flag {
    width: 20px;
    height: auto;
    border-radius: 2px;
    /* Several flags are largely white; without this they dissolve. */
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .12);
}

body.template-nebulous .numbers-flag-lg { width: 32px; }

/* ---- country sections and region tiles ---- */

body.template-nebulous .numbers-country { margin-bottom: 2.75rem; }

body.template-nebulous .numbers-country-head {
    display: flex;
    align-items: center;
    gap: .7rem;
    margin: 0 0 1rem;
    padding-bottom: .6rem;
    border-bottom: 1px solid var(--color-border, #d2d2d2);
    font-size: 1.3rem;
}

body.template-nebulous .numbers-country-meta {
    margin-left: auto;
    font-size: .82rem;
    font-weight: 500;
    color: var(--color-body, #696969);
    white-space: nowrap;
}

body.template-nebulous .numbers-grid {
    display: grid;
    gap: .85rem;
    grid-template-columns: repeat(auto-fill, minmax(17.5rem, 1fr));
    /*
     * A grid item stretches to its row by default, so opening one
     * region blew its two neighbours up to match — British Columbia
     * expanded and Manitoba became a tall empty box beside it. Each
     * tile keeps its own height instead.
     */
    align-items: start;
}

body.template-nebulous .numbers-region {
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 10px;
    background: #fff;
    overflow: hidden;
}

body.template-nebulous .numbers-region-head {
    display: flex;
    align-items: center;
    gap: .8rem;
    width: 100%;
    padding: .85rem 1rem;
    border: 0;
    background: none;
    text-align: left;
    cursor: pointer;
}

body.template-nebulous .numbers-region-head:hover { background: rgba(0, 0, 0, .025); }

/*
 * 44px, not 20px. A US state flag is usually a seal on a blue field
 * and is indistinguishable from its neighbours much below this — at
 * which point the flag is decoration rather than a way to find your
 * own state, which is the entire reason it is here.
 */
body.template-nebulous .numbers-region-flag {
    width: 44px;
    height: auto;
    flex: 0 0 auto;
    border-radius: 3px;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .12);
}

body.template-nebulous .numbers-region-flag-none {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 28px;
    border-radius: 3px;
    background: var(--color-box-background, #f3f4f6);
    font-size: .75rem;
    font-weight: 700;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-region-text {
    display: flex;
    flex-direction: column;
    /* Or a long region name pushes the price out of the tile. */
    min-width: 0;
}

body.template-nebulous .numbers-region-name {
    font-weight: 600;
    color: var(--color-heading, #243141);
    line-height: 1.25;
}

body.template-nebulous .numbers-region-count {
    font-size: .8rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-region-price {
    margin-left: auto;
    font-weight: 700;
    color: var(--color-heading, #243141);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .numbers-region-price small {
    font-weight: 500;
    font-size: .75rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-region-body {
    padding: .25rem 1rem 1rem;
    border-top: 1px solid var(--color-border, #d2d2d2);
}

/* ---- places within a region ---- */

body.template-nebulous .numbers-places {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    margin: .75rem 0 0;
    padding: 0;
    list-style: none;
    /* 579 places in Pennsylvania — this has to scroll, not grow. */
    max-height: 15rem;
    overflow-y: auto;
}

body.template-nebulous .numbers-places-wide { max-height: none; }

body.template-nebulous .numbers-place {
    padding: .35rem .65rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 6px;
    background: #fff;
    font-size: .85rem;
    color: var(--color-heading, #243141);
    cursor: pointer;
}

body.template-nebulous .numbers-place:hover {
    border-color: var(--color-primary-background, #f09546);
}

body.template-nebulous .numbers-place-where {
    margin-left: .4rem;
    font-size: .75rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-none,
body.template-nebulous .numbers-result-count {
    margin: .75rem 0 0;
    font-size: .9rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-result-count {
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

body.template-nebulous .numbers-freshness {
    margin: 2.5rem 0 0;
    font-size: .85rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-empty {
    padding: 3rem 1.5rem;
    text-align: center;
    border: 1px dashed var(--color-border, #d2d2d2);
    border-radius: 12px;
}

@media (max-width: 700px) {
    body.template-nebulous .numbers-toolbar { flex-direction: column; align-items: stretch; }
    body.template-nebulous .numbers-country-meta { margin-left: 0; }
    body.template-nebulous .numbers-country-head { flex-wrap: wrap; }
}

/* -----------------------------------------------------------------
 * Toll-free
 *
 * Deliberately the loudest thing on the page. Toll-free is what a
 * business buys, and the vanity search is the one moment in this
 * product where somebody types their own name and sees it come back
 * as a phone number.
 * ----------------------------------------------------------------- */

body.template-nebulous .tollfree {
    margin-bottom: 3.5rem;
}

body.template-nebulous .tollfree-hero {
    padding: 2.25rem 2rem 2rem;
    border-radius: 14px;
    background: linear-gradient(135deg, #f9f6f2 0%, #fdf1e3 100%);
    border: 1px solid #f0dcc4;
}

body.template-nebulous .tollfree-hero h2 {
    margin: .2rem 0 .5rem;
    font-size: clamp(1.5rem, 3vw, 2rem);
    line-height: 1.15;
    text-wrap: balance;
}

body.template-nebulous .tollfree-lede {
    margin: 0 0 1.5rem;
    max-width: 42rem;
    color: var(--color-body, #696969);
}

/* ---- the vanity search itself ---- */

body.template-nebulous .tollfree-vanity-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

/*
 * The fixed part of the number, shown as part of the input rather than
 * as a label. It is what makes an empty box obviously a phone number
 * instead of a search field.
 */
body.template-nebulous .tollfree-vanity-prefix {
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-size: clamp(1.1rem, 2.5vw, 1.5rem);
    font-weight: 700;
    color: var(--color-heading, #243141);
    white-space: nowrap;
}

body.template-nebulous .tollfree-vanity-field { flex: 1 1 12rem; min-width: 0; }

body.template-nebulous .tollfree-vanity-field input {
    width: 100%;
    padding: .55rem .8rem;
    border: 2px solid var(--color-primary-background, #f09546);
    border-radius: 8px;
    background: #fff;
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-size: clamp(1.1rem, 2.5vw, 1.5rem);
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
}

body.template-nebulous .tollfree-vanity-field input::placeholder {
    color: #c9ccd1;
    letter-spacing: .12em;
}

body.template-nebulous .tollfree-search-btn { padding: .7rem 1.6rem; font-size: 1rem; }

body.template-nebulous .tollfree-vanity-hint {
    margin: .6rem 0 0;
    font-size: .85rem;
    color: var(--color-body, #696969);
}

/* The live keypad translation — the bit that makes people believe it. */
body.template-nebulous .tollfree-digits {
    display: inline-block;
    margin-left: .5rem;
    padding: .1rem .5rem;
    border-radius: 4px;
    background: rgba(240, 149, 70, .18);
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-weight: 700;
    color: #8a4a09;
}

/* ---- the two ready-made products ---- */

body.template-nebulous .tollfree-products {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    margin-top: 1.25rem;
    align-items: start;
}

body.template-nebulous .tollfree-product {
    padding: 1.35rem 1.5rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 12px;
    background: #fff;
}

body.template-nebulous .tollfree-product h3 { margin: 0 0 .35rem; font-size: 1.05rem; }

body.template-nebulous .tollfree-price {
    margin: 0 0 .4rem;
    font-size: 1.9rem;
    font-weight: 700;
    color: var(--color-heading, #243141);
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .tollfree-price small {
    font-size: .85rem;
    font-weight: 500;
    color: var(--color-body, #696969);
}

body.template-nebulous .tollfree-detail {
    margin: 0 0 .5rem;
    font-size: .9rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .tollfree-best {
    margin: 0 0 1rem;
    font-size: .88rem;
    font-weight: 600;
    color: #8a4a09;
}

/* ---- results ---- */

body.template-nebulous .tollfree-results { margin-top: 1.5rem; }

body.template-nebulous .tollfree-waiting,
body.template-nebulous .tollfree-count {
    margin: 0 0 .75rem;
    font-size: .95rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .tollfree-count {
    font-weight: 600;
    color: var(--color-heading, #243141);
}

body.template-nebulous .tollfree-list {
    display: grid;
    gap: .5rem;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    margin: 0;
    padding: 0;
    list-style: none;
}

body.template-nebulous .tollfree-number {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    padding: .7rem .9rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 8px;
    background: #fff;
}

body.template-nebulous .tollfree-did {
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--color-heading, #243141);
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .tollfree-number-price {
    font-size: .85rem;
    font-weight: 600;
    color: var(--color-body, #696969);
}

body.template-nebulous .tollfree-number-price small {
    font-weight: 400;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-local-head {
    margin: 0 0 1.25rem;
    font-size: 1.5rem;
}

/* Visually hidden but read aloud — the vanity input's real label. */
body.template-nebulous .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/*
 * The Add-to-cart button on a search result.
 *
 * Sits at the bottom of a .tollfree-number card, full width, because
 * the cards are narrow and a right-aligned button next to a
 * tabular-nums phone number reads as part of the number.
 */
body.template-nebulous .tollfree-buy {
    margin-top: .5rem;
    width: 100%;
    padding: .45rem .75rem;
    font-size: .85rem;
    font-weight: 600;
}

/*
 * Disabled covers two different states on purpose — "adding" and
 * "already in your cart" — and both mean the same thing to the
 * customer: stop clicking, it is handled.
 */
body.template-nebulous .tollfree-buy[disabled] {
    opacity: .75;
    cursor: default;
}

/*
 * SIP settings on the phone-number page.
 *
 * Monospaced because every one of these is going to be typed into
 * another device by hand, and a server name in a proportional face
 * makes l/1 and O/0 the customer's problem.
 */
body.template-nebulous .phone-sip dt {
    font-size: .8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--color-body, #696969);
}

body.template-nebulous .phone-sip dd {
    margin: .15rem 0 0;
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-size: 1rem;
    color: var(--color-heading, #243141);
    word-break: break-all;
}

body.template-nebulous .phone-sip-password {
    display: inline-block;
    margin-right: .5rem;
}

/*
 * The Show/Hide control beside the SIP password.
 *
 * Its label swaps between two words of different widths, so it is
 * given a floor — without one the button changes size as you toggle
 * it, and the row of settings jumps.
 */
body.template-nebulous .phone-sip-toggle {
    min-width: 4.5rem;
    padding: .2rem .6rem;
    font-size: .8rem;
    vertical-align: baseline;
}

/*
 * The server-location chooser, sitting beside the search rather than
 * in a step of its own.
 */
body.template-nebulous .tollfree-pop {
    margin: 1.25rem 0 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

body.template-nebulous .tollfree-pop label {
    font-size: .85rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

body.template-nebulous .tollfree-pop select {
    padding: .4rem .6rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 6px;
    background: #fff;
    font-size: .9rem;
    max-width: 100%;
}

/* Full width once the label and the select stop fitting on one line. */
body.template-nebulous .tollfree-pop-hint {
    flex: 1 0 100%;
    margin: 0;
    font-size: .8rem;
    color: var(--color-body, #696969);
}

/* The "use this one for TLS" note under the SIP settings. */
body.template-nebulous .phone-sip-tls {
    margin: .75rem 0 0;
    font-size: .85rem;
}

body.template-nebulous .phone-sip-tls code {
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

/* -----------------------------------------------------------------
 * The numbers inside one town.
 *
 * Opens underneath the place that was clicked rather than in a modal:
 * people compare two nearby towns, and a dialog forces them to close
 * one before seeing the other.
 * ----------------------------------------------------------------- */

body.template-nebulous .numbers-dids {
    margin: .4rem 0 .75rem;
    padding: .75rem .9rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 8px;
    background: #fff;
    font-size: .9rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-dids-count {
    margin: 0 0 .6rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

body.template-nebulous .numbers-did-list {
    display: grid;
    gap: .4rem;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    margin: 0;
    padding: 0;
    list-style: none;
}

body.template-nebulous .numbers-did {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    padding: .5rem .65rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 6px;
}

body.template-nebulous .numbers-did-number {
    flex: 1 1 auto;
    font-family: 'DejaVu Sans Mono', 'Courier New', monospace;
    font-weight: 700;
    color: var(--color-heading, #243141);
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .numbers-did-sms {
    padding: .05rem .35rem;
    border-radius: 3px;
    background: rgba(240, 149, 70, .18);
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #8a4a09;
}

/* The button reuses .tollfree-buy, so only the width differs here. */
body.template-nebulous .numbers-did .tollfree-buy {
    flex: 1 0 100%;
    margin-top: .35rem;
}

/* An opened town keeps its highlight so it is clear which list belongs to it. */
body.template-nebulous .numbers-place.is-open {
    font-weight: 700;
    color: var(--color-heading, #243141);
}

/* Moving a number between points of presence. */
body.template-nebulous .phone-move {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

body.template-nebulous .phone-move label {
    font-size: .85rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

body.template-nebulous .phone-move select {
    flex: 1 1 14rem;
    min-width: 0;
    padding: .4rem .6rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 6px;
    background: #fff;
    font-size: .9rem;
}

/* Full width under the control, so the warning is read before clicking. */
body.template-nebulous .phone-move-note {
    flex: 1 0 100%;
    margin: .25rem 0 0;
    font-size: .8rem;
}

/* -----------------------------------------------------------------
 * Server location — its own card, above the products.
 *
 * It used to sit inside the toll-free block and therefore read as a
 * toll-free setting. It governs every number on the page, so it gets
 * its own frame and says so.
 * ----------------------------------------------------------------- */

/*
 * KEEPS ITS ACCENT BAR. I removed it reading "only the left orange
 * highlight on the Toll-Free card" as a complaint about this one;
 * it was about the FULL ORANGE OUTLINE the toll-free block had.
 * The bar was never the problem — the outline was. Both cards carry
 * the same left bar now, which is a consistent treatment rather than
 * two different kinds of emphasis.
 */
body.template-nebulous .numbers-pop-card {
    margin: 0 0 2rem;
    padding: 1.4rem 1.5rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 12px;
    background: #fff;
}

body.template-nebulous .numbers-pop-title {
    margin: 0 0 .4rem;
    font-size: 1.15rem;
}

body.template-nebulous .numbers-pop-lead {
    margin: 0 0 1rem;
    max-width: 46rem;
    font-size: .92rem;
    color: var(--color-body, #696969);
}

/* The select itself: bigger than a form control usually is here,
   because it is the only control in the card and was overlooked. */
body.template-nebulous .numbers-pop-card .tollfree-pop select {
    flex: 1 1 22rem;
    padding: .6rem .75rem;
    border: 2px solid var(--color-primary-background, #f09546);
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
    cursor: pointer;
}

/* -----------------------------------------------------------------
 * Toll-free, framed as one thing.
 *
 * The vanity search and the two ready-made products are all
 * toll-free; unframed they read as three unrelated blocks.
 * ----------------------------------------------------------------- */

/*
 * THE ONE ACCENTED CARD ON THE PAGE. A full orange outline shouted;
 * a left bar marks it just as clearly and lets the quieter border do
 * the work of holding the group together.
 */
body.template-nebulous .tollfree-framed {
    padding: 1.5rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 14px;
    background: #fff;
}

body.template-nebulous .tollfree-frame-head {
    margin: 0 0 1.25rem;
    padding-bottom: .9rem;
    border-bottom: 1px solid var(--color-border, #d2d2d2);
}

body.template-nebulous .tollfree-frame-head h2 {
    margin: 0 0 .25rem;
    font-size: 1.5rem;
}

body.template-nebulous .tollfree-frame-head p {
    margin: 0;
    color: var(--color-body, #696969);
}

/* The toggle's own answer, next to the toggle. */
/*
 * A LIGHT-BACKGROUND TOKEN ON A DARK BACKGROUND. --color-body is
 * #696969, which is correct against white and about 2.4:1 against
 * this navy — unreadable, and it fails WCAG outright.
 *
 * Matched to the hero lede rather than set to pure #fff, so the two
 * pieces of secondary text in this banner are the same colour rather
 * than nearly the same.
 */
body.template-nebulous .numbers-mode-status {
    margin: .75rem 0 0;
    font-size: .9rem;
    color: rgba(255, 255, 255, .82);
}

/*
 * #8a4a09, not the brand orange. The accent is a BACKGROUND colour —
 * as text on white it measures about 2.2:1 and fails outright. The
 * darker orange is already used for exactly this elsewhere in this
 * sheet, on the toll-free badges and keypad hint.
 */
/*
 * BACK TO THE BRAND ORANGE, and the round trip is the lesson.
 *
 * I changed this to #8a4a09 reasoning that the accent is a background
 * colour and fails as text &mdash; which is true ON WHITE, and this text
 * is on the navy hero. Dark orange on dark navy measures about
 * 1.8:1, worse than what it replaced. #f09546 on this ground is
 * roughly 6:1.
 *
 * The rule is not "this hex is for text" or "this one is not" &mdash; it
 * is that a colour is only readable against a stated background, and
 * the ground has to be checked each time.
 */
body.template-nebulous .numbers-mode-status strong {
    color: var(--color-primary-background, #f09546);
}

/* -----------------------------------------------------------------
 * Hero: the choice on the left, the explanation on the right.
 * ----------------------------------------------------------------- */

/*
 * LEFT, EXPLICITLY. The theme centres h1 by default, which is fine on
 * a marketing banner and wrong here — the eyebrow, the lede and the
 * cards beneath are all left-aligned, so a centred heading floated
 * free of everything it belongs to.
 */
body.template-nebulous .numbers-hero h1,
body.template-nebulous .numbers-hero .numbers-eyebrow,
body.template-nebulous .numbers-hero .numbers-lede {
    text-align: left;
}

/*
 * THE GUIDE GOES UNDER THE CARDS, NOT BESIDE THEM.
 *
 * Beside them it was a 22rem column holding the longest text on the
 * page, so it ran to about 650px tall and dragged the whole hero with
 * it &mdash; leaving a large empty navy field to its left. The narrow
 * column was making the height, not the word count.
 *
 * Full width, two columns inside, it uses the space that was already
 * empty and comes out roughly half as tall for the same content.
 */
body.template-nebulous .numbers-hero-choice {
    display: block;
    margin-top: 1.75rem;
}

body.template-nebulous .numbers-hero-choice-main {
    min-width: 0;
}

/*
 * The explainer. Lighter than the cards beside it on purpose: it is
 * there to be read once, not competed with.
 */
body.template-nebulous .numbers-guide {
    margin-top: 1.5rem;
    padding: 1.25rem 1.35rem;
    border: 1px solid rgba(255, 255, 255, .22);
    border-radius: 12px;
    background: rgba(255, 255, 255, .06);
    font-size: .88rem;
    line-height: 1.55;
    color: rgba(255, 255, 255, .86);
}

body.template-nebulous .numbers-guide h2 {
    margin: 0 0 .5rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--color-primary-background, #f09546);
}

body.template-nebulous .numbers-guide h2 + dl,
body.template-nebulous .numbers-guide h2 + ol {
    margin: 0 0 1.15rem;
}

body.template-nebulous .numbers-guide dt {
    font-weight: 700;
    color: #fff;
}

body.template-nebulous .numbers-guide dd {
    margin: 0 0 .55rem;
}

body.template-nebulous .numbers-guide ol {
    padding-left: 1.15rem;
}

body.template-nebulous .numbers-guide ol li {
    margin-bottom: .35rem;
}

body.template-nebulous .numbers-guide-foot {
    margin: 0;
    padding-top: .85rem;
    border-top: 1px solid rgba(255, 255, 255, .18);
    color: rgba(255, 255, 255, .72);
}

/* One column once the two stop fitting side by side. */
body.template-nebulous .numbers-guide-cols {
    display: grid;
    gap: 1.25rem 2.5rem;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}

/* The last block in each column should not add space above the rule. */
body.template-nebulous .numbers-guide-cols > div > :last-child {
    margin-bottom: 0;
}

/*
 * THE THEME RESETS FIGHT THIS CARD, twice, and both were only visible
 * on the rendered page.
 *
 * h2 is centred by default — same trap as the hero h1 — which put the
 * two headings out of line with every word beneath them.
 *
 * More seriously, the theme sets list-style-type: none on lists. "How
 * to order" is a SEQUENCE; without the numerals it is four unrelated
 * sentences, and the one thing the card exists to convey is the order
 * they happen in.
 */
body.template-nebulous .numbers-guide h2 {
    text-align: left;
}

body.template-nebulous .numbers-guide ol {
    list-style: decimal outside;
    padding-left: 1.35rem;
}

body.template-nebulous .numbers-guide ol li {
    list-style: decimal outside;
}

body.template-nebulous .numbers-guide ol li::marker {
    color: var(--color-primary-background, #f09546);
    font-weight: 700;
}

/*
 * EVERY HEADING ON THIS PAGE IS LEFT-ALIGNED, said once.
 *
 * The theme centres h1 and h2 by default. That has now caught the hero
 * title, the guide card's two headings, and both card titles here —
 * four times, each found only by looking at the rendered page, each
 * leaving a heading floating over left-aligned copy.
 *
 * Scoped to this page's own blocks rather than set globally, so it
 * cannot reach a page that wants the theme's centring.
 */
body.template-nebulous .numbers-pop-title,
body.template-nebulous .tollfree-frame-head h2,
body.template-nebulous .numbers-local-head {
    text-align: left;
}

/*
 * Local numbers, framed exactly like toll-free.
 *
 * Two products on one page deserve the same treatment. Unframed, the
 * search box, the country filter and fifty region tiles read as loose
 * page furniture rather than one thing you can buy.
 */
body.template-nebulous .numbers-local-framed {
    margin-top: 2rem;
    padding: 1.5rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 14px;
    background: #fff;
}

/* The heading loses its own spacing now that the header supplies it. */
body.template-nebulous .numbers-local-framed .numbers-local-head {
    margin: 0 0 .25rem;
}

body.template-nebulous .numbers-local-framed .numbers-freshness {
    margin-bottom: 0;
}

/* -----------------------------------------------------------------
 * International numbers — framed like the other two products.
 * ----------------------------------------------------------------- */

body.template-nebulous .numbers-intl-framed {
    margin-top: 2rem;
    padding: 1.5rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 14px;
    background: #fff;
}

body.template-nebulous .numbers-intl-head {
    margin: 0 0 .25rem;
    font-size: 1.5rem;
    text-align: left;
}

/*
 * The inbound-only notice. Given real weight rather than small print:
 * somebody buying a Berlin number reasonably assumes they can ring
 * Berlin with it, and finding out otherwise after paying would be our
 * fault, not theirs.
 */
body.template-nebulous .numbers-intl-note {
    margin: 0 0 1.5rem;
    padding: .8rem 1rem;
    border-radius: 8px;
    background: rgba(240, 149, 70, .12);
    font-size: .92rem;
    color: var(--color-heading, #243141);
}

body.template-nebulous .numbers-intl-type {
    margin-bottom: 1.75rem;
}

body.template-nebulous .numbers-intl-type h3 {
    margin: 0 0 .2rem;
    font-size: 1.1rem;
    text-align: left;
}

body.template-nebulous .numbers-intl-blurb {
    margin: 0 0 .8rem;
    font-size: .9rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-intl-empty {
    margin: 0;
    padding: .8rem 1rem;
    border: 1px dashed var(--color-border, #d2d2d2);
    border-radius: 8px;
    font-size: .9rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-intl-list {
    display: grid;
    gap: .5rem;
    grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
    margin: 0;
    padding: 0;
    list-style: none;
}

body.template-nebulous .numbers-intl-country {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    padding: .6rem .75rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-radius: 8px;
}

body.template-nebulous .numbers-intl-name {
    flex: 1 1 auto;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

body.template-nebulous .numbers-intl-name small {
    font-weight: 400;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-intl-price {
    font-weight: 700;
    color: var(--color-heading, #243141);
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .numbers-intl-price small {
    display: block;
    font-weight: 400;
    font-size: .78rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-intl-buy {
    flex: 1 0 100%;
    margin-top: .35rem;
}

/*
 * The wait warning. International numbers are allocated by a carrier
 * in that country, not issued instantly — a real order was still
 * showing "pending" half an hour later, so promising minutes would be
 * a promise we cannot keep.
 */
body.template-nebulous .numbers-intl-wait {
    margin: 0;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border, #d2d2d2);
    font-size: .85rem;
    color: var(--color-body, #696969);
}

/* The inbound-only caveat, inside the hero guide. */
body.template-nebulous .numbers-guide-caveat {
    margin: .5rem 0 0;
    font-weight: 700;
    color: var(--color-primary-background, #f09546);
}

/*
 * The location hint, once the page has chosen for you.
 *
 * A LIGHT GREEN PANEL, NOT LIGHT GREEN TEXT. Pale green type on white
 * measures around 2:1 and is unreadable — the same mistake the status
 * line made with the brand orange earlier. The tint carries the
 * "we did something helpful" signal and the text stays dark enough to
 * read: #1b5e20 on #e8f5e9 is about 9:1.
 *
 * The asterisk is a CSS marker rather than part of the string, because
 * the string is rewritten by JS through textContent every time the
 * choice changes — anything baked into it would need adding in three
 * places and would go missing from one of them.
 */
body.template-nebulous .tollfree-pop-hint {
    flex: 1 0 100%;
    margin: .6rem 0 0;
    padding: .55rem .8rem;
    border-radius: 6px;
    background: #e8f5e9;
    font-size: .85rem;
    color: #1b5e20;
}

body.template-nebulous .tollfree-pop-hint::before {
    content: '* ';
    font-weight: 700;
}

/*
 * The toolbar governs every product now, so it sits above them rather
 * than inside the local card, and carries six filters instead of three.
 *
 * The search shrinks to make room. It only ever searched cities, so it
 * is no longer the widest thing in a row it no longer dominates.
 */
body.template-nebulous .numbers-toolbar .numbers-search {
    flex: 0 1 14rem;
}

/* A card hidden by the filter. */
body.template-nebulous [data-section].is-filtered-out {
    display: none;
}

/* The count line above each international list. */
body.template-nebulous .numbers-intl-count {
    margin: 0 0 .75rem;
    font-size: .9rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

/*
 * Each international product is its own card now, so the note inside
 * it needs less top margin than it did when three shared one frame.
 */
body.template-nebulous .numbers-intl-framed + .numbers-intl-framed {
    margin-top: 1.5rem;
}

/*
 * The selected city, made obvious.
 *
 * It only changed weight and colour before, which on a row of
 * near-identical chips is not a selection anybody can see — Wayne
 * asked for the chosen one to be highlighted, and he was looking at a
 * list where nothing appeared chosen at all. Now it matches the active
 * filter button: the page has one way of saying "this one".
 */
body.template-nebulous .numbers-place.is-open {
    border-color: var(--color-primary-background, #f09546);
    background: var(--color-primary-background, #f09546);
    color: #fff;
    font-weight: 700;
}

/*
 * The results panel now sits below the row of cities at the region's
 * full width, so the grid can actually be a grid.
 */
body.template-nebulous .numbers-region-body > .numbers-dids {
    margin-top: .9rem;
}

body.template-nebulous .numbers-did-list {
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
}

/*
 * Number and button side by side rather than stacked. Stacked, sixty
 * numbers is sixty tall cards; inline, the same sixty fit a screen.
 */
body.template-nebulous .numbers-did {
    flex-wrap: nowrap;
    gap: .5rem;
}

body.template-nebulous .numbers-did .tollfree-buy {
    flex: 0 0 auto;
    margin-top: 0;
    width: auto;
    padding: .35rem .7rem;
    font-size: .78rem;
}

/*
 * AN OPEN REGION TAKES THE WHOLE ROW.
 *
 * The region cards sit three across, so an expanded one had about
 * 300px to show sixty phone numbers in — the grid collapsed to a
 * single file running hundreds of pixels down the page, which is what
 * Wayne photographed. Widening the panel alone could not fix it: the
 * card it lives in was the constraint.
 *
 * Spanning the row gives the results the width they were designed for
 * and makes the open region unmistakable, which the collapsed ones
 * beside it no longer compete with.
 */
body.template-nebulous .numbers-region.is-open {
    grid-column: 1 / -1;
}

/*
 * THE RESULTS PANEL MUST TAKE THE WHOLE ROW, IN EVERY LIST IT LANDS IN.
 *
 * .numbers-places is a FLEX row of city chips, and the panel is
 * appended into it as one more flex item — so it sized itself to its
 * own content, 331px, and the grid inside dutifully resolved to a
 * single column. Sixty numbers, one per line, 3,664px down the page.
 *
 * The earlier fix set `grid-column: 1 / -1` on an open region, which
 * is correct and does nothing here: the search results (the "find a
 * city" list, .numbers-places-wide) have no region wrapper at all, so
 * the panel falls back to being appended into the flex list itself.
 * That is the case Wayne was looking at. A flex-basis of 100% forces
 * its own row in a wrapping flex container and is harmless anywhere
 * else, so it fixes both lists with one rule rather than a second
 * special case for the search results.
 */
body.template-nebulous .numbers-dids {
    flex: 0 0 100%;
    width: 100%;
}

/*
 * Fifteen rows, not a four-row scrollbox — Wayne's call, and the right
 * one now that the panel is actually wide. Sixty numbers across four
 * columns IS fifteen rows, so the whole result set is visible at once
 * and nothing is hidden behind a scroll gutter inside a card.
 *
 * The cap that was briefly here fixed the wrong thing: the panel was
 * tall because it was one column wide, not because fifteen rows is too
 * many.
 */

/*
 * TWO FLAGS IN ONE BUTTON.
 *
 * The toll-free filter covers the United States and Canada together,
 * so it carries both flags rather than none — sitting between two
 * flagged buttons, a bare label read as a country whose flag was
 * missing. They overlap slightly so the pair reads as one mark rather
 * than two separate country buttons crammed together, and the gap to
 * the label matches what a single flag leaves.
 */
body.template-nebulous .numbers-flag-pair {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    /*
     * A GAP, NOT A TUCK. The second flag used to sit .35rem under the
     * first so the pair read as one mark. Wayne does not want them
     * overlapping anywhere, and he is right that it reads as a
     * rendering fault rather than a device — two national flags are two
     * things, and half-hiding one of them looks like a bug at every
     * size rather than a deliberate pairing at one.
     */
    gap: .25rem;
}

/*
 * The toolbar card. The frame comes from .tollfree-framed, shared with
 * the product cards deliberately — a second frame style for the one
 * control on the page would make it look like a different kind of
 * thing again, which is the problem it was meant to fix.
 *
 * The toolbar's own bottom margin belonged to it when it floated on the
 * page; inside a card the padding provides it, and leaving both would
 * print a band of empty white under the buttons.
 */
body.template-nebulous .numbers-toolbar-card {
    margin-bottom: 1.75rem;
}

body.template-nebulous .numbers-toolbar-card .numbers-toolbar {
    margin-bottom: 0;
}

/*
 * FIVE PER ROW, so sixty numbers is twelve rows — Wayne's call, and the
 * arithmetic is the point: a fixed column count makes the result set a
 * predictable shape instead of one that reflows between four and five
 * depending on how wide somebody's window happens to be.
 *
 * THE CELL HAD TO CHANGE SHAPE TO ALLOW IT, and this is measured rather
 * than assumed. Five columns at Wayne's window gives a 183px cell with
 * 165px of content width. Inline, a cell needs 204px — number, TEXTS
 * badge and button side by side — so all sixty overflowed and the
 * number itself was clipped to 43px. Number and badge alone still need
 * 166px against that 165.
 *
 * Shrinking the number until it fits was tried and rejected: at .82rem
 * it was still a pixel short, and the number is the one thing on this
 * page anybody is actually reading. So the cell stacks instead — the
 * card shape from the old single-column list, tiled five across.
 *
 * Stepping down on narrower screens is not decoration. Five columns on
 * a phone is five clipped numbers.
 */
body.template-nebulous .numbers-did-list {
    grid-template-columns: repeat(5, minmax(0, 1fr));
}

body.template-nebulous .numbers-did {
    flex-wrap: wrap;
    align-items: center;
    column-gap: .4rem;
    row-gap: .3rem;
}

body.template-nebulous .numbers-did .numbers-did-number {
    flex: 0 1 auto;
    font-size: .88rem;
}

body.template-nebulous .numbers-did .numbers-did-sms {
    font-size: .62rem;
    padding: .1rem .3rem;
}

/* Full width under the number, so the target is the whole cell edge to edge. */
body.template-nebulous .numbers-did .tollfree-buy {
    flex: 1 1 100%;
    width: 100%;
    margin-top: 0;
}

@media (max-width: 1000px) {
    body.template-nebulous .numbers-did-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
    body.template-nebulous .numbers-did-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 520px) {
    body.template-nebulous .numbers-did-list { grid-template-columns: 1fr; }
}

/*
 * The search's answer, inside the card that asked the question.
 *
 * A rule above the count separates it from the controls without making
 * it a second card — it is part of the same thing, not a new one. The
 * chips no longer need the standalone block's top margin now that the
 * card's own padding and this rule provide the space.
 */
body.template-nebulous .numbers-toolbar-card .numbers-results {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border, #d2d2d2);
}

body.template-nebulous .numbers-toolbar-card .numbers-result-count {
    margin-top: 0;
}

/*
 * Where the numbers land. No frame of its own: .numbers-dids already
 * draws one, and an empty host must take up no space at all — it sits
 * between two cards whenever nobody has opened a town, which is most
 * of the time.
 */
body.template-nebulous .numbers-dids-host:empty {
    display: none;
}

body.template-nebulous .numbers-dids-host > .numbers-dids {
    margin: 0 0 1.75rem;
}

/*
 * THE NUMBERS BOX GETS THE ORANGE RAIL, like every other card on this
 * page. It sits directly below the toolbar card at the same width now,
 * so a plain grey-bordered box read as a lesser thing than the cards
 * above and below it — it is the answer to the whole page.
 *
 * The same 4px and the same token as .tollfree-framed, deliberately
 * copied rather than approximated: two nearly-identical oranges side by
 * side look like a mistake, and the fallback has to match the others or
 * an install with no token set gets a differently-coloured rail on this
 * one box.
 *
 * The radius goes to 14px for the same reason — an 8px box abutting a
 * 14px card reads as a rounding error, not a decision.
 */
body.template-nebulous .numbers-dids {
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 14px;
}

/*
 * The local card's "calls both ways" note.
 *
 * Deliberately the SAME shape as .numbers-intl-note and a different
 * colour: the two notes answer the same question with opposite
 * answers, so they have to look like a matched pair rather than like
 * one warning and one unrelated paragraph. Green rather than the
 * page's orange because this one is a capability, not a caveat.
 */
body.template-nebulous .numbers-local-note {
    margin: 0 0 1.5rem;
    padding: .8rem 1rem;
    border-radius: 8px;
    background: rgba(45, 138, 85, .12);
    font-size: .92rem;
    color: var(--color-heading, #243141);
}

/*
 * "Show 32 more".
 *
 * Full width and quiet: it is a continuation of the list, not a fifth
 * thing competing with the orange buy buttons directly above it. An
 * outline rather than a fill for the same reason — sixty orange buttons
 * and then a sixty-first that does something completely different is
 * how somebody buys a number by accident.
 */
body.template-nebulous .numbers-did-more {
    display: block;
    width: 100%;
    margin-top: .75rem;
    padding: .55rem 1rem;
    border: 1px solid var(--color-primary-background, #f09546);
    border-radius: 8px;
    background: transparent;
    color: var(--color-heading, #243141);
    font-size: .88rem;
    font-weight: 600;
    cursor: pointer;
}

body.template-nebulous .numbers-did-more:hover {
    background: rgba(240, 149, 70, .12);
}


/*
 * The "Find a city" field.
 *
 * It was a plain bordered box sitting beside six styled buttons, which
 * made the one control people actually type into look like the least
 * important thing in the card.
 *
 * The icon sits INSIDE the field rather than beside it, so the field
 * still reads as one object and the label above keeps its own line.
 * Padding-left is reserved for it explicitly — an icon absolutely
 * positioned over text the user is typing is a bug that only appears
 * once somebody types a long name.
 */
body.template-nebulous .numbers-search-field {
    position: relative;
    display: block;
}

body.template-nebulous .numbers-search-icon {
    position: absolute;
    top: 50%;
    left: .85rem;
    transform: translateY(-50%);
    color: var(--color-body, #696969);
    font-size: .9rem;
    pointer-events: none;
}

body.template-nebulous .numbers-search input {
    padding-left: 2.4rem;
    border-radius: 8px;
    background: #fff;
    transition: border-color .15s ease, box-shadow .15s ease;
}

/*
 * A REAL FOCUS STATE. The default ring was being drawn under the
 * field's own border on this theme, so tabbing to it looked like
 * nothing had happened. Orange to match the page, and a ring rather
 * than a thicker border so the field does not change size and shove
 * the buttons beside it.
 */
body.template-nebulous .numbers-search input:focus {
    outline: none;
    border-color: var(--color-primary-background, #f09546);
    box-shadow: 0 0 0 3px rgba(240, 149, 70, .28);
}

body.template-nebulous .numbers-search input:focus + .numbers-search-icon,
body.template-nebulous .numbers-search-field:focus-within .numbers-search-icon {
    color: var(--color-primary, #e8830c);
}

body.template-nebulous .numbers-search input::placeholder {
    color: var(--color-body, #696969);
    opacity: 1;
}

/*
 * "What's on this page, and how to order".
 *
 * A quiet outline button in the hero where three columns of glossary
 * used to be. Bordered rather than filled: the two price chips beside
 * it are the hero's real decision, and a second solid button would
 * compete with them.
 */
body.template-nebulous .numbers-guide-open {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    margin-top: 1.25rem;
    padding: .55rem .95rem;
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: 8px;
    background: rgba(255, 255, 255, .06);
    color: #fff;
    font-size: .88rem;
    font-weight: 600;
    cursor: pointer;
}

body.template-nebulous .numbers-guide-open:hover {
    border-color: rgba(255, 255, 255, .6);
    background: rgba(255, 255, 255, .12);
}

/*
 * The dialog keeps the hero's own gradient rather than becoming a white
 * card. Every rule the guide already had is written for light text on
 * dark, and more importantly it is the same content it always was —
 * turning it white would make it look like a different feature that
 * happens to contain the same words.
 *
 * A translucent panel cannot work here: on the top layer there is no
 * hero behind it to tint, so the background has to be opaque.
 */
body.template-nebulous .numbers-guide-dialog {
    /*
     * 66rem, not 56: the columns are minmax(19rem, 1fr), so three of
     * them plus two 2.5rem gaps need 992px of content. At 56rem the
     * dialog offered 800 and the third column dropped to a second row
     * — measured, not guessed. 66rem leaves 1008.
     */
    width: min(66rem, calc(100vw - 2rem));
    max-height: calc(100vh - 4rem);
    overflow-y: auto;
    padding: 1.5rem;
    border: 1px solid rgba(255, 255, 255, .22);
    border-radius: 14px;
    background: linear-gradient(160deg, #1d2836 0%, #2c3a4d 100%);
    color: rgba(255, 255, 255, .86);
}

body.template-nebulous .numbers-guide-dialog::backdrop {
    background: rgba(17, 24, 33, .72);
}

body.template-nebulous .numbers-guide-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
    padding-bottom: .9rem;
    border-bottom: 1px solid rgba(255, 255, 255, .22);
}

body.template-nebulous .numbers-guide-head h2 {
    margin: 0;
    color: #fff;
    font-size: 1.25rem;
}

body.template-nebulous .numbers-guide-close {
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: 6px;
    background: transparent;
    color: #fff;
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
}

body.template-nebulous .numbers-guide-close:hover {
    background: rgba(255, 255, 255, .12);
}

/*
 * The two groups inside the toll-free card.
 *
 * A tint rather than another white card with an orange rail: these are
 * PARTS of the toll-free card, and giving them the same treatment as
 * the card containing them would flatten the hierarchy — six cards
 * where there are four, and no way to see which sits inside which.
 *
 * The tint is the page's own orange at very low alpha, so it groups
 * without becoming a third accent colour.
 */
body.template-nebulous .tollfree-group {
    margin-bottom: 1.5rem;
    padding: 1.25rem;
    border: 1px solid rgba(240, 149, 70, .35);
    border-radius: 12px;
    background: rgba(240, 149, 70, .06);
}

body.template-nebulous .tollfree-group:last-of-type {
    margin-bottom: 0;
}

body.template-nebulous .tollfree-group-head {
    margin: 0 0 1rem;
    padding-bottom: .7rem;
    border-bottom: 1px solid rgba(240, 149, 70, .3);
    color: var(--color-heading, #243141);
    font-size: 1.15rem;
}

/* The pitch inside the first group, demoted from h2 to h4. */
body.template-nebulous .tollfree-group .tollfree-hero h4 {
    margin: 0 0 .5rem;
    color: var(--color-heading, #243141);
    font-size: 1.35rem;
}

/*
 * The product headings dropped from h3 to h4 when the group heading
 * above them took h3 — a card cannot be the same heading level as the
 * group that contains it. Carry the h3's own styling across so the
 * change is structural and invisible.
 */
body.template-nebulous .tollfree-product h4 {
    margin: 0 0 .35rem;
    font-size: 1.05rem;
}

/*
 * THE TWO TOLL-FREE CARDS ARE EVEN AT EVERY WIDTH, not just the one I
 * happened to measure at.
 *
 * Matching the copy length evened them at 1568px and left them 26px
 * apart at 1013 — which is the width Wayne actually uses — because the
 * longer line wraps at some widths and not others. Any fix made of
 * words is a fix that holds at one viewport.
 *
 * The grid said align-items: start, so each card was only as tall as
 * its own text. Stretch makes the row decide, and the button is pushed
 * to the bottom of whichever card is shorter so the two "See available
 * numbers" targets stay on one line rather than floating at different
 * heights inside equal boxes.
 */
body.template-nebulous .tollfree-products {
    align-items: stretch;
}

body.template-nebulous .tollfree-product {
    display: flex;
    flex-direction: column;
}

body.template-nebulous .tollfree-product .tollfree-browse {
    margin-top: auto;
}

/*
 * CENTRE THE DIALOG. A modal <dialog> centres itself through the UA's
 * own `margin: auto` against `inset: 0` — and something in this theme
 * sets a plain top margin on dialogs, which replaces it and pins the
 * box to the top left. Measured on production: 0px of gap on the left
 * against 864px on the right.
 *
 * Restated here rather than hunting the offending rule, because the
 * fix has to survive the vendor CSS being updated. `auto` on all four
 * sides is what the UA wanted in the first place.
 */
body.template-nebulous .numbers-guide-dialog {
    margin: auto;
}

/*
 * THE HERO SPLITS IN TWO. Everything sat in full-width block children
 * with short content inside them, so the right half of a 1100px card
 * was empty navy — not a column, just unused space.
 *
 * The guide button and its dialog were moved INSIDE .numbers-hero-choice-main
 * before this, deliberately: they are siblings of the toggle, and as
 * direct children of a two-column grid they would have become grid
 * items and landed in the wrong cell.
 */
body.template-nebulous .numbers-hero-choice {
    display: grid;
    gap: 1.5rem 2rem;
    /*
     * TWO EQUAL HALVES, with the card centred in its own. Sized at
     * 24rem against a 1fr column the card was pinned to the hero's
     * right edge with all the slack pooled to its left, which read as
     * "pushed away" rather than "placed". Halving the row and centring
     * within it splits that slack either side of the card.
     */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
}

/*
 * A WHITE CARD ON THE NAVY, and the pattern is not invented here — it
 * is .product-hero-specs, which product.php already renders on this
 * exact kind of banner. A second look for the same job would make the
 * page feel assembled by different people.
 */
body.template-nebulous .numbers-hero-prices {
    justify-self: center;
    width: 100%;
    max-width: 24rem;
    padding: 0 0 1rem;
    border-radius: 12px;
    background: #fff;
    color: var(--color-heading, #243141);
    overflow: hidden;
}

body.template-nebulous .numbers-hero-prices-head {
    margin: 0 0 .25rem;
    padding: .7rem 1.1rem;
    background: rgba(240, 149, 70, .16);
    color: var(--color-heading, #243141);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/*
 * TWO COLUMNS, FILLED COLUMN-FIRST. Six stacked rows made the card
 * taller than the text beside it; paired up it is about half.
 *
 * grid-auto-flow: column is doing real work here, not just reflowing.
 * Row-first would interleave "Canada, local" with "Intl toll-free";
 * column-first puts the three US/Canada products in the left column
 * and the three international ones in the right, which is the same
 * split the rest of the page uses.
 */
body.template-nebulous .numbers-hero-price-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: repeat(3, auto);
    grid-auto-flow: column;
    gap: .1rem .9rem;
    margin: 0;
    padding: .35rem 1.1rem 0;
}

/*
 * Label above value, not beside it. At ~176px per column there is no
 * room for "US & Canada toll-free, from" and a price on one line, and
 * shrinking the label until it fits would make the smallest text on
 * the card the part that says what you are buying.
 */
body.template-nebulous .numbers-hero-price-row {
    padding: .45rem 0;
    border-bottom: 1px solid var(--color-border, #e6e8ec);
}

/*
 * The last row of EACH column, so neither column ends on a rule.
 * :last-child as well as :nth-child(3n) because the rows are
 * conditional — a catalogue missing one international product renders
 * four or five rows, and then the final row is not a multiple of three
 * and would keep a rule hanging under it.
 */
body.template-nebulous .numbers-hero-price-row:nth-child(3n),
body.template-nebulous .numbers-hero-price-row:last-child {
    border-bottom: 0;
}

/*
 * THE LABEL CARRIES THE WEIGHT, the figure does not — Wayne's call, and
 * it inverts what was here. Six bold prices in a small card read as six
 * competing shouts; bolding what each one IS and letting the amount sit
 * lighter makes the card scannable by product, which is how somebody
 * reads a price list they have not seen before.
 */
body.template-nebulous .numbers-hero-price-row dt {
    font-size: .74rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--color-heading, #243141);
}

body.template-nebulous .numbers-hero-price {
    margin: 0;
    font-size: 1rem;
    /* Lighter than the label above it, deliberately. */
    font-weight: 400;
    text-align: left;
    white-space: nowrap;
    /* Digits line up down the column as the rows re-price. */
    font-variant-numeric: tabular-nums;
}

body.template-nebulous .numbers-hero-price small {
    display: inline;
    margin-left: .25rem;
    font-size: .68rem;
    font-weight: 500;
    color: var(--color-body, #696969);
}

body.template-nebulous .numbers-hero-prices-foot {
    margin: .7rem 1.1rem 0;
    font-size: .74rem;
    line-height: 1.45;
    color: var(--color-body, #696969);
}

/*
 * One column on a narrow screen, panel AFTER the toggle — the phone
 * reading order stays h1, lede, chips, then prices.
 */
@media (max-width: 860px) {
    body.template-nebulous .numbers-hero-choice {
        grid-template-columns: minmax(0, 1fr);
    }
}


/*
 * Wider, and outlined in the page's own orange rather than the generic
 * grey — Wayne, and it matches the cards and the toll-free groups
 * around it. Faint: this is a field to type in, not a control to be
 * pulled toward, so it borrows the orange at low alpha rather than the
 * solid rail the cards use.
 *
 * 18rem instead of 14. It was narrowed to 14 when the six filter
 * buttons had to fit beside it on one row; measured after this change,
 * they still do.
 */
body.template-nebulous .numbers-toolbar .numbers-search {
    flex: 0 1 18rem;
}

body.template-nebulous .numbers-search input {
    border-color: rgba(240, 149, 70, .45);
}

body.template-nebulous .numbers-search input:hover {
    border-color: rgba(240, 149, 70, .7);
}

/*
 * Flags sit ON the heading baseline, not above it.
 *
 * .numbers-flag was sized for a chip and a list row; dropped into a
 * 1.5rem heading it rides high and slightly bigger than the cap
 * height, which reads as a misaligned image rather than part of the
 * title. Nudged to match the type it now sits beside.
 */
body.template-nebulous .tollfree-frame-head h2,
body.template-nebulous .numbers-local-head [data-local-title] {
    display: flex;
    align-items: center;
    gap: .55rem;
}

/*
 * 2.75rem = 44px, which is exactly the width of the state and province
 * flags on the cards below — the "lower ones" Wayne compared these to.
 * Matching that number rather than picking one that merely looks
 * bigger is the difference between the two sizes reading as one system
 * and as two guesses.
 *
 * I had originally sized these to the cap height of the type, which is
 * right for a flag set INTO a line of text and wrong for one that
 * identifies the card, which is what these do.
 *
 * Measured at 2, 2.4 and 2.75rem: the heading stays one line at 29px
 * in all three, and the counts stay on that line, so the largest is
 * free.
 */
body.template-nebulous .tollfree-frame-head h2 .numbers-flag,
body.template-nebulous .numbers-local-head .numbers-flag {
    flex: 0 0 auto;
    width: 2.75rem;
    height: auto;
}

/*
 * The counts sit at the far end of the heading line.
 *
 * The heading span has to be full width for margin-left:auto to have
 * anything to push against — it is a flex item in the h2, and without
 * this it shrinks to its text and the counts hug the title.
 */
body.template-nebulous .numbers-local-head [data-local-title] {
    width: 100%;
}

/*
 * Two pixels up and bold, per Wayne: .82rem was 13.1px, .95rem is
 * 15.2px. It sits on the same line as a 1.5rem heading now, and at the
 * old size it read as a caption that had drifted up rather than part
 * of the title row.
 *
 * The colour stays the muted body grey. Bold AND heading-dark would
 * make it compete with the country name beside it, which is the thing
 * the eye should land on first.
 */
body.template-nebulous .numbers-local-count {
    margin-left: auto;
    font-size: .95rem;
    font-weight: 700;
    color: var(--color-body, #696969);
    white-space: nowrap;
}

/*
 * The old country row keeps its heading semantics and loses every
 * visual trace — a border and a margin under an element with nothing
 * visible in it renders as a stray rule across the card.
 */
body.template-nebulous .numbers-country-head-quiet {
    margin: 0;
    padding: 0;
    border-bottom: 0;
}

/*
 * The heading pair gets slightly more air than the button's, because
 * its flags are 44px against 20px and the same gap looks tighter the
 * larger the things either side of it are.
 */
body.template-nebulous .tollfree-frame-head h2 .numbers-flag-pair {
    gap: .3rem;
}

/*
 * .is-hidden is this page's own class and the global [hidden] rule does
 * NOT cover it, so it still needs stating. The [hidden] half that used
 * to sit here was redundant: nebulous.css already carries a global
 * `[hidden] { display: none !important }`, and adding a per-component
 * copy is the seventh patch for a problem fixed once.
 */
body.template-nebulous .numbers-local-head [data-local-title].is-hidden {
    display: none;
}

/*
 * THE SEARCH BUTTON SAT 8px LOW, and the button was not the problem.
 *
 * .tollfree-vanity-field is a <label>, and labels on this page carry a
 * 16px bottom margin for stacked forms. Inside a flex row with
 * align-items:center that margin is part of the item's box, so the
 * field's CONTENT centres 8px higher than its siblings while the row
 * dutifully centres all three. The prefix and the button were where
 * they belonged; the field was the one out of place.
 *
 * Measured: centres at 897 / 905 / 905 before, all three at 897 after.
 */
body.template-nebulous .tollfree-vanity-row .tollfree-vanity-field {
    margin-bottom: 0;
}

/*
 * The vanity search's answer, on the line under the field.
 *
 * Wayne: "It was at the bottom and I didn't notice it." The message was
 * rendering correctly the whole time, 500px down, past the bottom of
 * the window — so the button read as dead. An answer nobody sees is the
 * same as no answer.
 *
 * Inline after the digits badge rather than on its own line, so the
 * row does not jump taller the moment a search returns.
 */
body.template-nebulous .tollfree-vanity-status {
    margin-left: .5rem;
    font-weight: 600;
}

body.template-nebulous .tollfree-vanity-status:empty {
    display: none;
}

/* Nothing found, or the carrier could not be reached — a fact, not an
   alarm, so it takes the body colour rather than a red. */
body.template-nebulous .tollfree-vanity-status.is-empty {
    color: var(--color-body, #696969);
    font-weight: 500;
}

body.template-nebulous .tollfree-vanity-status.is-waiting {
    color: var(--color-body, #696969);
    font-weight: 500;
}

/*
 * The setup fee, beside the monthly it belongs to.
 *
 * Wayne: a lot of numbers do not list it. Local numbers really do
 * charge one — $1.20 pay-as-you-go, $2.55 on a plan — and it appeared
 * nowhere, so a card reading $14.85 was the price of something that
 * costs $17.40 to start. Toll-free US and Canada+US genuinely charge
 * nothing, so they still show nothing; international already printed
 * its own where the country charges one.
 *
 * Its own line under the monthly, quieter: it is a real cost and must
 * be legible, but it is paid once and the monthly is the number people
 * compare.
 */
body.template-nebulous .numbers-region-setup,
body.template-nebulous .numbers-hero-price small[data-setup-plan] {
    display: block;
    font-size: .68rem;
    font-weight: 500;
    color: var(--color-body, #696969);
    white-space: nowrap;
}

/* The toll-free cards' setup line, matching the region and hero ones. */
body.template-nebulous .tollfree-setup {
    margin: 0 0 .35rem;
    font-size: .78rem;
    font-weight: 600;
    color: var(--color-body, #696969);
}

/*
 * The price of the numbers listed below it, on the same line as the
 * count. Set apart rather than run on, because "60 numbers available
 * for Langley, BC $12.75/mo" reads as one sentence that says something
 * it does not mean.
 */
body.template-nebulous .numbers-dids-price {
    margin-left: .6rem;
    padding-left: .6rem;
    border-left: 1px solid var(--color-border, #d2d2d2);
    font-weight: 600;
    color: var(--color-body, #696969);
}

/*
 * The "added to your cart" confirmation.
 *
 * Deliberately small and centred rather than a full-width banner: it
 * interrupts to offer a choice, and it is dismissed by making one.
 */
body.template-nebulous .cart-added {
    width: min(26rem, calc(100vw - 2rem));
    margin: auto;
    padding: 1.5rem;
    border: 1px solid var(--color-border, #d2d2d2);
    border-left: 4px solid var(--color-primary-background, #f09546);
    border-radius: 14px;
    background: #fff;
    color: var(--color-heading, #243141);
}

body.template-nebulous .cart-added::backdrop {
    background: rgba(17, 24, 33, .55);
}

body.template-nebulous .cart-added-title {
    margin: 0 0 .5rem;
    font-size: 1.15rem;
    font-weight: 700;
}

body.template-nebulous .cart-added-item {
    margin: 0 0 .25rem;
    font-weight: 600;
}

body.template-nebulous .cart-added-count {
    margin: 0 0 1.25rem;
    font-size: .88rem;
    color: var(--color-body, #696969);
}

body.template-nebulous .cart-added-actions {
    display: flex;
    gap: .6rem;
}

/* Checkout is the primary action and looks it; keep shopping is the
   quieter one, because the customer who wants it is already leaving. */
body.template-nebulous .cart-added-keep,
body.template-nebulous .cart-added-checkout {
    flex: 1 1 50%;
    padding: .6rem 1rem;
    border-radius: 8px;
    font-size: .92rem;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}

body.template-nebulous .cart-added-keep {
    border: 1px solid var(--color-border, #d2d2d2);
    background: #fff;
    color: var(--color-heading, #243141);
}

body.template-nebulous .cart-added-checkout {
    border: 1px solid var(--color-primary-background, #f09546);
    background: var(--color-primary-background, #f09546);
    color: #fff;
}

body.template-nebulous .cart-added-keep:hover {
    background: rgba(0, 0, 0, .03);
}

body.template-nebulous .cart-added-checkout:hover {
    filter: brightness(.95);
}

/*
 * The one-time setup fee on a cart line.
 *
 * Same treatment as the "$x each" note beside it: this is working shown
 * for a subtotal the customer would otherwise be unable to reconstruct,
 * not a second price competing with the first.
 */
body.template-nebulous .cart-item-setup {
    display: block;
    font-size: .78rem;
    font-weight: 600;
    color: var(--color-body, #696969);
}


/* ---- HELP ICONS IN THE CLIENT AREA --------------------------------

   A SECOND COPY, AND IT HAS TO BE. These same three rules are in
   app.css, which the client area never loads: partials/header.php
   hands off to the active template's own header and returns before
   the app.css <link> is ever printed. A help icon styled only there
   renders as a bare unstyled button — the exact failure that had
   accumulated twenty times by 2026-08-30, and one that neither php -l
   nor PHPStan nor reading the source can see, because the rule really
   is written, just in a file this page does not request.

   The behaviour is shared (/assets/js/help-bubble.js); only the paint
   is duplicated. --admin-accent does not exist here, so the fallback
   in the var() is what actually applies — kept as a var() anyway so a
   theme that defines one wins.

   EVERY SELECTOR IS PREFIXED body.template-nebulous, and that is not
   habit. The theme styles bare buttons with
   `body.template-nebulous button { padding: 0.6rem 1.25rem }`, which at
   (0,1,2) outranks a bare `.whiz-help-icon` at (0,1,0) — so the 18px
   circle rendered as a 40px rounded rectangle while the background,
   which nothing else set, applied correctly and made it look
   deliberate. The prefix takes these to (0,2,1) and wins.
   ------------------------------------------------------------------ */
body.template-nebulous .whiz-help-icon {
    /* EVERY DIMENSION HERE IS EVEN, AND THAT IS THE POINT.
       Wayne's display is devicePixelRatio 1.5, so one CSS pixel is one
       and a half device pixels and an ODD css size lands on a half
       device pixel that has to be antialiased. 19px x 1.5 = 28.5 — the
       circle edge could never be crisp at that size, whatever colour it
       was. 16 x 1.5 = 24, the 2px ring x 1.5 = 3, the 10px glyph
       x 1.5 = 15: all whole device pixels.
       Chrome also snapped a 1px border down to 0.666667px (one device
       pixel) on that display, which is why the thin ring looked like it
       was bleeding. 2px survives at 3 device pixels.
       16px is also the right size typographically: it annotates a 16px
       card title whose capitals are ~11px, so it reads as a footnote
       rather than as a button.
       THE GLYPH IS 10px, NOT 12px, AND THAT IS ARITHMETIC TOO. Measured
       from the font itself, fa-info at 12px has 11px of ink above the
       baseline and 2px below — 13px of mark inside a black centre that
       is only 12px across once the ring takes its 2px a side. It
       overflowed, and because those bearings are asymmetric it
       overflowed downwards, which is what read as "misaligned". At 10px
       the ink is about 10.8px and fits. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* border-box or the 2px ring is ADDED to the width and every icon
       in the app quietly renders 23px instead of 19px. */
    box-sizing: border-box;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    /* THE SAME INK AS THE HEADING IT ANNOTATES. Measured on the live
       page: the phone number and every card title render #243141 — a
       dark blue-grey, not black and not grey. A pure #000 icon beside
       them was a second, slightly different dark, which is the kind of
       near-miss that reads as a mistake rather than a choice. */
    background: var(--color-heading, #243141);
    /* 2px, an INTEGER. The first attempt used 1.5px, which on a 1x
       display has no whole pixel to occupy — the browser smeared it
       across two, and that soft double edge is what read as the ring
       "bleeding out". */
    border: 2px solid #f09546;
    color: #fff;
    /* White on black is 21:1. The accent stays on the ring, where it
       carries no text: white on #f09546 measures 2.31:1 and the glyph
       disappears into its own dot, which already shipped once. */
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    margin-left: 6px;
    vertical-align: middle;
    position: relative;
    padding: 0;
    transition: border-color .15s;
}

body.template-nebulous .whiz-help-icon:hover {
    /* A DELIBERATE INVERSION, and it must say so in full.
       Setting only border-color here was a bug: the theme styles
       button:hover with its own light background, which then won,
       painting the circle white while the glyph stayed white too — the
       "i" simply vanished. Both properties are restated so nothing
       underneath can supply half of the state. */
    background: #fff;
    color: var(--color-heading, #243141);
}

body.template-nebulous .whiz-help-mark {
    /* OFFSETS ARE FROM THE PADDING BOX, INSIDE THE RING. That is the
       whole subtlety here: an absolutely positioned child measures
       left/top from the padding edge, not the border edge, so the 2px
       ring is already spent before these numbers apply. left:7px put
       the mark at 9px from the outer edge and 2px right of centre —
       measured on the live page, not guessed.
       left:5px lands it at 2+5 = 7px, with 7px the other side of a 2px
       mark in a 16px circle. top:2px puts the dot at 4px, and the mark
       is 8px tall (2px dot, 1px gap, 5px stem), leaving 4px clear top
       and bottom. Symmetric both ways, on whole pixels, with no font
       metrics to drift. */
    position: absolute;
    top: 2px;
    left: 5px;
    width: 2px;
    height: 2px;
    background: currentColor;
}

body.template-nebulous .whiz-help-mark::after {
    content: "";
    position: absolute;
    top: 3px;
    left: 0;
    width: 2px;
    height: 5px;
    background: currentColor;
}

body.template-nebulous .whiz-help-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

body.template-nebulous .whiz-help-bubble {
    position: fixed;
    z-index: 2000;
    top: 0;
    left: 0;
    width: 260px;
    max-width: calc(100vw - 24px);
    background: #1f2937;
    color: #fff;
    font-size: 0.8rem;
    font-weight: 400;
    line-height: 1.4;
    text-align: left;
    padding: 0.6rem 0.75rem;
    border-radius: 6px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
    white-space: normal;
    pointer-events: none;
}

/* ---- PHONE NUMBER MANAGEMENT ---------------------------------------

   A GRID OF EQUAL CARDS, not one column of wide panels and not rows
   inside a panel. Every destination a number can ring — each forwarding,
   each greeting — is its own card, so adding one makes the page grow in
   the same visual language rather than lengthening a list nested inside
   something else.

   .ca-grid could not do this: it is a fixed two-column 1.65fr/1fr built
   for the calling-card page's one-big-one-small pairing. Here the
   controls are of roughly equal weight and there are eight or more of
   them, so the grid has to be "as many equal columns as fit".

   MEASURED, NOT GUESSED. .ca-shell is max-width:1180px with a fixed
   250px sidebar and a 28px gap, so the content column never exceeds
   902px however wide the monitor — the cap is the shell, not the
   screen. On Wayne's own ~1013px window it is 720px.

   That makes the minimum track the only real lever:

   Measured on the real page rather than calculated: the content column
   is 606px inside a 997px window, and caps at 902px however wide the
   monitor goes.

       min     606px (his window)   902px (wide)   card width when wide
       210px          2                  4              ~211px
       250px          2                  3              ~287px

   250px. Four columns fitted and looked it — card titles wrapped onto
   two lines, a SIP hostname broke mid-word, and the file picker was
   truncated to "Choose File N...sen". Three comfortable columns beat
   four cramped ones; the count was never the goal.

   min-width: 0 is load-bearing. A grid item defaults to min-width:auto,
   so one long SIP hostname makes its track wider than its share and the
   whole row scrolls sideways — the same trap .card-grid's flex rows hit.
   -------------------------------------------------------------------- */
.phone-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 1.25rem;
    margin-bottom: 1.5rem;
}

.phone-tiles > .ca-card {
    min-width: 0;
    /* Column, so the action row can be pinned to the bottom and every
       card in a row ends on the same line however much text is above. */
    display: flex;
    flex-direction: column;
    padding: 1.3rem 1.4rem 1.4rem;
}

.phone-tiles .ca-card-title {
    font-size: 1rem;
    margin-bottom: 0.5rem;
}

.phone-tiles .ca-card-lead {
    font-size: 0.85rem;
    line-height: 1.5;
    margin: 0 0 1rem;
}

/*
 * A CARD THAT HOLDS A TABLE GETS TWO COLUMNS.
 *
 * A table in a 250px track has nowhere to put its columns, so
 * .ca-table-wrap does the only thing it can and scrolls sideways --
 * which hides the data and puts a scrollbar under a card that has
 * plenty of room to its right. Two tracks is enough for every table on
 * this page.
 *
 * span 2, NOT 1 / -1: full width would stretch a four-column table
 * across the whole page on a wide screen, which is worse to read than
 * the scrollbar was.
 */
.phone-tile-wide {
    grid-column: span 2;
}

/*
 * THE AUDIO PLAYER IS THE WIDEST THING IN ANY OF THESE TABLES.
 *
 * Chrome gives <audio controls> a natural width around 300px, which on
 * its own put the messages table seven pixels past its card and brought
 * the scrollbar back after the card had been widened to fix exactly
 * that. Measured, not guessed -- 562px of table in 555px of card.
 *
 * 220px still shows play, the scrubber and the time.
 */
.phone-tiles .ca-table audio {
    width: 220px;
    /*
     * A FLOOR, because the table will happily take the space back.
     * With only max-width:100% the auto layout squeezed the player to
     * 158px, which drops the scrubber -- leaving a voicemail you can
     * start but cannot skip through. Measured in the browser; the card
     * has 555px and the table needs about 482px at this size, so the
     * room was always there.
     */
    min-width: 200px;
    vertical-align: middle;
}

/*
 * UNWOUND WHEN THERE IS ONLY ONE COLUMN TO SPAN.
 *
 * auto-fit collapses empty tracks, but an item asking for two forces
 * the grid to keep two -- so on a phone this would halve every card
 * instead of widening one. The breakpoint is where the grid itself
 * stops fitting two 250px tracks beside the client-area sidebar.
 */
@media (max-width: 760px) {
    .phone-tile-wide {
        grid-column: auto;
    }
}

/* Pushes whatever follows it to the bottom of the card. */
.phone-tile-spacer {
    flex: 1 1 auto;
}

/* NO SPANNING CARDS. A `grid-column: span 2` looks right at three or
   four columns and breaks at two: the spanning card cannot fit beside
   its neighbour, drops to the next row, and leaves a visible hole where
   it should have been. Two columns is exactly what Wayne's own window
   produces, so the broken case was the common one.

   A media query cannot fix it either — the breakpoint would have to
   watch the CONTENT column, which is 606px inside a 997px window, and
   media queries only see the viewport. Equal cards throughout, and the
   ordering carries the emphasis instead. */

/* THE CARD THAT IS CURRENTLY RINGING. The accent bar every .ca-card
   already carries is widened and the border picks up the accent, so the
   live destination is identifiable at a glance without a badge
   competing with the title for the top-right corner. */
.phone-tiles > .ca-card.is-active {
    border-color: var(--color-primary-background);
}

.phone-tiles > .ca-card.is-active::before {
    width: 100%;
}

.phone-tile-badge {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    background: var(--color-primary-background);
    color: #fff;
    margin-bottom: 0.6rem;
    align-self: flex-start;
}

/* The number itself. One fact, stated once, at the size it deserves. */
.phone-hero {
    margin-bottom: 1.25rem;
}

.phone-hero .phone-hero-number {
    font-size: 1.9rem;
    font-weight: 700;
    line-height: 1.15;
    color: var(--color-heading);
    margin: 0 0 0.25rem;
    overflow-wrap: anywhere;
}

/* Label/value pairs inside a tile — the SIP settings. dl rather than a
   table because at 250px wide a two-column table wraps into porridge. */
.phone-facts {
    margin: 0;
}

/* STACKED, not label-left/value-right. Side by side, the value gets
   whatever the label leaves — and `vancouver1.globalwebconcepts.net` is
   longer than that remainder, so it broke mid-word into "globa" and
   "lwebconcepts.net". Given the full width of the card it fits on one
   line. */
.phone-facts > div {
    display: block;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--color-box-background-border);
}

.phone-facts > div:last-child {
    border-bottom: none;
}

.phone-facts dt {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted, #6b7280);
    margin: 0 0 0.15rem;
}

.phone-facts dd {
    margin: 0;
    font-size: 0.875rem;
    min-width: 0;
    overflow-wrap: anywhere;
}

.phone-tiles label {
    display: block;
    font-size: 0.78rem;
    font-weight: 600;
    margin: 0 0 0.3rem;
    color: var(--color-heading);
}

.phone-tiles select,
.phone-tiles input[type="text"],
.phone-tiles input[type="tel"],
.phone-tiles input[type="email"],
.phone-tiles input[type="password"],
.phone-tiles input[type="file"] {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    margin-bottom: 0.85rem;
    font-size: 0.875rem;
}

.phone-tiles .ca-muted {
    font-size: 0.76rem;
    line-height: 1.45;
    margin: 0.6rem 0 0;
}

/* The action row at the foot of a card. */
.phone-tile-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-top: 0.25rem;
}

/* A destructive action should not look like the primary one beside it,
   and should not shout either — it is a legitimate thing to want. */
.phone-tiles .ca-btn-danger {
    background: transparent;
    border: 1px solid #dc2626;
    color: #dc2626;
}

.phone-tiles .ca-btn-danger:hover {
    background: #dc2626;
    color: #fff;
}
/* ---- THE CARD, BIGGER ON SCREEN ONLY -------------------------------

   The card is drawn 324px wide because that is ID-1 at 96dpi — the size
   of a real credit card — and it has to STAY that size when printed or
   it stops being the object it is imitating. On a screen, beside a row
   of buttons, 324px reads as a thumbnail and its 9px instructions are
   unreadable. Wayne, on both counts: "make the card bigger... it's in
   vert small letters."

   zoom rather than transform: scale(). A transform does not affect
   layout, so the scaled card would overlap whatever follows it and
   need a hand-maintained spacer to compensate; zoom reflows, so the
   page just works. It is supported in every browser this client area
   targets, including Firefox since 126.

   AND IT IS UNDONE FOR PRINT, which is the whole point of doing it
   here rather than changing WIDTH_PX. A printed card must come out at
   ID-1 so it fits a wallet.
   -------------------------------------------------------------------- */
body.template-nebulous .voip-card-wrap {
    zoom: 1.45;
    /* Keeps the zoomed card from overflowing a narrow column. */
    max-width: 100%;
}

@media (max-width: 620px) {
    body.template-nebulous .voip-card-wrap {
        zoom: 1;
    }
}

@media print {
    body.template-nebulous .voip-card-wrap,
    body.template-nebulous .voip-card-real {
        zoom: 1;
    }

}

/* ==========================================================================
   PHONE NUMBER PAGE (2026-09-06)

   Added with the tabbed rebuild, where every setting moved off VoIP.ms
   and onto our own switch.
   ========================================================================== */

/* The destination choice. Radios rather than a select because there are
   five of them, they are the point of the page, and one is "reject every
   call" -- which somebody should have to look at rather than scroll past
   inside a closed dropdown. */
.phone-choices {
    display: grid;
    gap: 0.5rem;
    margin: 0 0 1.1rem;
}
.phone-choice {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-radius: 8px;
    cursor: pointer;
}
/* The whole row reacts, not just the dot -- the label is the target. */
.phone-choice:hover {
    background: var(--color-box-background-divider, #f4f4f4);
}
.phone-choice input {
    /* Aligns the control with the first line of text rather than the
       centre of a two-line block. */
    margin-top: 0.2rem;
    flex: 0 0 auto;
}
.phone-choice > span {
    display: grid;
    gap: 0.15rem;
    min-width: 0;
}
.phone-choice .ca-muted {
    font-size: 0.86rem;
}

/* A checkbox that reads as one line, not a stray control above a label. */
.phone-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.9rem 0 0.2rem;
    cursor: pointer;
}

/* The one-time SIP password. Deliberately loud: it is shown once, cannot
   be shown again, and somebody scrolling past it has lost it. */
.phone-credential {
    border-color: var(--color-primary-background, #f09546);
}
.phone-credential code {
    font-size: 1.02rem;
    /* Selectable in one gesture, because this gets copied into a phone. */
    user-select: all;
    word-break: break-all;
}

/* Buttons that belong to one table row. Wraps rather than forcing the
   row wider, which on a phone is what pushes a table into a horizontal
   scroll nobody finds. */
.phone-inline-form {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

/* ==========================================================================
   THE BROWSER SOFTPHONE (2026-09-07)

   A telephone, so it is laid out like one: a display, a keypad, and the
   controls under it. Deliberately not a form -- nothing here is
   submitted, and making it look like one would invite somebody to press
   Enter expecting a page to save.
   ========================================================================== */

.webphone {
    display: grid;
    gap: 0.9rem;
    max-width: 22rem;
}

/* The display. Reads as a status line rather than an alert, because it
   spends most of its life saying something unremarkable. */
.webphone-status {
    margin: 0;
    padding: 0.6rem 0.8rem;
    border-radius: 8px;
    background: var(--color-box-background-divider, #f4f4f4);
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}
.webphone-status.is-live {
    background: #e7f5ec;
    color: #1c6b3c;
}
.webphone-status.is-busy {
    background: #fdf1e3;
    color: #8a4b12;
}
.webphone-status.is-error {
    background: #fdeaea;
    color: #97231f;
}

.webphone-number {
    width: 100%;
    font-size: 1.15rem;
    font-variant-numeric: tabular-nums;
    /* A phone number is read in groups; letter-spacing is what makes a
       mistyped digit visible before the call is placed. */
    letter-spacing: 0.04em;
}

.webphone-keys {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.4rem;
}

.webphone-key {
    padding: 0.75rem 0;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-radius: 8px;
    background: var(--color-box-background, #ffffff);
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
    cursor: pointer;
    /* Big enough to hit with a thumb, which is how a phone is used. */
    min-height: 2.75rem;
}
.webphone-key:hover {
    background: var(--color-box-background-divider, #f4f4f4);
}
.webphone-key:active {
    /* A key that does not move under the finger feels broken on a
       touchscreen, where there is no other feedback. */
    transform: translateY(1px);
}

.webphone-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

/* The number being dialled, above the keypad. Its own row so the label
   sits on top rather than beside a field people type digits into. */
.webphone-dial {
    display: grid;
    gap: 0.25rem;
}

/* ==========================================================================
   THE ADD-CREDIT DIALOG (2026-09-07)

   Buying credit used to leave the page for the payment screen and come
   back. On a page whose whole purpose is a telephone you are using, that
   means hanging up to pay.
   ========================================================================== */

/*
 * THE HIDDEN ATTRIBUTE NEEDS HELP HERE, and this is not optional.
 *
 * `display: flex` below is an AUTHOR display, which beats the browser's
 * own [hidden] rule -- so el.hidden would set the attribute, change
 * nothing, and leave this dialog permanently across every page that
 * renders it. This file has no global [hidden] guard, only per-component
 * ones (see .chat-widget-panel), so each new component that toggles
 * visibility has to bring its own.
 */
.ca-modal[hidden] {
    display: none;
}

.ca-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.ca-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(16, 24, 40, 0.55);
}
.ca-modal-panel {
    position: relative;
    width: 100%;
    max-width: 26rem;
    max-height: 90vh;
    /* Scrolls inside itself: a payment form on a short screen must not
       push its own Pay button off the bottom of the window. */
    overflow-y: auto;
    padding: 1.5rem;
    border-radius: 12px;
    background: var(--color-box-background, #ffffff);
    box-shadow: 0 12px 40px rgba(16, 24, 40, 0.25);
}
.ca-modal-title {
    margin: 0 0 0.9rem;
    font-size: 1.15rem;
}
.ca-modal-close {
    position: absolute;
    top: 0.5rem;
    right: 0.7rem;
    border: 0;
    background: none;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    color: var(--color-body, #696969);
}
.ca-modal-message {
    margin: 0 0 0.9rem;
    padding: 0.55rem 0.7rem;
    border-radius: 8px;
    background: var(--color-box-background-divider, #f4f4f4);
    font-size: 0.9rem;
}
.ca-modal-message.is-error {
    background: #fdeaea;
    color: #97231f;
}
.ca-modal-summary {
    margin: 0 0 0.9rem;
    font-weight: 600;
}
.ca-modal-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 1.1rem;
}

.credit-amount-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.credit-amount-currency {
    font-size: 1.1rem;
    font-weight: 600;
}

/* One method per row. Payment choices read as a list of decisions, not
   a row of buttons where the important one is whichever is widest. */
.credit-methods {
    display: grid;
    gap: 0.45rem;
}
.credit-method {
    width: 100%;
    text-align: center;
}
.credit-gateway {
    margin-top: 1rem;
}
.credit-done {
    margin: 0;
    font-weight: 600;
    color: #1c6b3c;
}

/* One step of the dialog at a time. Hidden with the attribute rather
   than a class, and given no display of its own -- an author display
   here would beat [hidden] and the JS would silently do nothing, which
   this codebase has been caught by before. */
.credit-step {
    margin: 0;
}

/* ==========================================================================
   SOFTPHONE — REDIAL (2026-09-07)

   The call-control rules that used to live here were built on .ca-btn
   and are gone: see SOFTPHONE - THEME A below, which replaced them.
   Leaving both would have meant two sets of colours competing for the
   same buttons, decided by source order.
   ========================================================================== */

/* ---- Recent numbers, beside the keypad ---------------------------- */

.webphone-recent {
    min-width: 0;
}
.webphone-recent-title {
    margin: 0 0 0.5rem;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-body, #696969);
}
.webphone-recent-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 0.3rem;
}
.webphone-recent-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-radius: 6px;
}
.webphone-recent-number {
    font-variant-numeric: tabular-nums;
    font-size: 0.9rem;
    /* A long number must not push the redial button off the row. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/*
 * TWO CLASSES, DELIBERATELY. The theme paints every button --
 * `body.template-nebulous button`, specificity (0,1,2) -- so a single
 * class at (0,1,0) loses and the redial button came out brand orange
 * instead of green. Naming the list as well makes it (0,2,0), which
 * wins on class count.
 *
 * Structural rather than prefixed with the body class, so an install
 * running a different template still gets a green button rather than
 * losing the rule to a class it does not have.
 */
.webphone-recent-list .webphone-redial {
    flex: 0 0 auto;
    width: 1.9rem;
    height: 1.9rem;
    border: 0;
    border-radius: 50%;
    background: #1c8c4a;
    color: #ffffff;
    cursor: pointer;
    font-size: 0.8rem;
    line-height: 1;
}
.webphone-recent-list .webphone-redial:hover {
    background: #17773e;
}

/* The dialer and the recent list, side by side where there is room.
   The keypad has a natural width and the list does not, so the dialer
   is sized and the list takes what is left. */
.webphone-layout {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: 1fr;
}
@media (min-width: 620px) {
    .webphone-layout {
        /*
         * The dialer is sized to its keypad and the list takes what is
         * left. minmax(0, ...) on BOTH tracks is what stops a long
         * number widening the grid and pushing the card into a
         * horizontal scroll -- a grid track's default minimum is its
         * content, not zero, which is the usual cause of that.
         */
        grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
        align-items: start;
    }
}

/* Shown only when there is nothing to redial. No display of its own, so
   the hidden attribute keeps working -- an author display here would
   beat it and the message would sit under a full list. */
.webphone-recent-empty {
    margin: 0;
}

/* ==========================================================================
   CREDIT / HISTORY CARD (2026-09-07)

   Replaced a paragraph explaining how the phone works -- a thing read
   once -- with the two questions people return for: how much is left,
   and where did the rest go.
   ========================================================================== */

.webphone-credit-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-body, #696969);
}

/* Three figures somebody checks without reading a list. */
.webphone-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.4rem;
    margin: 0.9rem 0 0;
}
.webphone-stat {
    background: var(--color-box-background-divider, #f4f4f4);
    border-radius: 6px;
    padding: 0.45rem 0.4rem;
    text-align: center;
    min-width: 0;
}
.webphone-stat b {
    display: block;
    font-size: 0.95rem;
    font-variant-numeric: tabular-nums;
    /* A long talk time must shrink rather than widen the card. */
    overflow: hidden;
    text-overflow: ellipsis;
}
.webphone-stat span {
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-body, #696969);
}

/* Small folder tabs, matching the page's own -- the same idea at a
   smaller size, rather than a second way of showing tabs. */
.webphone-histtabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin: 0.9rem 0 0;
    border-bottom: 1px solid var(--color-box-background-border, #d2d2d2);
}
.webphone-histtabs .webphone-histtab {
    padding: 0.35rem 0.7rem;
    margin-bottom: -1px;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-bottom-color: var(--color-box-background-border, #d2d2d2);
    border-radius: 4px 4px 0 0;
    background: var(--color-box-background-divider, #f4f4f4);
    color: var(--color-primary-background, #f09546);
    font-size: 0.78rem;
    font-weight: 500;
    cursor: pointer;
}
.webphone-histtabs .webphone-histtab.is-on {
    background: var(--color-box-background, #ffffff);
    border-bottom-color: var(--color-box-background, #ffffff);
    color: var(--color-heading, #243141);
    font-weight: 700;
}

/*
 * THE LIST SCROLLS, THE CARD DOES NOT GROW. A history that stretches
 * the card pushes everything below it off the screen -- and this card
 * sits beside a telephone somebody may be using.
 */
.webphone-histscroll {
    max-height: 190px;
    overflow-y: auto;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-top: 0;
    border-radius: 0 0 6px 6px;
}

/* No display of its own, so [hidden] keeps working on the panes. */
.webphone-histpane {
    margin: 0;
}

.webphone-histrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4rem 0.55rem;
    border-bottom: 1px solid var(--color-box-background-divider, #f4f4f4);
    font-size: 0.83rem;
}
.webphone-histrow:last-child { border-bottom: 0; }

.webphone-histmain {
    min-width: 0;
    /* A long number wraps its timestamp underneath rather than pushing
       the amount out of the row. */
    display: flex;
    flex-direction: column;
}
.webphone-histwhen {
    color: var(--color-body, #696969);
    font-size: 0.76rem;
}
.webphone-histamt {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.webphone-histamt.is-in,
.webphone-histmain i.is-in {
    color: #1c8c4a;
    font-weight: 600;
}
.webphone-histempty {
    margin: 0;
    padding: 0.7rem 0.55rem;
    color: var(--color-body, #696969);
    font-size: 0.83rem;
}

/* ==========================================================================
   SOFTPHONE — THEME A (2026-09-07)

   A black keypad tray with white keys, and four controls on one row.

   NOT BUILT ON .ca-btn. The controls were, and inherited the site's
   button styling, which is why they all came out the same orange: a
   component whose whole job is to say "green calls, red ends" cannot
   borrow its colours from a brand palette. These carry their own.
   ========================================================================== */

.webphone-keypanel {
    background: #15181d;
    /* The brand colour as an EDGE rather than a fill. Filling the tray
       orange puts a third loud colour directly above the green Call and
       the red Hang up, and during a call those two have to be the
       loudest things on the card. As a frame it brands the keypad and
       leaves that hierarchy alone.

       The token, not a literal, so an install running its own palette
       gets its own colour here. Safe to use: the only redefinition of
       --color-primary-background is scoped to .button1.color1, so this
       resolves to the :root value rather than an invalid one -- which
       would kill the whole declaration and draw no border at all. */
    border: 2px solid var(--color-primary-background, #f09546);
    border-radius: 10px;
    padding: 10px;
    margin: 0 0 0.75rem;
}
.webphone-keypanel .webphone-keys {
    margin: 0;
    gap: 6px;
}

/* White keys on the tray. The theme's own key styling is overridden
   here because it painted them like page buttons. */
.webphone-keypanel .webphone-key {
    background: #ffffff;
    /* A hairline instead of the drop shadow. The shadow separated the
       keys from a black tray; against the orange frame the outline does
       that job and keeps the row of keys reading as one block. */
    border: 1px solid #15181d;
    border-radius: 8px;
    color: #15181d;
    min-height: 2.5rem;
    font-size: 1rem;
}
.webphone-keypanel .webphone-key:hover {
    background: #eef1f5;
}
.webphone-keypanel .webphone-key:active {
    transform: translateY(1px);
}

/* ---- The control row -------------------------------------------- */

.webphone-actions {
    display: grid;
    /* Four across, always. minmax(0,...) so a long word shrinks the
       label rather than widening the row into a scroll. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 5px;
}

.webphone-actions .webphone-ctrl {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 0.42rem 0.2rem;
    border: 1px solid transparent;
    border-radius: 8px;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.1;
    cursor: pointer;
    min-width: 0;
}
.webphone-actions .webphone-ctrl i {
    font-size: 0.85rem;
}
.webphone-actions .webphone-ctrl span {
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

.webphone-actions .is-connect { background: #15181d; color: #ffffff; }
.webphone-actions .is-connect:hover:not(:disabled) { background: #23272f; }

.webphone-actions .is-call { background: #1c8c4a; color: #ffffff; }
.webphone-actions .is-call:hover:not(:disabled) { background: #17773e; }

.webphone-actions .is-hangup { background: #c0392b; color: #ffffff; }
.webphone-actions .is-hangup:hover:not(:disabled) { background: #a52f23; }

/* Mute is a STATE, not an action, so it carries no colour of its own. */
.webphone-actions .is-mute {
    background: #ffffff;
    border-color: #d7dade;
    color: #3a4250;
}
.webphone-actions .is-mute:hover:not(:disabled) { background: #f2f4f7; }

/* Muted: the one time this button does take colour, because it is the
   thing people check fastest on a call. */
.webphone-actions .is-mute.is-muted {
    background: #fdeaea;
    border-color: #c0392b;
    color: #97231f;
}

/*
 * DIMMED, NOT REPAINTED.
 *
 * Disabled buttons used to turn flat grey, so before a call the row
 * showed one coloured button and three colourless ones -- which read as
 * the colours having failed rather than as controls waiting for
 * something to act on. Keeping their colour at reduced opacity means
 * the row looks the same shape before and during a call.
 */
.webphone-actions .webphone-ctrl:disabled {
    opacity: 0.45;
    cursor: default;
}

/* ---- The video stage (2026-09-07) ---------------------------------
   Below the phone, so the controls stay where the hand already is when
   video starts. Hidden until a call actually carries video. */

/*
 * [hidden] NEEDS HELP HERE. The rule below sets an AUTHOR display,
 * which outranks the browser's own [hidden] rule -- so el.hidden would
 * set the attribute, change nothing, and leave a black rectangle under
 * the keypad on every page that renders the phone. This file has no
 * global [hidden] guard, only per-component ones, so each component
 * that toggles visibility has to bring its own.
 */
.webphone-stage[hidden] {
    display: none;
}

.webphone-stage {
    position: relative;
    display: block;
    margin: 0.75rem 0 0;
    border: 2px solid var(--color-primary-background, #f09546);
    border-radius: 10px;
    overflow: hidden;
    background: #15181d;
    /* Holds the box before any pixels arrive, so the card does not jump
       when the far end's camera comes up. */
    aspect-ratio: 16 / 9;
}

.webphone-remote-video {
    display: block;
    width: 100%;
    height: 100%;
    /* contain, not cover: a caller's face cropped to fill the box is
       worse than letterboxing, and portrait phones are common. */
    object-fit: contain;
    background: #15181d;
}

.webphone-local-video {
    position: absolute;
    right: 8px;
    bottom: 8px;
    width: 27%;
    max-width: 130px;
    border-radius: 6px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    background: #0c0e11;
    object-fit: cover;
    /* Mirrored, because a preview of yourself that moves the wrong way
       when you move is genuinely disorienting. */
    transform: scaleX(-1);
}
.webphone-local-video[hidden] {
    display: none;
}

.webphone-stage-hint {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    color: #c9d1d9;
    font-size: 0.82rem;
    pointer-events: none;
}
.webphone-stage-hint[hidden] {
    display: none;
}

.webphone-stage-full {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    width: 30px;
    height: 30px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 6px;
    background: rgba(12, 14, 17, 0.62);
    color: #ffffff;
    cursor: pointer;
    font-size: 0.8rem;
}
.webphone-stage-full:hover {
    background: rgba(12, 14, 17, 0.85);
}

/* Fullscreen paints its own black behind the element, so the stage has
   to fill it or the video sits in a small box in the middle of it. */
.webphone-stage:fullscreen {
    aspect-ratio: auto;
    width: 100%;
    height: 100%;
    border: 0;
    border-radius: 0;
}

/*
 * THE FALLBACK FOR PHONES, where the Fullscreen API is not dependable:
 * iOS Safari will not fullscreen an arbitrary element at all, and
 * Android Chrome refuses it often enough that a button which sometimes
 * does nothing is worse than none.
 *
 * Filling the viewport with CSS needs no API, no permission and no
 * gesture rules, and to somebody holding a phone it looks the same.
 */
.webphone-stage.is-expanded {
    position: fixed;
    inset: 0;
    z-index: 2000;
    width: 100vw;
    /* dvh, not vh: on a phone the address bar shrinks the viewport as
       you scroll, and 100vh leaves a strip of page showing under the
       video. Falls back to vh where dvh is unsupported. */
    height: 100vh;
    height: 100dvh;
    aspect-ratio: auto;
    border: 0;
    border-radius: 0;
    background: #000000;
}

.webphone-stage.is-expanded .webphone-remote-video {
    height: 100%;
}

/* The local preview scales up with it -- 27% of a phone screen is a
   postage stamp. */
.webphone-stage.is-expanded .webphone-local-video {
    width: 22%;
    max-width: 200px;
    right: 14px;
    bottom: 14px;
}

/* Nothing behind it should scroll while it is up. */
body.webphone-expanded {
    overflow: hidden;
}

/* ---- The video toggle, beside the number label ---- */

.webphone-dial-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

/*
 * NAMED CONTAINER, because body.template-nebulous button paints every
 * button on the storefront at (0,1,2) and a single class loses to it.
 * .webphone-dial-head .webphone-video-toggle is (0,2,0) and wins.
 */
.webphone-dial-head .webphone-video-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.55rem;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-radius: 999px;
    background: var(--color-box-background, #ffffff);
    color: var(--color-body, #696969);
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.4;
    cursor: pointer;
}
.webphone-dial-head .webphone-video-toggle.is-on {
    background: #15181d;
    border-color: #15181d;
    color: #ffffff;
}
.webphone-dial-head .webphone-video-toggle i {
    font-size: 0.75rem;
}

/* No camera in the machine. Shown rather than hidden, because a control
   that vanishes reads as a missing feature; one that is visibly
   unavailable, with a reason on hover, reads as a machine without a
   camera -- which is the truth. */
.webphone-dial-head .webphone-video-toggle:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* The keyboard shortcuts, said on screen. A keyboard-driven phone
   nobody knows is keyboard-driven is the same as one that is not. */
.webphone-keyhint {
    margin: 0.6rem 0 0;
    font-size: 0.72rem;
    color: var(--color-body, #696969);
    line-height: 1.6;
}
.webphone-keyhint kbd {
    display: inline-block;
    padding: 0 0.28rem;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-bottom-width: 2px;
    border-radius: 4px;
    background: var(--color-box-background, #ffffff);
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--color-heading, #243141);
}

/*
 * THE PHONE GETS AN EDGE.
 *
 * Status, number, keypad and controls were four blocks floating in the
 * card with nothing tying them together, which read as unfinished
 * rather than as one device. A border is what makes them a telephone
 * instead of a list of controls that happen to be adjacent.
 */
.webphone {
    padding: 0.9rem;
    border: 1px solid var(--color-box-background-border, #d2d2d2);
    border-radius: 10px;
    background: var(--color-box-background, #ffffff);
}

/*
 * "Disconnect" was being cut to "Disconn…". The labels were held to one
 * line with an ellipsis, which is right for a phone number and wrong
 * for a word somebody has to read to know what the button does. They
 * wrap now, and the row is given a floor so it does not jump height
 * when a label goes from one line to two.
 */
.webphone-actions .webphone-ctrl {
    min-height: 3.1rem;
    justify-content: center;
}
.webphone-actions .webphone-ctrl span {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    text-align: center;
    /* Breaks a long word rather than pushing the track wider, which
       would put the fourth control off the edge of the card. */
    overflow-wrap: anywhere;
}
