/* Creon Studio, creonos.co.uk
 *
 * A site that sells web design has to be evidence of it, so this is written by hand rather than
 * pulled from a framework: nothing to download, nothing to parse, and a first paint that beats
 * the tired sites it is pitching against.
 *
 * Tokens are declared once on :root and then only REDEFINED in the theme blocks. A colour whose
 * only definition sits inside a media query never applies in the default un-stamped state, which
 * is how a page ends up rendering one theme's text on the other theme's ground.
 */

:root {
  --paper:      #FBFAF7;
  --raise:      #FFFFFF;
  --ink:        #101416;
  --ink-soft:   #56625F;
  --rule:       #E3E0D8;
  --accent:     #0B5D5A;
  --accent-on:  #FFFFFF;
  --wash:       #EAF1EF;
  --hot:        #C2551F;
  /* The "before" columns in a results chart: a grey, so the accent carries the one thing the
     chart says. Validated against --paper and --accent (dataviz validate_palette.js): contrast
     over 3:1, and 18.7 apart even for colour blind readers. */
  --bar-before: #858F8C;
  --shadow-sm:  0 1px 2px rgba(16, 20, 22, .05);
  --shadow-lg:  0 2px 4px rgba(16, 20, 22, .04), 0 18px 40px -28px rgba(16, 20, 22, .5);

  --measure:    64ch;
  --step--1:    .875rem;
  --step-0:     1rem;
  --step-1:     1.19rem;
  --step-2:     1.6rem;
  --step-3:     2.1rem;
  --step-4:     clamp(2.4rem, 5.5vw, 3.4rem);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:     #0E1211;
    --raise:     #151A19;
    --ink:       #E9E7E1;
    --ink-soft:  #97A29E;
    --rule:      #262D2B;
    --accent:    #52B3A6;
    --accent-on: #06110F;
    --wash:      #18211F;
    --hot:       #E07A45;
    --bar-before: #6B7572;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow-lg: 0 2px 4px rgba(0, 0, 0, .4), 0 18px 40px -28px rgba(0, 0, 0, .9);
  }
}

