:root {
  --purple: #4f2683;
  --purple-dark: #38185f;
  --purple-light: #f2edf9;
  --white: #ffffff;
  --text: #1a1a1f;
  --muted: #63636e;
  --border: #e4e0ec;
  --good: #1a7f4e;
  /* Header/hero backdrop only, kept separate from --purple-light (selected
     cards, pills) so tuning this doesn't touch the rest of the UI. Lightness
     is ~12% darker than --purple-light (20% darker, then 10% back lighter);
     hue nudged from 265 to 250 (further from pink/magenta, toward blue). */
  --hero-gradient-start: #c8c1ea;
  --hero-gradient: linear-gradient(180deg, var(--hero-gradient-start) 0%, var(--white) 100%);
  --hero-gradient-height: 480px;
}

* { box-sizing: border-box; }

body.public {
  font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  background: var(--white);
  color: var(--text);
  margin: 0;
  line-height: 1.55;
}

a { color: var(--purple); }

/* A real, literally-fixed element behind everything - the hero (and any
   page content that scrolls into the top ~480px) is transparent, so this
   shows through it. Because it's fixed rather than something re-painted on
   scroll, it never itself changes for any reason - only the transparent
   content in front of it moves. */
.public-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--hero-gradient-height);
  background: var(--hero-gradient);
  /* Negative, not 0: a positioned z-index:0 element paints ABOVE ordinary
     static in-flow content regardless of DOM order, which would bury the
     hero heading and section content behind an opaque backdrop. Negative
     z-index is the one stacking layer that paints below static content
     instead, so it only ever shows through where something in front of it
     is actually transparent. */
  z-index: -1;
  pointer-events: none;
}

/* Reserves the header's (constant, full-size) footprint in the document so
   .public-header can be position:fixed - see the header rule below for why
   fixed rather than sticky. base_public.html measures the header once (not
   on every scroll frame) and sets this. */
.public-header-spacer {
  height: var(--header-spacer-h, 90px);
}

.public-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 5%;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Fixed, not sticky: a sticky header still reserves its OWN (shrinking)
     height in document flow, so as its padding and the nav's max-height
     animate down, the space it reserves shrinks with them - and because
     that collapse happens over a short scroll distance, content below gets
     pulled up faster than the finger physically scrolling it, which reads
     as a sudden lurch right as scrolling starts. Fixed removes the header
     from flow entirely, so .public-header-spacer above (constant height)
     is the only thing anything else's position depends on - the header can
     freely animate its own size with zero effect on anyone else's layout.
     Unlike the hero, the header can't just be transparent: it sits in front
     of whatever has scrolled underneath it, so it needs its own OPAQUE
     paint to actually hide that content rather than let it bleed through.
     No background-attachment:fixed here on purpose - mobile browsers
     routinely fail to render a fixed-attachment background at all (falling
     back to background-color, which read as the header going plain white)
     despite reliably supporting position:fixed elements like
     .public-backdrop. It doesn't need the trick anyway: this header is
     already viewport-pinned by its own positioning, so a plain top-anchored
     background shows the same slice of the gradient as .public-backdrop
     regardless. */
  background-image: var(--hero-gradient);
  background-color: var(--white);
  background-repeat: no-repeat;
  background-position: top center;
  background-size: 100% var(--hero-gradient-height);
  z-index: 10;
}
/* A soft fade right at the header's (currently shrinking) bottom edge, so
   content disappearing under it dissolves instead of getting a hard visible
   cut. Positioned off an ::after rather than a sibling element so it always
   tracks the header's own animated height. Same reasoning as the header
   itself on background-attachment: omitted, not needed - this strip is
   pinned to the header's own top-anchored background, not the viewport. */
.public-header::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 24px;
  background-image: var(--hero-gradient);
  background-color: var(--white);
  background-repeat: no-repeat;
  /* Own-box background-position normally starts back at the image's top
     (row 0) for every element, but this strip needs to pick up exactly
     where the header's own background left off (row = the header's current
     height), or the two would visibly disagree at the seam. base_public.html
     tracks the header's live pixel height into --header-h for this. */
  background-position: center calc(-1 * var(--header-h, 0px));
  background-size: 100% var(--hero-gradient-height);
  mask-image: linear-gradient(to bottom, black, transparent);
  -webkit-mask-image: linear-gradient(to bottom, black, transparent);
  pointer-events: none;
}