:root[data-theme="dark"] {
  --paper:     #0E1211;
  --raise:     #151A19;
  --ink:       #E9E7E1;
  --ink-soft:  #97A29E;
  --rule:      #262D2B;
  --accent:    #52B3A6;
  --accent-on: #06110F;
  --wash:      #18211F;
  --hot:       #E07A45;
  --bar-before: #6B7572;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow-lg: 0 2px 4px rgba(0, 0, 0, .4), 0 18px 40px -28px rgba(0, 0, 0, .9);
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: var(--step-0);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

.wrap { width: 100%; max-width: 68rem; margin-inline: auto; padding-inline: 1.25rem; }
.wrap.narrow { max-width: 44rem; }

.skip {
  position: absolute; left: -999px;
  background: var(--accent); color: var(--accent-on);
  padding: .6rem 1rem; border-radius: 3px; z-index: 20;
}
.skip:focus { left: .75rem; top: .75rem; }

a { color: var(--accent); text-underline-offset: .18em; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

h1, h2, h3, .logo {
  font-family: "Bricolage Grotesque", "Public Sans", system-ui, sans-serif;
  font-weight: 700;
  letter-spacing: -.02em;
  text-wrap: balance;
  margin: 0;
}
h1 { font-size: var(--step-4); line-height: 1.04; }
h2 { font-size: var(--step-3); line-height: 1.12; }
h3 { font-size: var(--step-1); line-height: 1.25; }

p { margin: 0 0 1.05rem; }
.lede { font-size: var(--step-1); color: var(--ink-soft); max-width: var(--measure); }
.sub  { color: var(--ink-soft); margin-top: -.3rem; }
.small { font-size: var(--step--1); color: var(--ink-soft); }

.eyebrow {
  font-size: .72rem; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 .7rem;
}

/* ── header ─────────────────────────────────────────────────────────────────────────────── */
header.site {
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(8px);
  position: sticky; top: 0; z-index: 10;
}
header.site .bar {
  max-width: 68rem; margin-inline: auto; padding: .85rem 1.25rem;
  display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;
}
.logo { font-size: 1.12rem; text-decoration: none; color: var(--ink); white-space: nowrap; }
.logo span { color: var(--accent); }
header.site nav { flex: 1 1 auto; }
header.site nav ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 1.1rem;
  font-size: var(--step--1); font-weight: 500;
}
header.site nav a { color: var(--ink-soft); text-decoration: none; }
header.site nav a:hover, header.site nav a[aria-current="page"] { color: var(--accent); }
.call {
  font-weight: 600; font-size: var(--step--1); white-space: nowrap;
  text-decoration: none; color: var(--ink);
  border: 1px solid var(--rule); border-radius: 999px; padding: .34rem .85rem;
}
.call:hover { border-color: var(--accent); color: var(--accent); }

/* ── hero ───────────────────────────────────────────────────────────────────────────────── */
.hero { padding: 4.5rem 0 3rem; }
.hero h1 { max-width: 17ch; }
.hero .lede { margin-top: 1.1rem; }

.actions { display: flex; flex-wrap: wrap; gap: .7rem; margin: 1.6rem 0 .8rem; }
.btn {
  display: inline-block; text-decoration: none; font-weight: 600;
  padding: .72rem 1.3rem; border-radius: 3px;
  background: var(--accent); color: var(--accent-on);
  border: 1px solid var(--accent);
  transition: transform .12s ease, filter .12s ease;
}
.btn:hover { filter: brightness(1.08); transform: translateY(-1px); }
.btn.ghost { background: transparent; color: var(--ink); border-color: var(--rule); }
.btn.ghost:hover { border-color: var(--accent); color: var(--accent); }

/* ── the number strip ───────────────────────────────────────────────────────────────────── */
.strip { border-block: 1px solid var(--rule); background: var(--wash); }
.strip dl {
  margin: 0; display: grid; gap: 0;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
/* `.strip div` also matched the .wrap DIV that contains the list, and at specificity
   (0,1,1) it beat `.wrap` (0,1,0) and zeroed its left padding. The whole strip has been
   flush to the viewport edge since the site was built, on desktop as well, while every
   other section was inset. Scoped to the list's own children. */
.strip dl > div { padding: 1.1rem 1.25rem 1.1rem 0; }
.strip dt {
  font-size: .7rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: .2rem;
}
.strip dd {
  margin: 0; font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
  font-size: 1.45rem; font-variant-numeric: tabular-nums;
}

/* ── bands ──────────────────────────────────────────────────────────────────────────────── */
.band { padding: 3.6rem 0; }
.band.alt { background: var(--wash); border-block: 1px solid var(--rule); }
.band h2 + .sub { margin-bottom: 1.6rem; }
.band h2 { margin-bottom: .35rem; }

.grid { display: grid; gap: 1rem; margin-top: 1.5rem; }
.work-grid { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.svc-grid  { grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }

/* ── work cards: a browser frame, honestly empty until real screenshots exist ───────────── */
.work {
  display: flex; flex-direction: column; text-decoration: none; color: inherit;
  background: var(--raise); border: 1px solid var(--rule); border-radius: 5px;
  overflow: hidden; box-shadow: var(--shadow-sm);
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}
.work:hover {
  transform: translateY(-2px); box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--rule));
}
.work .chrome {
  display: flex; align-items: center; gap: .34rem;
  padding: .55rem .7rem; border-bottom: 1px solid var(--rule);
  background: linear-gradient(180deg,
              hsl(var(--hue, 200) 30% 92% / .55), hsl(var(--hue, 200) 30% 88% / .3));
  font-size: .74rem; color: var(--ink-soft);
}
:root[data-theme="dark"] .work .chrome,
:root:not([data-theme="light"]) .work .chrome {
  background: linear-gradient(180deg,
              hsl(var(--hue, 200) 28% 20% / .8), hsl(var(--hue, 200) 28% 15% / .5));
}
.work .chrome i {
  width: .46rem; height: .46rem; border-radius: 50%;
  background: hsl(var(--hue, 200) 30% 62%); flex: none;
}
.work .chrome .url { margin-left: .45rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.work .chrome .url.muted { opacity: .6; }
.work .meta { padding: 1rem 1.05rem 1.15rem; display: grid; gap: .18rem; }
.work .meta strong { font-size: 1.02rem; }
.work .trade { font-size: var(--step--1); color: var(--ink-soft); }

.svc {
  display: grid; gap: .35rem; text-decoration: none; color: inherit;
  background: var(--raise); border: 1px solid var(--rule); border-left: 3px solid var(--accent);
  border-radius: 3px; padding: 1.15rem 1.2rem;
  transition: transform .14s ease, box-shadow .14s ease;
}
.svc:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.svc strong { font-family: "Bricolage Grotesque", sans-serif; font-size: 1.05rem; }
.svc span { font-size: var(--step--1); color: var(--ink-soft); }

.more { font-weight: 600; }

/* ── inner pages ────────────────────────────────────────────────────────────────────────── */
.crumbs { border-bottom: 1px solid var(--rule); font-size: var(--step--1); color: var(--ink-soft); }
.crumbs .wrap { padding-block: .7rem; }
.crumbs a { color: var(--ink-soft); }
.crumbs span:last-child { color: var(--ink); }

.page { padding: 2.8rem 0 3.4rem; }
.page h1 { margin-bottom: .9rem; }
.page h2 { font-size: var(--step-2); margin: 2.4rem 0 .8rem; }
.page p, .page li { max-width: var(--measure); }

ul.ticks { list-style: none; padding: 0; margin: 0 0 1.1rem; display: grid; gap: .6rem; }
ul.ticks li { position: relative; padding-left: 1.6rem; }
ul.ticks li::before {
  content: ""; position: absolute; left: .1rem; top: .58em;
  width: .62rem; height: .62rem; border-radius: 2px;
  background: var(--accent);
  clip-path: polygon(14% 46%, 0 60%, 40% 100%, 100% 22%, 86% 8%, 38% 70%);
}
ul.plain { list-style: none; padding: 0; margin: 0 0 1rem; display: grid; gap: .45rem; }

.case .visit a { font-weight: 600; }
.case h1 { color: var(--ink); }

.areas-list {
  list-style: none; padding: 0; margin: 1.4rem 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .5rem;
}
.areas-list li {
  border: 1px solid var(--rule); border-radius: 3px; padding: .5rem .8rem;
  background: var(--raise); font-size: var(--step--1);
}

ul.contact { list-style: none; padding: 0; margin: 1.6rem 0; display: grid; gap: .8rem; }
ul.contact li {
  display: flex; flex-wrap: wrap; gap: .3rem 1rem; align-items: baseline;
  border-bottom: 1px solid var(--rule); padding-bottom: .8rem;
}
ul.contact span {
  flex: 0 0 6.5rem; font-size: .72rem; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-soft);
}

/* ── call to action ─────────────────────────────────────────────────────────────────────── */
.cta { background: var(--accent); color: var(--accent-on); padding: 3.4rem 0; }
.cta h2 { color: var(--accent-on); margin-bottom: .7rem; }
.cta p { color: color-mix(in srgb, var(--accent-on) 88%, transparent); max-width: var(--measure); }
.cta .small { color: color-mix(in srgb, var(--accent-on) 72%, transparent); }
.cta .btn { background: var(--accent-on); color: var(--accent); border-color: var(--accent-on); }
.cta .btn.ghost {
  background: transparent; color: var(--accent-on);
  border-color: color-mix(in srgb, var(--accent-on) 45%, transparent);
}
.cta .btn.ghost:hover { border-color: var(--accent-on); color: var(--accent-on); }

/* ── footer ─────────────────────────────────────────────────────────────────────────────── */
footer.site {
  border-top: 1px solid var(--rule); background: var(--raise);
  padding: 2.8rem 0 2rem; font-size: var(--step--1);
}
footer.site .cols {
  display: grid; gap: 2rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
footer.site h3 {
  font-size: .72rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: .6rem;
}
footer.site ul { list-style: none; padding: 0; margin: 0; display: grid; gap: .35rem; }
footer.site a { color: var(--ink-soft); text-decoration: none; }
footer.site a:hover { color: var(--accent); text-decoration: underline; }
footer.site .logo { font-size: 1.05rem; margin-bottom: .5rem; }
.areas, .legal { color: var(--ink-soft); margin-top: 1.8rem; max-width: none; }
.legal { border-top: 1px solid var(--rule); padding-top: 1rem; margin-top: 1.2rem; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}


/* ══════════════════════════════════════════════════════════════════════════════════════════════
   CONVERSION PASS
   The site had no form, no price, no WhatsApp and no viewport breakpoints.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* The build toggles panels with the `hidden` attribute. Several of them are grids and flex
   containers, whose display would otherwise beat the UA rule and show every state at once. */
[hidden] { display: none !important; }

/* Every button on the site now jumps to #contact, and the header is sticky, so without these the
   heading you jumped to lands underneath it and the visitor sees the middle of the form. */
html { scroll-behavior: smooth; scroll-padding-top: 5.5rem; }
#contact { scroll-margin-top: 5.5rem; }

@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ── the phone number stops being the least important thing on the page ───────────────────── */
.call {
  display: inline-flex; align-items: center; gap: .42rem;
  min-height: 44px; padding: .4rem 1.05rem;
  font-weight: 600; font-size: var(--step--1); white-space: nowrap;
  text-decoration: none; border-radius: 999px;
  background: var(--accent); color: var(--accent-on); border: 1px solid var(--accent);
}
.call svg { width: 1.05em; height: 1.05em; flex: none; }
.call:hover { filter: brightness(1.08); border-color: var(--accent); color: var(--accent-on); }

/* ── the CTA becomes the form ─────────────────────────────────────────────────────────────── */
.cta-grid {
  display: grid; gap: 2.4rem;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.cta-say h2 { margin-bottom: .7rem; }
.price-line {
  font-size: var(--step-1);
  color: var(--accent-on);
  border-top: 1px solid color-mix(in srgb, var(--accent-on) 25%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent-on) 25%, transparent);
  padding: .8rem 0; margin: 1.3rem 0;
}
.cta .note { color: color-mix(in srgb, var(--accent-on) 80%, transparent); }

.direct { list-style: none; padding: 0; margin: 1.2rem 0 0; display: grid; gap: .45rem; }
.direct a {
  color: var(--accent-on); font-weight: 600;
  text-decoration-color: color-mix(in srgb, var(--accent-on) 45%, transparent);
}
.direct a:focus-visible { outline-color: var(--accent-on); }

/* ── the form itself ──────────────────────────────────────────────────────────────────────── */
.cta-form {
  background: color-mix(in srgb, var(--accent-on) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-on) 22%, transparent);
  border-radius: 6px; padding: 1.4rem 1.4rem 1.5rem;
}
.enquiry .f { display: grid; gap: .3rem; margin: 0 0 .9rem; max-width: none; }
.enquiry label { font-size: var(--step--1); font-weight: 600; color: var(--accent-on); }
.enquiry .opt {
  font-weight: 400; text-transform: uppercase; letter-spacing: .1em; font-size: .64rem;
  color: color-mix(in srgb, var(--accent-on) 65%, transparent);
}
.enquiry input, .enquiry textarea {
  font: inherit; width: 100%;
  min-height: 44px; padding: .6rem .75rem;
  background: var(--paper); color: var(--ink);
  border: 1px solid transparent; border-radius: 4px;
}
.enquiry textarea { min-height: 6.5rem; resize: vertical; line-height: 1.5; }
.enquiry input:focus-visible, .enquiry textarea:focus-visible {
  outline: 3px solid var(--accent-on); outline-offset: 1px;
}
.enquiry input:user-invalid, .enquiry textarea:user-invalid { border-color: var(--hot); }

button.btn { font: inherit; cursor: pointer; }
button.btn[disabled] { opacity: .65; cursor: default; }
.enquiry .submit { width: 100%; min-height: 48px; margin-top: .3rem; }
.formnote { margin: .8rem 0 0; color: color-mix(in srgb, var(--accent-on) 72%, transparent); }

/* Off-screen, NOT display:none. A bot that reads the computed style skips a display:none field
   and happily fills an off-screen one, which is the entire point of the trap. */
.hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

.enquiry-done h3, .enquiry-fallback h3 { color: var(--accent-on); margin-bottom: .5rem; }
.enquiry-done p, .enquiry-fallback p {
  color: color-mix(in srgb, var(--accent-on) 88%, transparent);
}
.enquiry-done a, .enquiry-fallback a { color: var(--accent-on); font-weight: 600; }
.fallback-text {
  font: inherit; width: 100%; margin: .6rem 0 .2rem; padding: .7rem .8rem;
  background: var(--paper); color: var(--ink);
  border: 1px solid transparent; border-radius: 4px; line-height: 1.5;
}

/* ── work cards: real screenshots of real live sites ──────────────────────────────────────── */
.work .shot {
  /* height: auto is load bearing. The width and height ATTRIBUTES on the img (which are there so
     the browser reserves the space and the card does not jump while it loads) map to CSS
     presentational hints. `width: 100%` overrides the width hint, but with no author height the
     hint `height: 800px` survives, and an explicit height makes aspect-ratio inert. The card then
     renders a 337x800 box and object-fit: cover crops a 1:1 strip out of the middle of the
     screenshot, which reads as a broken image rather than a CSS mistake. */
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 10;
  object-fit: cover; object-position: top center;
  background: var(--wash); border-bottom: 1px solid var(--rule);
}
.case-shot {
  display: block; width: 100%; height: auto; margin: 1.6rem 0;
  border: 1px solid var(--rule); border-radius: 5px; box-shadow: var(--shadow-lg);
}

/* A card with no screenshot. Same 16:10 box as a real one, so a row of cards is one shape
   whether or not there is a photograph to put in it. The card's own --hue tints it, so the six
   trades stay visually distinct rather than all becoming the same grey rectangle. */
.work .noshot {
  display: flex; align-items: center; justify-content: center;
  width: 100%; aspect-ratio: 16 / 10;
  border-bottom: 1px solid var(--rule);
  background:
    radial-gradient(120% 120% at 50% 0%,
      hsl(var(--hue, 200) 40% 88%) 0%, hsl(var(--hue, 200) 26% 94%) 60%, var(--wash) 100%);
}
.work .noshot span {
  font-family: "Bricolage Grotesque", "Public Sans", sans-serif;
  font-weight: 700; font-size: 1.02rem; letter-spacing: -.01em; text-align: center;
  padding: 0 1.2rem;
  color: hsl(var(--hue, 200) 30% 32%);
}
:root[data-theme="dark"] .work .noshot,
:root:not([data-theme="light"]) .work .noshot {
  background:
    radial-gradient(120% 120% at 50% 0%,
      hsl(var(--hue, 200) 26% 22%) 0%, hsl(var(--hue, 200) 20% 16%) 60%, var(--wash) 100%);
}
:root[data-theme="dark"] .work .noshot span,
:root:not([data-theme="light"]) .work .noshot span { color: hsl(var(--hue, 200) 34% 76%); }

/* Equal height rows regardless of how long a business name is. */
.work .meta { margin-top: auto; }

/* ── testimonials. Renders nothing while TESTIMONIALS is empty. ───────────────────────────── */
.quotes { display: grid; gap: 1rem; margin-top: 1.5rem;
          grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
.quote {
  background: var(--raise); border: 1px solid var(--rule); border-left: 3px solid var(--accent);
  border-radius: 3px; padding: 1.2rem 1.3rem; margin: 0;
}
.quote blockquote { margin: 0 0 .8rem; font-size: var(--step-1); }
.quote figcaption { font-size: var(--step--1); color: var(--ink-soft); }
.quote figcaption strong { color: var(--ink); }

/* ── the sticky call bar: hidden on desktop, the main event on a phone ────────────────────── */
.callbar { display: none; }

/* ══ MOBILE ═══════════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 760px) {
  /* The nav was seven items in a wrapping flex row: three or four lines of sticky header, 20 to
     28% of an iPhone SE viewport, permanently. One scrollable row instead. */
  header.site .bar { gap: .5rem .8rem; padding: .6rem 1rem; }
  .logo { flex: 1 1 auto; }
  .call { order: 2; }
  header.site nav {
    order: 3; flex: 1 0 100%;
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; margin: 0 -1rem; padding: 0 1rem;
  }
  header.site nav::-webkit-scrollbar { display: none; }
  header.site nav ul { flex-wrap: nowrap; gap: 1.15rem; }
  header.site nav a { display: block; padding: .2rem 0; white-space: nowrap; }

  .hero { padding: 2.6rem 0 2rem; }
  .cta-grid { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; }
  .cta { padding: 2.6rem 0; }
  .band { padding: 2.6rem 0; }
  .actions .btn { flex: 1 1 auto; text-align: center; min-height: 48px;
                  display: inline-flex; align-items: center; justify-content: center; }

  /* Two targets, both comfortably over the 44px minimum, on every page. For a trades audience
     reading on a phone this is likely worth more than everything else on the site. */
  .callbar {
    display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    background: var(--rule);
    border-top: 1px solid var(--rule);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -6px 24px -18px rgba(0, 0, 0, .8);
  }
  .cb {
    display: flex; align-items: center; justify-content: center;
    min-height: 54px; padding: .5rem;
    font-weight: 700; font-size: var(--step-0); text-decoration: none;
    background: var(--accent); color: var(--accent-on);
  }
  .cb.wa { background: #1FA855; color: #FFFFFF; }

  /* Without this the bar sits on top of the footer and the legal line is unreadable. A fixed
     full-bleed element with no safe-area inset is a bug this project has shipped before. */
  body { padding-bottom: calc(54px + env(safe-area-inset-bottom)); }
}

@media (max-width: 420px) {
  .strip dl { grid-template-columns: 1fr 1fr; }
  .cta-form { padding: 1.1rem 1rem 1.2rem; }
}



/* ── the colour tokens the beams block depends on ─────────────────────────────────────────────
   Declared here, not in the beams block, because that block uses them thirty times and defines
   none: it was written to be appended to a sheet that already had them.

   All three theme states, in this order, for the reason set out at the top of this file: a value
   defined only inside the dark media query does not exist in the default un-stamped state, and one
   defined only on bare :root is beaten by the (0,2,0) theme selectors.

   HSL components without the function wrapper, so they can be used as `hsl(var(--glow-1) / .2)`
   with any alpha at the call site. */
:root {
  --glow-1: 174 68% 42%;   /* brand teal */
  --glow-2: 20 82% 54%;    /* burnt orange, the warm counterpoint */
  --glow-3: 205 74% 50%;   /* cool blue, so the field is not two tone */
  --glow-strength: .50;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* Higher on a dark ground: the same alpha over near black reads as almost nothing. */
    --glow-1: 172 66% 48%;
    --glow-2: 20 80% 56%;
    --glow-3: 205 76% 56%;
    --glow-strength: .62;
  }
}

:root[data-theme="dark"] {
  --glow-1: 172 66% 48%;
  --glow-2: 20 80% 56%;
  --glow-3: 205 76% 56%;
  --glow-strength: .62;
}

/* ── the hero's base layout, which the beams block depends on ─────────────────────────────────
   Beams was written to be APPENDED after an earlier block that declared these three properties,
   and its own comments rely on them: "position:relative and isolation:isolate, so a negative
   z-index here lands behind the words", and "everything here is clipped by .hero's
   overflow:hidden". That earlier block was replaced wholesale, so they are restated here.

   overflow:hidden is not cosmetic. .hero::before is inset -18% -22%, deliberately wider than the
   section so no drift exposes an edge, and unclipped that spilled 70px past the viewport at 390px
   and gave the homepage a sideways scroll. It did not show up in an element scan because a
   pseudo-element is not in the DOM, so the probe blamed some innocent nav links instead. */
.hero { position: relative; isolation: isolate; overflow: hidden; }
.hero > .wrap { position: relative; z-index: 1; }

/* ══ BEAMS + SWEEP ═════════════════════════════════════════════════════════════════════════════
 *
 * Replaces the drifting aurora with something that reads as deliberate lighting rather than
 * weather. Light rakes in through a window at 13 degrees, a bright accent sweep crosses the
 * headline on a long loop, and there is a grid and a scanline behind it for the light to catch.
 *
 * WHAT IS OVERRIDDEN, AND WHY EVERY PROPERTY IS RESTATED
 * .hero::before and .hero::after are both fully re-declared below, every property from the AURORA
 * block written out again, because a partial override leaves the old value in place. The same is
 * true of the mobile block: the old `@media (max-width:760px)` rule set `animation-duration: 34s`
 * on .hero::before, which with TWO animations on that element would have applied 34s to the
 * entrance fade as well, so the mobile block down the bottom restates the whole shorthand.
 * .hero .wrap::before (the grain) is deliberately NOT touched.
 *
 * PAINT BUDGET, since the site's whole pitch is that it builds fast pages: two composited layers
 * in the hero, both animating transform and opacity only. No animated filter, no animated
 * box-shadow, no backdrop-filter, nothing that can trigger layout or paint mid-animation.
 */

/* ── new tokens, declared in all three theme blocks ───────────────────────────────────────────
 * A token defined only inside the dark media query is undefined in the default un-stamped light
 * state, so it falls back to nothing and renders one theme's colour on the other theme's ground.
 * Order matters too: :root[data-theme="dark"] and :root:not([data-theme="light"]) are the same
 * specificity, so the explicit stamp has to come LAST or the toggle stops working one way. */

:root {
  --beam-strength: .22;   /* alpha of the main rake                            */
  --beam-soft:     .13;   /* the second, wider, crossing set                   */
  --beam-shade:    .055;  /* the DARK between the beams, which is most of the
                             drama: light you can see is light with shadow
                             beside it                                          */
  --sweep-strength: .46;  /* peak alpha of the travelling sweep                */
  --sweep-core:    .28;   /* the white hot line inside it, as a fraction of it */
  --mesh-grid:     9%;    /* hairline grid, mixed out of --ink so it themes    */
  --mesh-scan:     3.5%;  /* the scanline                                      */
  --flood-wash:    .22;   /* the wash that floods a work card on hover         */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* Every one of these is higher. The same alpha over near black reads as nothing at all. */
    --beam-strength: .32;
    --beam-soft:     .18;
    --beam-shade:    .16;
    /* THESE TWO ARE A CEILING, NOT A TASTE. Rendered with the words hidden and sampled pixel by
       pixel, .48 with a .28 core put the brightest ground under the h1 at 3.01:1 against --ink on
       the dark theme: inside the 3:1 large-text floor by nothing at all, and the h1 is the one
       thing on this page that has to be read. The light theme keeps the full .28 core because it
       measures 13:1 there and has room to spare. If the sweep ever wants turning up again, these
       are the two numbers, and the check is to re-render and re-measure, not to eyeball it. */
    --sweep-strength: .38;
    --sweep-core:    .14;
    --mesh-grid:     10%;
    --mesh-scan:     5%;
    --flood-wash:    .30;
  }
}

:root[data-theme="dark"] {
  --beam-strength: .32;
  --beam-soft:     .18;
  --beam-shade:    .16;
  --sweep-strength: .38;
  --sweep-core:    .14;
  --mesh-grid:     10%;
  --mesh-scan:     5%;
  --flood-wash:    .30;
}

/* ── layer 1: the beams ──────────────────────────────────────────────────────────────────────
 * Three repeating-linear-gradients: the main rake at 103deg, a wider warm set at 97deg so the two
 * cross rather than reading as wallpaper, and a third at 103deg again offset into the gaps that
 * puts SHADOW between the beams. That third one is doing most of the work. Light you can see is
 * light with dark beside it, and a hero made only of brighter patches reads as a smudge.
 * Soft stops, not hard bands: light does not have edges you can point at. Three radial gradients
 * underneath keep the colour the aurora had.
 *
 * The mask runs at 13deg, along the beams, so they fade at BOTH ends the way a shaft of light
 * does where it enters and where it lands. rgba() rather than the modern slash syntax because
 * -webkit-mask-image is here for Safari before 15.4, and that is the one place it matters. */
.hero::before {
  content: "";
  position: absolute;
  inset: -18% -22%;
  z-index: -3;
  background:
    repeating-linear-gradient(103deg,
      transparent 0px,
      transparent 26px,
      hsl(var(--glow-1) / calc(var(--beam-strength) * .34)) 44px,
      hsl(var(--glow-1) / var(--beam-strength)) 63px,
      hsl(var(--glow-1) / calc(var(--beam-strength) * .28)) 84px,
      transparent 106px,
      transparent 130px,
      hsl(var(--glow-3) / calc(var(--beam-strength) * .5)) 144px,
      transparent 162px,
      transparent 228px),
    repeating-linear-gradient(97deg,
      transparent 0px,
      transparent 96px,
      hsl(var(--glow-2) / var(--beam-soft)) 138px,
      transparent 182px,
      transparent 348px),
    repeating-linear-gradient(103deg,
      transparent 0px,
      transparent 164px,
      hsl(196 26% 7% / var(--beam-shade)) 197px,
      transparent 228px),
    radial-gradient(46% 52% at 18% 10%,
      hsl(var(--glow-1) / calc(var(--glow-strength) * .9)) 0%, transparent 70%),
    radial-gradient(40% 46% at 84% 18%,
      hsl(var(--glow-3) / calc(var(--glow-strength) * .7)) 0%, transparent 72%),
    radial-gradient(44% 50% at 62% 88%,
      hsl(var(--glow-2) / calc(var(--glow-strength) * .5)) 0%, transparent 72%);
  -webkit-mask-image: linear-gradient(13deg,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .35) 11%, rgba(0, 0, 0, 1) 30%,
    rgba(0, 0, 0, 1) 70%, rgba(0, 0, 0, .3) 89%, rgba(0, 0, 0, 0) 100%);
          mask-image: linear-gradient(13deg,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .35) 11%, rgba(0, 0, 0, 1) 30%,
    rgba(0, 0, 0, 1) 70%, rgba(0, 0, 0, .3) 89%, rgba(0, 0, 0, 0) 100%);
  animation: beam-in 1.1s ease-out both,
             rake 24s ease-in-out infinite alternate;
  will-change: transform;
}