.wordmark {
  display: flex;
  align-items: center;
  text-decoration: none;
  /* The link's only child is the logo image now. Without this the link keeps
     the descender space an inline box reserves for text that isn't there,
     and the header measures a few pixels taller than the logo it contains -
     which .public-header-spacer would then faithfully reserve. */
  line-height: 0;
}
.wordmark img {
  display: block;
  height: 2.2rem;
  width: auto;
}

.public-nav { display: flex; gap: 1.75rem; align-items: center; flex-wrap: wrap; }
.public-nav a {
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
}
.public-nav a:hover { color: var(--purple); }

.btn-primary {
  display: inline-block;
  background: var(--purple);
  color: var(--white) !important;
  padding: 0.7rem 1.4rem;
  border-radius: 6px;
  font-weight: 700;
  text-decoration: none;
  border: none;
  cursor: pointer;
  font-size: 0.95rem;
}
.btn-primary:hover { background: var(--purple-dark); }

.btn-outline {
  display: inline-block;
  background: transparent;
  color: var(--purple) !important;
  padding: 0.65rem 1.35rem;
  border-radius: 6px;
  font-weight: 700;
  text-decoration: none;
  border: 2px solid var(--purple);
  font-size: 0.95rem;
}

.hero {
  padding: 4.5rem 5% 3.5rem;
  text-align: center;
  background: transparent;
}
.hero h1 {
  font-size: clamp(2rem, 5vw, 3.2rem);
  margin: 0 0 0.75rem;
  color: var(--text);
}
.hero h1 .accent { color: var(--purple); }
.hero p.lead {
  font-size: 1.15rem;
  color: var(--muted);
  max-width: 640px;
  margin: 0 auto 2rem;
}
.hero .cta-row { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }

.section { padding: 3.5rem 5%; max-width: 1100px; margin: 0 auto; }
.section h2 {
  font-size: 1.9rem;
  margin-bottom: 0.5rem;
  text-align: center;
}
.section .subhead {
  text-align: center;
  color: var(--muted);
  margin-bottom: 2.5rem;
  max-width: 600px;
  margin-left: auto;
  margin-right: auto;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1.5rem;
}

.pcard {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.75rem;
  background: var(--white);
  display: flex;
  flex-direction: column;
}
/* Pin the trailing link/button to the card bottom so all cards in a row
   align regardless of how much text each one has. */
.pcard .card-link,
.pcard .btn-primary { margin-top: auto; }
.pcard .card-link {
  padding-top: 1rem;
  font-weight: 600;
  text-decoration: none;
}
.pcard .card-link:hover { text-decoration: underline; }
.pcard h3 { margin-top: 0; color: var(--purple); }
.pcard .tier-trigger {
  font-size: 0.85rem;
  color: var(--muted);
  margin: -0.5rem 0 0.5rem;
}

/* Segmented pill toggle for billing frequency. */
.freq-toggle {
  display: inline-flex;
  background: var(--purple-light);
  border-radius: 999px;
  padding: 5px;
  gap: 4px;
}
.freq-btn {
  background: transparent;
  color: var(--purple);
  border: none;
  border-radius: 999px;
  padding: 0.6rem 1.5rem;
  font-weight: 700;
  font-size: 0.95rem;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.15s, color 0.15s;
}
.freq-btn:hover:not(.active) { background: rgba(79, 38, 131, 0.12); }
.freq-btn.active {
  background: var(--purple);
  color: var(--white);
  box-shadow: 0 2px 6px rgba(79, 38, 131, 0.35);
}
.freq-note { color: var(--muted); font-size: 0.9rem; margin-top: 0.5rem; }