/* The rake is deliberately slow and small. The drama is the sweep; this is the thing the sweep
   moves through, and if it moves fast enough to notice it stops looking like light. */
@keyframes rake {
  from { transform: translate3d(-2.4%, -1.4%, 0) scale(1.03); }
  to   { transform: translate3d(2.4%, 1.4%, 0) scale(1.09); }
}

@keyframes beam-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── layer 2: texture, pointer highlight, scrim ──────────────────────────────────────────────
 * Fully restated. The list order is the paint order, first on top: highlight, then the scanline,
 * then the grid, and the scrim LAST so it paints behind the texture and lifts the whole field off
 * the page colour without flattening it. The scrim is not decoration, it is the reason the small
 * print at the bottom of the hero is still readable with all this behind it.
 *
 * The grid and the scanline are mixed out of --ink, so they are dark hairlines on paper and light
 * hairlines on the dark ground with no second set of tokens. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background-image:
    radial-gradient(26rem 26rem at var(--mx, 50%) var(--my, 32%),
      hsl(var(--glow-1) / .22) 0%, hsl(var(--glow-1) / .07) 44%, transparent 72%),
    repeating-linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) var(--mesh-scan), transparent) 0px,
      color-mix(in srgb, var(--ink) var(--mesh-scan), transparent) 1px,
      transparent 1px, transparent 6px),
    linear-gradient(to right,
      color-mix(in srgb, var(--ink) var(--mesh-grid), transparent) 1px, transparent 1px),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) var(--mesh-grid), transparent) 1px, transparent 1px),
    linear-gradient(to bottom,
      transparent 0%,
      color-mix(in srgb, var(--scrim) 46%, transparent) 58%,
      color-mix(in srgb, var(--scrim) 92%, transparent) 100%);
  background-size: auto, auto, 46px 46px, 46px 46px, auto;
  background-repeat: no-repeat, repeat, repeat, repeat, no-repeat;
}

/* ── layer 3: the sweep ──────────────────────────────────────────────────────────────────────
 * .hero .wrap::after is the one free slot in the hero, and .hero .wrap already carries
 * position:relative and isolation:isolate, so a negative z-index here lands behind the words and
 * cannot escape upward. z-index:-2 puts it under the grain (.hero .wrap::before, z-index:-1) so
 * the grain sits over the light rather than beside it.
 *
 * THE MASK IS A CONTRAST DECISION, NOT A LOOK. The sweep is masked to the top of the wrap, which
 * is the eyebrow and the h1: 700 weight at 2.4rem and up, where WCAG asks 3:1, and the worst
 * frame of the sweep crossing it measures 3.78:1 on the dark theme. It is gone by 60% of the
 * wrap's height, which on desktop and on a 390px phone alike is above the first line of the
 * lede, so no body copy is ever behind the bright core.
 *
 * It travels 410% of its own width, which from left:-46% carries it clear of the right edge, and
 * it fades at both ends of the trip so it never pops in or out at the hero boundary. */
.hero .wrap::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -46%;
  width: 38%;
  z-index: -2;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg,
    transparent 0%,
    hsl(var(--glow-1) / calc(var(--sweep-strength) * .16)) 30%,
    hsl(var(--glow-1) / calc(var(--sweep-strength) * .68)) 62%,
    hsl(var(--glow-1) / var(--sweep-strength)) 76%,
    hsl(0 0% 100% / calc(var(--sweep-strength) * var(--sweep-core))) 80%,
    hsl(var(--glow-2) / calc(var(--sweep-strength) * .45)) 87%,
    transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 6%, rgba(0, 0, 0, 1) 34%,
    rgba(0, 0, 0, .18) 46%, rgba(0, 0, 0, 0) 60%);
          mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 6%, rgba(0, 0, 0, 1) 34%,
    rgba(0, 0, 0, .18) 46%, rgba(0, 0, 0, 0) 60%);
  transform: translate3d(0, 0, 0) skewX(-11deg);
  animation: sweep 13s cubic-bezier(.42, .04, .22, 1) 1.6s infinite;
  will-change: transform, opacity;
}