/* /order page */
.order-form .order-step {
  color: var(--purple);
  margin: 1.75rem 0 0.75rem;
  font-size: 1.05rem;
}
.order-form .order-step:first-of-type { margin-top: 0; }

.tier-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.quote-form .tier-option { margin: 0 !important; position: relative; }
.tier-option input[type="radio"] { position: absolute; opacity: 0; }
.tier-option .tier-box {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  border: 2px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  cursor: pointer;
  height: 100%;
}
.tier-option input:checked + .tier-box {
  border-color: var(--purple);
  background: var(--purple-light);
}
.tier-option .tier-sub { font-size: 0.82rem; color: var(--muted); }
.tier-option .tier-price { font-weight: 800; font-size: 1.15rem; margin-top: 0.35rem; }
.tier-option .tier-price em { font-style: normal; font-weight: 500; font-size: 0.85rem; color: var(--muted); }

.option-list { display: flex; flex-direction: column; gap: 0.5rem; }
.option-row {
  display: flex !important;
  align-items: center;
  gap: 0.6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
  cursor: pointer;
  margin: 0 !important;
  font-weight: 500 !important;
}
.option-row input[type="checkbox"] { width: 1.15em; height: 1.15em; flex: 0 0 auto; accent-color: var(--purple); }
.option-row span:not(.option-price) { flex: 1 1 auto; min-width: 0; }
.option-row .option-price { margin-left: auto; flex-shrink: 0; font-weight: 700; color: var(--purple); }
/* .option-row sets display:flex !important, which beats a plain inline
   style.display toggle from JS - this class wins on specificity instead. */
.option-row.option-row-hidden { display: none !important; }

.order-total {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  background: var(--purple);
  color: var(--white);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin-top: 1.25rem;
}
.order-total-num { font-size: 1.9rem; font-weight: 800; }
.order-total-unit { opacity: 0.85; }

@media (max-width: 640px) {
  .tier-picker { grid-template-columns: 1fr; }
}
.pcard .price {
  font-size: 1.8rem;
  font-weight: 800;
  color: var(--text);
  margin: 0.4rem 0 0.9rem;
}
.pcard .price .unit { font-size: 0.95rem; font-weight: 500; color: var(--muted); }
.pcard .price-sm {
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--text);
  margin: 0.4rem 0 0.9rem;
}
.pcard ul { padding-left: 1.1rem; color: var(--muted); margin: 0; }
.pcard ul li { margin-bottom: 0.3rem; }

.band {
  background: var(--purple);
  color: var(--white);
}
.band .section h2, .band .section p { color: var(--white); }
.band .section .subhead { color: #e6dcf5; }

.addon-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1rem;
}
.addon-table th, .addon-table td {
  text-align: left;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
}
.addon-table th { color: var(--muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; }

.trust-row {
  display: flex;
  justify-content: center;
  gap: 2.5rem;
  flex-wrap: wrap;
  padding: 2rem 5%;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.9rem;
}

form.quote-form {
  max-width: 560px;
  margin: 0 auto;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 2rem;
}
form.quote-form label {
  display: block;
  margin: 1rem 0 0.3rem;
  font-weight: 600;
  font-size: 0.9rem;
}
form.quote-form input,
form.quote-form select,
form.quote-form textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
}
form.quote-form input:focus,
form.quote-form select:focus,
form.quote-form textarea:focus {
  outline: 2px solid var(--purple);
  outline-offset: 1px;
}
.field-invalid { border-color: #dc2626 !important; }
.field-error {
  display: block;
  color: #dc2626;
  font-size: 0.8rem;
  margin-top: 0.3rem;
}
.cancel-form .field-error { margin-top: 0.3rem; }
.stripe-card-element {
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--white);
}
.stripe-card-element.StripeElement--focus {
  outline: 2px solid var(--purple);
  outline-offset: 1px;
}
form.quote-form .btn-primary { margin-top: 1.5rem; width: 100%; text-align: center; }
form.quote-form .row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }

.thanks-box {
  text-align: center;
  padding: 4rem 5%;
  max-width: 560px;
  margin: 0 auto;
}
.thanks-box .checkmark {
  font-size: 3rem;
  color: var(--good);
}