/* 2.7 seconds of travel, then ten seconds of nothing. A sweep that runs constantly is a banner
   ad; one that arrives every so often is a room with a window in it. */
@keyframes sweep {
  0%   { transform: translate3d(0, 0, 0) skewX(-11deg);    opacity: 0; }
  3%   { opacity: .92; }
  15%  { opacity: .92; }
  21%  { transform: translate3d(410%, 0, 0) skewX(-11deg); opacity: 0; }
  100% { transform: translate3d(410%, 0, 0) skewX(-11deg); opacity: 0; }
}

/* MEASURED, not guessed. Rendering the hero field with the words hidden and sampling the
   brightest pixel in each line's own columns put the small print at 3.85:1 against --ink-soft on
   the dark theme, under the 4.5:1 floor, because the beams lift the ground underneath it. That
   line carries the price, so it gets ink rather than the field getting flattened. The lede is
   over the floor either way and is lifted for the same reason. */
.hero .lede  { color: color-mix(in srgb, var(--ink) 90%, var(--ink-soft)); }
.hero .small { color: color-mix(in srgb, var(--ink) 62%, var(--ink-soft)); }

/* ── the entrance ────────────────────────────────────────────────────────────────────────────
 * Pure animation-delay, no JavaScript, no observer. `both` fill is what holds each line at
 * opacity 0 through its delay, and it is also the trap: the base file zeroes animation-DURATION
 * under reduced motion but not animation-DELAY, so backwards fill would leave the headline blank
 * for a third of a second on exactly the machines that asked for less. The reduced-motion block
 * at the bottom kills the animation outright, which kills the delay with it, and the unanimated
 * state of every one of these elements is fully visible and untransformed. */
@keyframes rise {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

.hero .eyebrow,
.hero h1,
.hero .lede,
.hero .actions,
.hero .small,
.strip dl > div {
  animation: rise .62s cubic-bezier(.16, .84, .34, 1) both;
}

.hero .eyebrow { animation-delay: .04s; }
.hero h1       { animation-delay: .10s; }
.hero .lede    { animation-delay: .22s; }
.hero .actions { animation-delay: .32s; }
.hero .small   { animation-delay: .40s; }

/* The numbers rise out from behind the strip's own top rule rather than floating over it. */
.strip { overflow: hidden; }

.strip dl > div:nth-child(1) { animation-delay: .46s; }
.strip dl > div:nth-child(2) { animation-delay: .52s; }
.strip dl > div:nth-child(3) { animation-delay: .58s; }
.strip dl > div:nth-child(4) { animation-delay: .64s; }

/* ── work cards: the accent floods in from the left edge ─────────────────────────────────────
 * Two wipes, both scaleX from the left edge, both transform only.
 *   .work::before   a tinted wash across the chrome and the screenshot
 *   .meta::before   a solid accent flood under the caption, 60ms behind it
 * The caption text flips to --accent-on, but on a DELAY: white text arriving before the accent
 * has covered the ground under it is white on white for a fifth of a second. Going back the other
 * way there is no delay, so the colour returns before the flood does. */
.work { position: relative; overflow: hidden; }

.work::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(100deg,
    hsl(var(--glow-1) / var(--flood-wash)) 0%,
    hsl(var(--glow-1) / calc(var(--flood-wash) * .55)) 52%,
    hsl(var(--glow-3) / calc(var(--flood-wash) * .35)) 100%);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .34s cubic-bezier(.2, .7, .3, 1);
}
.work:hover::before,
.work:focus-visible::before { transform: scaleX(1); }

/* z-index 2 makes .meta a stacking context, which is what keeps its own -1 pseudo behind its text
   and in front of nothing else. The 2px rainbow rule on .work::after is also z-index 2 and later
   in tree order, so it still sits on top of everything. */
.work .meta { position: relative; z-index: 2; }

.work .meta::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg,
    var(--accent) 0%,
    color-mix(in srgb, var(--accent) 86%, var(--accent-on)) 100%);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .26s cubic-bezier(.2, .7, .3, 1);
}
.work:hover .meta::before,
.work:focus-visible .meta::before { transform: scaleX(1); transition-delay: .06s; }

.work .meta strong,
.work .trade { transition: color .14s ease; }

.work:hover .meta strong,
.work:focus-visible .meta strong,
.work:hover .trade,
.work:focus-visible .trade { color: var(--accent-on); transition-delay: .16s; }

.work:hover, .work:focus-visible { transform: translateY(-6px); }

/* The service cards get the same idea at a quarter of the volume, so the page reads as one thing.
   inset:0 is the padding box, so the 3px accent border down the left stays crisp. */
.svc { position: relative; z-index: 0; overflow: hidden; }

.svc::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg,
    hsl(var(--glow-1) / calc(var(--flood-wash) * .9)) 0%,
    hsl(var(--glow-1) / calc(var(--flood-wash) * .25)) 100%);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .3s cubic-bezier(.2, .7, .3, 1);
}
.svc:hover::before,
.svc:focus-visible::before { transform: scaleX(1); }

/* ── the closing block gets the same light, held still ───────────────────────────────────────
 * Same beams, white, no animation at all, so the bottom of the page rhymes with the top for
 * roughly nothing. White at .05 over the accent moves the contrast of --accent-on by a fraction
 * of a point and it stays well the right side of AA in both themes. */
.cta { position: relative; isolation: isolate; overflow: hidden; }

.cta::before {
  content: "";
  position: absolute;
  inset: -10% -14%;
  z-index: -1;
  pointer-events: none;
  background:
    repeating-linear-gradient(103deg,
      transparent 0px, transparent 40px,
      hsl(0 0% 100% / .05) 74px,
      transparent 108px, transparent 212px),
    repeating-linear-gradient(97deg,
      transparent 0px, transparent 130px,
      hsl(0 0% 100% / .028) 176px,
      transparent 224px, transparent 420px);
  -webkit-mask-image: linear-gradient(13deg,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 26%,
    rgba(0, 0, 0, 1) 74%, rgba(0, 0, 0, 0) 100%);
          mask-image: linear-gradient(13deg,
    rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 26%,
    rgba(0, 0, 0, 1) 74%, rgba(0, 0, 0, 0) 100%);
}

.cta > .wrap { position: relative; z-index: 1; }

/* ── mobile ──────────────────────────────────────────────────────────────────────────────────
 * The crossing set of beams goes, the grid gets coarser, the scanline goes, and the animation
 * shorthand is restated in full. The old mobile rule set a bare `animation-duration: 34s`; with two
 * animations on this element a single duration is repeated across the list, so the entrance fade
 * would have taken 34 seconds. Everything here is clipped by .hero's overflow:hidden, and nothing
 * uses 100vw, so there is no horizontal scroll at 390px and the fixed call bar is untouched. */
@media (max-width: 760px) {
  .hero::before {
    inset: -16% -24%;
    /* The 97deg crossing set goes, because at 390px it only ever shows one and a half of its
       bands and reads as a smudge. The beams and the shadow between them stay at full strength:
       most of the people this site is selling to are holding a phone, and a hero that is only
       dramatic on a desktop is dramatic for nobody. Gradients rasterise once; what costs on a
       phone is the layer and the mask, and those are the same either way. */
    background:
      repeating-linear-gradient(103deg,
        transparent 0px,
        transparent 22px,
        hsl(var(--glow-1) / calc(var(--beam-strength) * .5)) 38px,
        hsl(var(--glow-1) / var(--beam-strength)) 56px,
        transparent 88px,
        transparent 112px,
        hsl(var(--glow-3) / calc(var(--beam-strength) * .45)) 126px,
        transparent 146px,
        transparent 196px),
      repeating-linear-gradient(103deg,
        transparent 0px,
        transparent 142px,
        hsl(196 26% 7% / var(--beam-shade)) 170px,
        transparent 196px),
      radial-gradient(52% 46% at 22% 14%,
        hsl(var(--glow-1) / var(--glow-strength)) 0%, transparent 72%),
      radial-gradient(48% 44% at 82% 84%,
        hsl(var(--glow-2) / calc(var(--glow-strength) * .55)) 0%, transparent 72%);
    animation: beam-in 1s ease-out both,
               rake 34s ease-in-out infinite alternate;
  }

  .hero::after {
    background-image:
      radial-gradient(15rem 15rem at 50% 26%,
        hsl(var(--glow-1) / .18) 0%, transparent 70%),
      linear-gradient(to right,
        color-mix(in srgb, var(--ink) var(--mesh-grid), transparent) 1px, transparent 1px),
      linear-gradient(to bottom,
        color-mix(in srgb, var(--ink) var(--mesh-grid), transparent) 1px, transparent 1px),
      linear-gradient(to bottom, transparent 0%,
        color-mix(in srgb, var(--scrim) 52%, transparent) 64%,
        color-mix(in srgb, var(--scrim) 92%, transparent) 100%);
    background-size: auto, 34px 34px, 34px 34px, auto;
    background-repeat: no-repeat, repeat, repeat, no-repeat;
  }

  .hero .wrap::after { animation-duration: 16s; }
}