.public-footer {
  border-top: 1px solid var(--border);
  padding: 2.5rem 5%;
  text-align: center;
  color: var(--muted);
  font-size: 0.85rem;
}
.public-footer a { color: var(--muted); text-decoration: none; margin: 0 0.5rem; }
.public-footer .footer-logo {
  display: block;
  width: auto;
  height: 120px;
  margin: 0 auto 1.25rem;
}

.placeholder-note {
  background: #fff8e1;
  border: 1px solid #f0d98c;
  color: #6b5c14;
  padding: 0.6rem 0.9rem;
  border-radius: 6px;
  font-size: 0.85rem;
  margin-bottom: 1.5rem;
}

@media (max-width: 640px) {
  .public-header { flex-direction: column; align-items: flex-start; gap: 0.75rem; }
  .public-nav { gap: 1rem; }
  /* Closer to this layout's real (logo + two stacked buttons) height than
     the 90px desktop-ish default, so there's less of a jump once JS
     measures and corrects it on load. */
  .public-header-spacer { height: var(--header-spacer-h, 185px); }
  form.quote-form .row-2 { grid-template-columns: 1fr; }
}

.cancel-section {
  border-top: 1px solid var(--border);
  padding: 2rem 5%;
  text-align: center;
  background: var(--purple-light);
}
.cancel-section h3 { margin: 0 0 0.4rem; color: var(--purple); font-size: 1.1rem; }
.cancel-section p { color: var(--muted); font-size: 0.85rem; margin: 0 auto 1rem; max-width: 420px; }
.cancel-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
  align-items: flex-start;
  max-width: 640px;
  margin: 0 auto;
}
.cancel-form .field-wrap {
  flex: 1 1 160px;
  display: flex;
  flex-direction: column;
}
.cancel-form input {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.9rem;
  font-family: inherit;
  width: 100%;
}
.cancel-form .btn-outline { flex: 0 0 auto; }

@media (max-width: 640px) {
  .cancel-form { flex-direction: column; }
  .cancel-form .field-wrap, .cancel-form .btn-outline { flex: 1 1 auto; width: 100%; }
}