/* ── reduced motion ──────────────────────────────────────────────────────────────────────────
 * `animation: none` rather than a zeroed duration, because a zeroed duration still runs the
 * animation, still honours the DELAY, and still lands on the end state. Killing it outright is
 * the only way to be sure nothing is briefly invisible. Every element below has a correct
 * unanimated state: the beams sit still and lit, the headline and the numbers are simply there,
 * and the sweep, which is the only thing here with no still form worth showing, is gone. The
 * hovers land on their finished state instantly instead of wiping. */
@media (prefers-reduced-motion: reduce) {
  .hero::before {
    animation: none;
    transform: translate3d(0, 0, 0) scale(1.04);
    opacity: 1;
  }

  .hero .wrap::after { animation: none; opacity: 0; }

  .hero .eyebrow,
  .hero h1,
  .hero .lede,
  .hero .actions,
  .hero .small,
  .strip dl > div {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .work::before,
  .work .meta::before,
  .svc::before,
  .work .meta strong,
  .work .trade { transition: none; }

  .work:hover::before,
  .work:focus-visible::before,
  .work:hover .meta::before,
  .work:focus-visible .meta::before,
  .svc:hover::before,
  .svc:focus-visible::before { transform: scaleX(1); }

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

/* ══ THE MONTHLY PLAN ═════════════════════════════════════════════════════════════════════════ */

.plan {
  position: relative;
  border-block: 1px solid var(--rule);
  background:
    radial-gradient(90% 120% at 15% 0%,
      color-mix(in srgb, var(--accent) 9%, transparent) 0%, transparent 62%),
    var(--wash);
}

.plan-grid {
  display: grid;
  gap: 2.2rem;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  align-items: start;
  margin-top: 1.9rem;
}

.plan-grid .ticks { gap: .85rem; }
.plan-grid .ticks li { max-width: none; }

.plan-price {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-top: 3px solid var(--accent);
  border-radius: 6px;
  padding: 1.7rem 1.6rem 1.6rem;
  box-shadow: var(--shadow-lg);
}

.plan .amount {
  font-family: "Bricolage Grotesque", "Public Sans", sans-serif;
  font-weight: 700;
  font-size: clamp(3.2rem, 7vw, 4.4rem);
  line-height: 1;
  letter-spacing: -.035em;
  margin: 0 0 .7rem;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  color: var(--ink);
}
.plan .cur { font-size: .48em; margin-right: .04em; }
.plan .per {
  font-family: "Public Sans", sans-serif;
  font-size: .23em;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink-soft);
  margin-left: .5em;
  /* Its own line under the number on a narrow card, rather than pushing the number smaller. */
  flex: 0 1 auto;
}

.plan-price .small { margin-bottom: 1.2rem; }

.plan-price .btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 50px;
  margin-bottom: .9rem;
}
.plan-price .more { display: block; text-align: center; font-size: var(--step--1); }

/* No list beside it, so it is one column and does not stretch to full width and look empty. */
.plan-grid.compact { grid-template-columns: minmax(0, 26rem); }

@media (max-width: 860px) {
  .plan-grid { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; }
  /* The price goes FIRST on a phone. The reader is scrolling to find out what it costs, and
     making them travel a screen of bullet points to get there loses the impatient ones, who are
     most of them. */
  .plan-price { order: -1; }
}


/* ── the mark ─────────────────────────────────────────────────────────────────────────────── */
.logo {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
}
.logo .mark {
  width: 1.55em;
  height: 1.55em;
  flex: none;
  /* currentColor on the arc, so the mark follows the theme rather than needing a second copy. */
  color: var(--accent);
}
footer.site .logo .mark { width: 1.4em; height: 1.4em; }


/* ── case study numbers ───────────────────────────────────────────────────────────────────── */
/* Rendered only for a client who has agreed to their numbers being shown (build.py). */
.results { margin: 2.2rem 0 2.6rem; }
.results .headline {
  margin: .2rem 0 1.2rem;
  font-size: var(--step-1);
  font-weight: 600;
  color: var(--ink);
}
.results .source { margin-top: 1rem; }

.table-wrap { overflow-x: auto; margin: 1.2rem 0; }
.results table { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
.results th, .results td {
  padding: .55rem .75rem .55rem 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--rule);
}
.results thead th { color: var(--ink-soft); font-weight: 600; }
.results tbody th { color: var(--ink); font-weight: 500; }
.results td { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The chart. Columns are 24px at most and grow from one baseline with 4px rounded tops. Values
   wear text colours, never the column's. Two values always show and the rest on hover; the table
   straight after the chart carries every value, so nothing depends on colour or on hovering. */
.chart { margin: 1.4rem 0 .6rem; }
.chart figcaption { margin-bottom: 1.8rem; font-size: var(--step--1); color: var(--ink-soft); }
.chart figcaption strong {
  display: block;
  font-size: var(--step-0);
  font-weight: 600;
  color: var(--ink);
}
.plot { position: relative; height: 12rem; margin-left: 2.2rem; }
.plot .gridlines span {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px solid var(--rule);
}
.plot .gridlines i {
  position: absolute;
  right: 100%;
  top: 0;
  transform: translateY(-50%);
  padding-right: .5rem;
  font-style: normal;
  font-size: .75rem;
  line-height: 1;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.plot .cols { position: absolute; inset: 0; display: flex; }
.plot .col { flex: 1; display: flex; justify-content: center; align-items: flex-end; }
.plot .bar {
  position: relative;
  width: 24px;
  max-width: 45%;
  background: var(--bar-before);
  border-radius: 4px 4px 0 0;
}
.plot .col.new .bar { background: var(--accent); }
.plot .val {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  padding-bottom: .3rem;
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
  white-space: nowrap;
  opacity: 0;
}
.plot .col.shown .val, .plot .col:hover .val { opacity: 1; }
.plot .event { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--ink-soft); }
.chart .xlabels, .chart .groups { display: flex; margin-left: 2.2rem; }
.chart .xlabels span {
  flex: 1;
  padding-top: .45rem;
  text-align: center;
  font-size: .75rem;
  color: var(--ink-soft);
}
.chart .groups span {
  margin: .45rem .4rem 0;
  padding-top: .35rem;
  text-align: center;
  font-size: .75rem;
  color: var(--ink-soft);
  border-top: 1px solid var(--rule);
}

/* More than one client under a trade page: a rule between them, not a card around each. */
.trade-case + .trade-case { margin-top: .9rem; padding-top: 1rem; border-top: 1px solid var(--rule); }