.legal-content h1 { font-size: 1.7rem; color: var(--purple); margin-top: 2rem; }
.legal-content h2 { font-size: 1.15rem; color: var(--purple); margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.legal-content h2:first-of-type { border-top: none; padding-top: 0; }
.legal-content p { color: var(--text); }
.legal-content ul { padding-left: 1.3rem; }
.legal-content li { margin-bottom: 0.6rem; color: var(--text); }
.legal-content strong { color: var(--purple-dark); }

/* A full sticky header (logo, nav, two buttons) ate too much of the
   viewport once scrolled, especially on mobile where the nav wraps onto its
   own row. base_public.html's inline script drives --shrink continuously
   from 0 (top of page) to 1 (scrolled past _SHRINK_RANGE_PX) on every public
   page - not a threshold snap, so it reads as one fluid motion regardless of
   scroll speed. Short transitions below just smooth over individual scroll
   frames; they aren't what makes this feel gradual. */
.public-header {
  --shrink: 0;
  padding-top: calc(1.1rem - (0.6rem * var(--shrink)));
  padding-bottom: calc(1.1rem - (0.6rem * var(--shrink)));
  transition: padding-top 0.12s linear, padding-bottom 0.12s linear;
}
.public-header .wordmark img {
  height: calc(2.2rem - (0.5rem * var(--shrink)));
  transition: height 0.12s linear;
}
.public-header .public-nav {
  opacity: calc(1 - var(--shrink));
  max-height: calc(200px - (200px * var(--shrink)));
  overflow: hidden;
  transition: opacity 0.12s linear, max-height 0.12s linear;
}

@media (max-width: 640px) {
  /* On the row layout (desktop) fading opacity and collapsing max-height
     over the same range looks fine - there's nothing beneath the fading
     buttons to reveal, and the header's total height barely changes (it's
     one row regardless). On this column layout, the two stacked buttons ARE
     most of the header's height, so how fast that height changes matters:
     it used to stagger opacity and max-height (opacity finishes fading
     first, then max-height collapses) specifically so a still-opaque button
     never got clipped mid-height. That fixed the clipping, but it also made
     the header's total height shrink very unevenly - barely at all at
     first, then very fast - and hero text scrolling past at a constant rate
     doesn't shrink unevenly to match: during the fast phase the header's
     edge can retreat faster than the text is scrolling toward it, which
     un-covers text that had already started disappearing under the header,
     before it covers it again for good once the header settles.

     Fixed with two changes from the base rule: uniform timing (no stagger,
     matching desktop's rate), and swapping max-height/overflow:hidden for
     transform:scaleY + a matching negative margin-bottom. max-height
     seemed like it should work here too (its clip region and the scaled
     content should shrink in lockstep, since both key off the same
     --shrink) but overflow clipping is computed against the element's
     pre-transform layout box, not what the transform actually paints, and
     in testing that mismatch clipped the second button almost to nothing.
     margin-bottom sidesteps that entirely: it's a real layout property, so
     it reclaims space exactly like max-height would have, without any
     clip region to disagree with what scaleY is painting. */
  .public-header .public-nav {
    transform-origin: top;
    transform: scaleY(calc(1 - var(--shrink)));
    margin-bottom: calc(-1 * var(--nav-natural-h, 130px) * var(--shrink));
    max-height: none;
    overflow: visible;
    transition: opacity 0.12s linear, transform 0.12s linear, margin-bottom 0.12s linear;
  }
  /* The logo is a single wide lockup (roughly 6:1), so at the desktop height
     it already eats most of a narrow screen's width. Start it smaller and
     hold it there for the whole scroll range: on this column layout the nav
     collapsing is doing all the height-saving that matters, and a logo that
     also shrinks just adds a second thing moving at a different rate to the
     hero text scrolling past it. */
  .public-header .wordmark img {
    height: 1.75rem;
    transition: none;
  }
}

/* --- Brand artwork ---------------------------------------------------------
   The truck illustration is decoration around copy that has to stand on its
   own: it is free to drop to zero columns on a narrow screen, and it carries
   no information that isn't also in the text beside it. The mascot used to
   sit in the hero and on the thank-you pages; it was taken off the site on
   2026-09-14 and now appears only in the ads. */

.cta-split {
  display: grid;
  gap: 1.75rem;
  align-items: center;
  justify-items: center;
  /* Narrower than the 1100px .section it sits in. Left to fill that width,
     the two columns end up at opposite ends of the screen and stop reading
     as one unit - the picture wants to sit next to the sentence, not across
     the page from it. */
  max-width: 760px;
  margin: 0 auto;
}
.cta-art {
  display: block;
  width: 100%;
  max-width: 250px;
  height: auto;
  border-radius: 12px;
  box-shadow: 0 8px 22px rgba(79, 38, 131, 0.22);
}
@media (min-width: 760px) {
  .cta-split {
    grid-template-columns: auto 1fr;
    text-align: left;
    gap: 2.5rem;
  }
  /* .section h2 and .section .subhead both hard-code text-align:center, which
     outranks the inherited value from .cta-split - so hand them the grid
     cell's alignment back explicitly. */
  .cta-split h2,
  .cta-split .subhead { text-align: inherit; }
  .cta-split .subhead { margin-left: 0; margin-right: 0; }
}

/* --- Before/after proof band -----------------------------------------------
   Its own near-black ground rather than the purple .band: the photograph is a
   night shot whose own edges fall away to black, and on purple it read as a
   picture stuck onto a coloured panel instead of the panel being the night. */
.band-night { background: #131319; }
.band-night .section h2 { color: var(--white); }
.band-night .section .subhead { color: #b6b2c4; }
.proof-shot {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  /* Inset, so it doesn't add to the element's box and shift the layout - the
     photograph's dark corners need an edge or they dissolve into the band. */
  outline: 1px solid rgba(255, 255, 255, 0.16);
  outline-offset: -1px;
}
