/* ============================================================
   GEORGEHELOU.COM — Ink · Bone · Slate | Meridian accent
   Type: Spectral (serif) + Helvetica (body)
   Built from DC Brand Storyboard v2 — restraint is the brief.
   ============================================================ */

:root {
  --ink: #1B1F1E;
  --ink-soft: #2A2F2E;
  --bone: #F6F4EF;
  --bone-deep: #ECE9E1;
  --slate: #656A67;      /* AA on bone 5.01:1 and bone-deep 4.54:1 */
  --slate-quiet: #8A8F8C; /* decorative only — never carries text */
  --meridian: #2F5D5A;
  --clay: #8A6A52; /* reserved — barely used on the executive door */
  --hairline: #d9d3c8;
  --hairline-ink: rgba(247, 244, 238, 0.18);
  --serif: "Spectral", Georgia, serif;
  --sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* THE hidden ATTRIBUTE MUST WIN.
   It works through the UA stylesheet's [hidden]{display:none}, which ANY class
   selector outranks — so `.sally-mount { display: flex }` silently disabled it,
   and a panel JavaScript believed it had hidden stayed on screen with an empty
   thread beside the docked pill. George screenshotted exactly that on the
   evening of 14 August; it was not reproduced at the time because the probe
   asked the element whether it was `hidden` (it said yes) instead of asking the
   browser whether it was DRAWN (it was). Standard defensive rule, and the
   reason every serious reset carries it. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }

body {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--bone);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }

a { color: var(--meridian); text-decoration: none; }
a:hover { text-decoration: underline; }
.ink .form-note, .ink .form-note a { color: var(--bone); }

.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
/* ★ ONE STRAIGHT EDGE DOWN EVERY PAGE — George's decision, option A, on SC's
   defect list item 9: "YES A LINE THEM ALL UP".
   Both columns were centred — .wrap at 1080 and .wrap-narrow at 780 — so their
   LEFT EDGES differed by (1080-780)/2 = 150px. On Entrepreneurs and
   Organisations the two alternate section by section, so the headings visibly
   stepped in and out as you scrolled; on About, Testimonials and Contact the
   body stays one width, so those read straight. Two pages drifting and three
   not is the part that looked broken.
   The narrow column was there for a reason and it stays: long prose is hard to
   read across 1080px. So the BOX becomes the same box as .wrap, and the reading
   measure moves onto the children — comfortable line length, one gutter. */
.wrap-narrow { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
.wrap-narrow > * { max-width: 732px; }   /* the old narrow measure, now left-aligned */

/* ---------- Header ---------- */
header.site {
  background: var(--bone);
  border-bottom: 1px solid var(--hairline);
  position: sticky; top: 0; z-index: 50;
}
/* The bar now carries five things, not four — George's direct line joined it on
   12 Aug. Four fitted 1280 with nothing to spare; five do not, so the bar runs
   to 1440 and its gaps came in from 40 to 24. See the note at the collapse
   breakpoint below for what happens when even that is not enough. */
.site-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 18px 24px; max-width: 1440px; margin: 0 auto;
}
.wordmark {
  font-family: var(--serif); font-weight: 400; font-size: 1.35rem; white-space: nowrap;
  letter-spacing: 0.01em; color: var(--ink); text-decoration: none;
}
.wordmark:hover { text-decoration: none; }
/* The GH monogram sits in the same ruled box the old "GH" text stand-in had,
   in the same place: 36x24 outer, 1px ink rule, 10px to the name. Measured
   against the previous header, not estimated — the frame and the name both
   land where they always did. <picture> IS the box, so no page markup changes;
   it is also the flex item, so it carries the no-shrink rule — without it the
   bar squeezes the monogram out of proportion. */
.wordmark { display: inline-flex; align-items: center; gap: 10px; }
.wordmark picture {
  flex: 0 0 auto; width: 36px; height: 24px; border: 1px solid var(--ink);
  display: inline-flex; align-items: center; justify-content: center; line-height: 0;
}
.wordmark .gh-mark { height: 18px; width: auto; display: block; }
nav.main { display: flex; gap: 20px; align-items: center; }
nav.main a {
  color: var(--ink); font-size: 0.78rem; letter-spacing: 0.07em; text-transform: uppercase; white-space: nowrap;
  text-decoration: none; padding-bottom: 2px; border-bottom: 1px solid transparent;
}
nav.main .btn { display: none; } /* CTA lives in the bar on desktop, in the menu on mobile */
nav.main a:hover, nav.main a.active { border-bottom-color: var(--ink); }
.btn {
  display: inline-block; background: var(--ink); color: var(--bone);
  font-family: var(--sans); font-size: 0.8rem; letter-spacing: 0.12em;
  text-transform: uppercase; font-weight: 400; white-space: nowrap;
  padding: 14px 22px; border: 1px solid var(--ink);
  text-decoration: none; transition: background 0.2s ease;
  cursor: pointer;
}
.btn:hover { background: var(--ink-soft); text-decoration: none; }
.btn.on-ink { background: var(--bone); color: var(--ink); border-color: var(--bone); }
.btn.on-ink:hover { background: var(--bone-deep); }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--ink); color: var(--bone); }

/* mobile nav */
.nav-toggle {
  display: none; background: none; border: none; cursor: pointer;
  width: 44px; height: 44px; padding: 9px 10px; /* thumb-sized hit area */
}
.nav-toggle span { display:block; width:24px; height:2px; background:var(--ink); margin:5px 0; }
/* THE MENU COLLAPSES AT 1460, NOT 1140 — and that is a real change to how a
   laptop sees this site, so it is written down rather than left to be
   discovered. The bar cannot hold the wordmark, six nav links, the direct line
   and a 366px booking button at once: measured, the four of them need 1427px
   and a 1360 screen offers 1312. Something had to give, and a hamburger is a
   smaller loss than a page that scrolls sideways.
   ★ THE HISTORY, BECAUSE THIS NUMBER HAS MOVED TWICE IN ONE DAY AND BOTH MOVES
   HAD CAUSES WORTH KEEPING.
   1400 -> 1460: George renamed the "Proof" tab to "Testimonials", seven
   characters longer, and the bar was already at its limit. Re-measured, the row
   needed 1444px, so at exactly 1440 — the commonest MacBook width there is —
   the booking button sat 4px past the edge and the page scrolled sideways.
   1460 -> 1330: that fix cost George the menu on his own screen, and he said
   so — the bar "disappears at times". Measured rather than guessed: wordmark
   181 + nav 706 + phone 99 + CTA 361 = 1347px of content before any gap. THE
   BUTTON WAS A QUARTER OF THE BAR. It now reads "Book a Clarity Session" (243px,
   his choice from four measured options), which buys 118px and brings the full
   menu back to ~1326 — his 1440 MacBook, a 1366 laptop, and a half-width window
   on a large screen.
   ★ THE FULL PROMISE DID NOT GO ANYWHERE: "Book your 20-Minute Clarity Session"
   still appears in the hero, in the collapsed mobile menu, on every page's
   in-body call to action and on /contact. Only the header BAR — where 361px of
   a 1444px row was being spent on words the visitor meets again four lines
   later — was shortened. */
@media (max-width: 1330px) {
  nav.main {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    background: var(--bone); border-bottom: 1px solid var(--hairline);
    flex-direction: column; padding: 12px 24px; gap: 4px; align-items: flex-start;
  }
  /* Menu rows need a thumb-sized target, not just a text baseline. */
  nav.main a { padding: 11px 0; }
  /* ★ BUT THE BUTTON IS AN <a> TOO. The rule above is written for plain menu
     links and lands on `.btn` as well, wiping the button's own left and right
     padding to zero — so inside the drawer the black box shrank to exactly the
     width of its text and the words sat hard against the edge. It is the most
     important button on the site and it looked like a mistake, on every page.
     Additive: the plain links keep the rule above, the button gets its sides
     back. Diagnosed by SC from the rendered page, confirmed here in the source:
     there was no `.btn` override in this block at all. */
  nav.main .btn { padding: 11px 22px; }
  nav.main.open { display: flex; }
  .nav-toggle { display: block; }
  .site-bar > .btn { display: none; }
  nav.main .btn { display: inline-block; }
}

/* ---------- Hero (Ink) ---------- */
/* George's ruling, 12 Aug: the band tightened from 96/88 to 46/44, and the
   portrait moved ABOVE the name. Sally holds the right column and stretches to
   the CTA's baseline — align-items: stretch is what makes her read as part of
   his composition rather than an object dropped into it. */
.hero {
  background: var(--ink); color: var(--bone);
  padding: 46px 0 44px;
}
.hero-grid {
  display: grid; grid-template-columns: 1.5fr 1fr; gap: 60px; align-items: stretch;
  max-width: 1080px; margin: 0 auto; padding: 0 24px;
}
/* The hairline between the name and the descriptor. */
.hero-rule {
  border: none; border-top: 1px solid var(--hairline-ink);
  margin: 22px 0 0; max-width: 430px;
}
.hero h1 {
  font-family: var(--serif); font-weight: 300; font-size: 3.6rem;
  line-height: 1.08; letter-spacing: 0.005em;
}
.hero .descriptor {
  font-family: var(--serif); font-style: italic; font-weight: 400;
  font-size: 1.5rem; margin-top: 14px; color: var(--bone);
}
.hero .place {
  font-size: 0.92rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(247,244,238,0.55); margin-top: 14px;
}
/* The direct line: a phone number and an inbox, for the person who would rather
   not type into a form. On the ink hero, so it borrows the hero's own contrast. */
.hero .direct { margin-top: 18px; font-size: 0.95rem; color: rgba(247,244,238,0.75); max-width: 720px; }
.hero .direct a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
.hero .btn { margin-top: 36px; }
/* The portrait now opens the left column. DC ruled a 430-wide plate in a 12px
   Bone mount; the width is held here and the HEIGHT is left to the picture,
   because the asset is a deliberate cut from the lossless original and a fixed
   box would silently re-crop it. Same reasoning as the archive frames. */
.hero-portrait {
  background: var(--bone); padding: 12px;
  box-shadow: 0 0 0 1px var(--hairline-ink);
  max-width: 454px; margin-bottom: 26px;
}
.hero-portrait img { width: 100%; height: auto; }
@media (max-width: 820px) {
  .hero { padding: 34px 0 36px; }
  .hero-grid { grid-template-columns: 1fr; gap: 34px; }
  .hero h1 { font-size: 2.5rem; }
  .hero-portrait { max-width: 320px; }
}

/* page heroes (inner pages) */
.hero.compact { padding: 72px 0 64px; }
/* Inner-page h1s are sentences, not a name, so they need a measure to wrap inside and
   a little more air between their lines than the 1.08 the home display h1 wants. */
.hero.compact h1 { font-size: 2.7rem; font-weight: 300; line-height: 1.18; max-width: 900px; }
.hero.compact .lede {
  font-family: var(--serif); font-size: 1.35rem; font-weight: 400;
  line-height: 1.5; margin-top: 20px; max-width: 720px; color: rgba(247,244,238,0.92);
}
@media (max-width: 820px) { .hero.compact h1 { font-size: 2.1rem; } }

/* ---------- Sections ---------- */
/* 52, not 76 — George's ruling of 12 Aug, sitewide. */
section { padding: 52px 0; }
section.tint { background: var(--bone-deep); }
section.ink { background: var(--ink); color: var(--bone); }
.rule { border: none; border-top: 1px solid var(--hairline); margin: 0; }
.kicker {
  font-size: 0.8rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--slate); margin-bottom: 18px;
}
.ink .kicker { color: rgba(247,244,238,0.55); }
h2 {
  font-family: var(--serif); font-weight: 300; font-size: 2.1rem;
  line-height: 1.2; margin-bottom: 24px;
}
/* Heading leading, continuing the scale above: display h1 1.08, sentence h1 1.18,
   h2 1.2, h3 1.3. Without it h3 inherited the body's 1.65, and a heading that wraps
   on a phone was set so loosely its own two lines read as two separate things.

   Every heading wraps BALANCED (see below): the browser evens the lines itself, at
   whatever width the reader's screen happens to be. That is what replaced the hand-placed
   <br> breaks — a hard break is right at exactly one window width and lopsided at every
   other, which is how a title ends up long-line-then-stub. */
h3 {
  font-family: var(--serif); font-weight: 600; font-size: 1.25rem;
  line-height: 1.3; margin-bottom: 10px;
}
h1, h2, h3 { text-wrap: balance; }
.prose p { margin-bottom: 1.2em; max-width: 68ch; }
.prose p:last-child { margin-bottom: 0; }
.serif-lede {
  font-family: var(--serif); font-size: 1.3rem; line-height: 1.55; font-weight: 400;
}

/* ---------- Credibility ledger ---------- */
.ledger {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
  border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline);
}
.ledger div {
  padding: 34px 20px; text-align: center;
  border-left: 1px solid var(--hairline);
}
.ledger div:first-child { border-left: none; }
.ledger strong {
  display: block; font-family: var(--serif); font-weight: 600;
  font-size: 1.7rem; line-height: 1.15; margin-bottom: 6px;
}
.ledger span { font-size: 0.85rem; letter-spacing: 0.06em; color: var(--slate); }
.ledger-note {
  text-align: center; font-family: var(--serif); font-style: italic;
  color: var(--slate); font-size: 1.02rem; padding-top: 22px; max-width: 640px;
  margin: 0 auto;
}
@media (max-width: 820px) {
  .ledger { grid-template-columns: repeat(2, 1fr); }
  .ledger div:nth-child(3) { border-left: none; }
  .ledger div { border-top: 1px solid var(--hairline); }
  .ledger div:nth-child(-n+2) { border-top: none; }
}

/* ---------- Quote panels ---------- */
.quote-panel {
  background: #fff; border: 1px solid var(--hairline);
  padding: 38px 42px; margin-bottom: 26px;
}
.quote-panel blockquote {
  font-family: var(--serif); font-size: 1.12rem; line-height: 1.6; font-weight: 400;
}
.quote-panel cite {
  display: block; font-style: normal; margin-top: 18px;
  font-size: 0.9rem; letter-spacing: 0.04em; color: var(--slate);
}
.quote-panel cite strong { color: var(--ink); font-weight: 600; letter-spacing: 0.02em; }
.quote-panel.pulled .full { display: none; }
.quote-panel.pulled.open .full { display: inline; }
.quote-panel .expand {
  background:none; border:none; color: var(--meridian); cursor:pointer;
  font-size: 0.88rem; padding: 0; margin-top: 10px; font-family: var(--sans);
}
.quote-panel.distinguished {
  background: var(--ink); color: var(--bone); border-color: var(--ink);
}
.quote-panel.distinguished cite { color: rgba(247,244,238,0.65); }
.quote-panel.distinguished cite strong { color: var(--bone); }
.quote-panel .datednote {
  font-size: 0.8rem; color: rgba(247,244,238,0.5); margin-top: 8px;
}
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; align-items: start; }
@media (max-width: 820px) { .two-col { grid-template-columns: 1fr; } }

/* Prose beside a fixed side column. The per-instance width rides --aside rather than an
   inline grid-template-columns: an inline declaration outranks the mobile collapse, so
   about.html could never stack and measured 569px wide inside a 390px viewport. */
.two-col.aside,
.media-band.aside { grid-template-columns: 1fr var(--aside); }
@media (max-width: 820px) {
  .two-col.aside,
  .media-band.aside { grid-template-columns: 1fr; }
}

/* ---------- The letter ---------- */
.letter-frame {
  background: #fffdf9; border: 1px solid var(--hairline);
  box-shadow: 0 1px 0 var(--hairline), 0 14px 40px rgba(20,22,26,0.07);
  padding: 64px 72px; margin: 0 auto; max-width: 760px;
}
.letter-frame h2 {
  font-family: var(--serif); font-weight: 600; font-size: 1.45rem; margin-bottom: 30px;
}
.letter-frame p {
  font-family: var(--serif); font-size: 1.08rem; line-height: 1.75; margin-bottom: 1.3em;
}
.letter-frame .signoff { margin-top: 34px; margin-bottom: 0; }
.letter-disclosure {
  text-align: center; font-size: 0.88rem; color: var(--slate);
  letter-spacing: 0.03em; margin-top: 22px;
}
@media (max-width: 640px) { .letter-frame { padding: 40px 26px; } }

/* ---------- Offer ladder / cards ---------- */
.card-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; }
.offer-card {
  background: #fff; border: 1px solid var(--hairline); padding: 34px 30px;
}
.offer-card .step {
  font-size: 0.78rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--slate); margin-bottom: 12px;
}
.offer-card h2 { margin-bottom: 12px; }
.offer-card p { font-size: 0.96rem; color: var(--ink-soft); }
@media (max-width: 820px) { .card-row { grid-template-columns: 1fr; } }

/* ---------- Laws list ---------- */
.laws { counter-reset: law; max-width: 720px; }
.laws li {
  list-style: none; padding: 22px 0 22px 66px; position: relative;
  border-top: 1px solid var(--hairline);
}
.laws li:last-child { border-bottom: 1px solid var(--hairline); }
.laws li::before {
  counter-increment: law; content: counter(law);
  position: absolute; left: 6px; top: 20px;
  font-family: var(--serif); font-size: 1.6rem; color: var(--slate);
}
.laws strong { font-family: var(--serif); font-size: 1.08rem; }

/* ---------- Media band ---------- */
.media-band { display: grid; grid-template-columns: 300px 1fr; gap: 44px; align-items: center; }
.media-band img { border: 1px solid var(--hairline); }
@media (max-width: 820px) { .media-band { grid-template-columns: 1fr; } }

/* ---------- Clarity session ---------- */
.clarity-grid { display: grid; grid-template-columns: 320px 1fr; gap: 56px; align-items: center; }
.clarity-grid img { border: 1px solid var(--hairline-ink); }
@media (max-width: 820px) { .clarity-grid { grid-template-columns: 1fr; } }

/* ---------- Doors (teasers) ---------- */
.doors { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
.door {
  border: 1px solid var(--hairline); background: #fff;
  padding: 42px 38px; display: block; color: var(--ink); text-decoration: none;
  transition: border-color 0.2s ease;
}
.door:hover { border-color: var(--ink); text-decoration: none; }
.door h3 { font-size: 1.4rem; margin-bottom: 12px; }
.door p { color: var(--ink-soft); font-size: 0.98rem; margin-bottom: 16px; }
.door .go { font-size: 0.9rem; letter-spacing: 0.05em; color: var(--meridian); }
@media (max-width: 820px) { .doors { grid-template-columns: 1fr; } }

/* ---------- Contact form ---------- */
.req-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 40px; }
.req-card {
  /* ★ TOP-ALIGNED EXPLICITLY, because a <button> centres its own content.
     There is no CSS rule doing it — it is the button's intrinsic layout, which
     is why the computed style reads `normal` and nothing in this file looked
     guilty. The effect: "Executive Workshops" has a one-line description where
     its neighbours have two, so its title sat 10px lower and the row read as
     crooked. Declaring the axis makes the titles line up regardless of how long
     any description runs. */
  display: flex; flex-direction: column; justify-content: flex-start;
  border: 1px solid var(--hairline); background: #fff; padding: 24px 20px;
  cursor: pointer; text-align: left; font-family: var(--sans); transition: all 0.15s ease;
}
.req-card h2 { font-family: var(--serif); font-weight: 600; font-size: 1.05rem; margin-bottom: 6px; color: var(--ink); }
.req-card p { font-size: 0.85rem; color: var(--slate); line-height: 1.45; }
.req-card.selected { background: var(--ink); border-color: var(--ink); }
.req-card.selected h2 { color: var(--bone); }
.req-card.selected p { color: rgba(247,244,238,0.65); }
@media (max-width: 820px) { .req-cards { grid-template-columns: 1fr; } }

.chip-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 8px; }
.chip {
  border: 1px solid var(--hairline); background: #fff; color: var(--ink);
  padding: 8px 18px; font-size: 0.88rem; cursor: pointer; font-family: var(--sans);
  transition: all 0.12s ease; border-radius: 999px;
  /* thumb-sized on a phone — these are the busiest controls on the site */
  min-height: 44px;
}
.chip.selected { background: var(--ink); color: var(--bone); border-color: var(--ink); }

.field-label {
  font-size: 0.8rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--slate); margin: 34px 0 14px;
}
input[type=text], input[type=email], input[type=tel], textarea, select {
  width: 100%; padding: 14px 16px; border: 1px solid var(--hairline);
  background: #fff; font-family: var(--sans); font-size: 1rem; color: var(--ink);
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--meridian); outline-offset: -1px; }
/* ★ THIS BLOCK MUST SIT AFTER THE SHARED INPUT RULE ABOVE, and the first
   attempt put it before. The shared rule sets `background: #fff` — the
   SHORTHAND, which resets background-image to none — and `padding: 14px 16px`.
   At equal specificity the later rule wins, so the chevron silently never
   drew and the padding never widened. It computed appearance:none and looked
   half-fixed, which is worse than not fixed: the native arrow was gone and
   nothing replaced it. Order is the fix; specificity games are not needed. */
/* ★ THE BROWSER'S OWN ARROW CANNOT BE MOVED. A select with `appearance: auto`
   draws its chevron at a fixed inset — about 8px — and no amount of
   padding-right shifts it, which is why it sat closer to the edge than every
   other field on the form breathed. Turning the native appearance off and
   drawing the chevron ourselves is the only way to place it, and it is the
   standard cure. padding-right leaves room so a long option can never run
   underneath it. The shape is the site's own chevron in --slate, not a new one. */
select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%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' stroke='%23656A67' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  padding-right: 44px;
}

textarea { min-height: 140px; resize: vertical; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }

/*
 * THE PHONE FIELD — the country code is furniture, not something to type.
 *
 * "+61 (0)" sits INSIDE the box's border but OUTSIDE the input, so it cannot be
 * selected, deleted or accidentally submitted. Putting it in the input's value
 * looks the same and behaves badly: the first thing a person does is put the
 * caret at the end and type, and the second is backspace through the prefix
 * wondering why it will not go.
 *
 * The border moves from the input to the wrapper, so the prefix and the field
 * read as one control and the focus ring still draws round the whole thing.
 */
.tel-field {
  display: flex; align-items: stretch; width: 100%;
  border: 1px solid var(--hairline); background: #fff;
}
.tel-field {
  /* THE GOV.UK INPUT-PREFIX PATTERN, and the reason it is worth copying exactly.
     The first cut put "+61 (0)" INSIDE one shared border with the number, so the
     whole control read as a single text box — and George tapped the left half of
     it and nothing happened: "as soon as you click it disappears and allows you
     to type your own - it was not doing that with me."
     ★ THE STANDARD DOES NOT MAKE THE PREFIX CLICKABLE. It makes the prefix
     visually, unmistakably NOT THE INPUT: its own border, its own fill, and
     cursor:default so it never invites a caret. GOV.UK's compiled CSS, verbatim:
     `.govuk-input__prefix { border: 2px solid #0b0c0c; background-color: #f3f2f1;
     cursor: default; flex: 0 0 auto }`.
     Fixing this with JavaScript — pointer-events, a click handler forwarding
     focus — was papering over a control that was lying about where you type. */
  /* ★ THE COUNTRY ALWAYS TAKES ITS OWN ROW. This field lives in a one-third
     grid column, so it is narrow at EVERY desktop width — three things across
     never fits, and at 900px it truncated the country to "Australia +" and cut
     the number in half. A media query on the VIEWPORT cannot see that, because
     the constraint is the column, not the screen. Stacking unconditionally is
     both simpler and correct at every size. */
  display: flex; flex-wrap: wrap; align-items: stretch; width: 100%;
  border: 0; background: none;
}
.tel-country {
  flex: 1 0 100%; min-width: 0; max-width: 100%;
  border: 1px solid var(--hairline);
  /* background-COLOR, not the shorthand: `background: var(--bone-deep)` resets
     background-image to none and silently swallowed the chevron drawn for every
     other select on the form. The generic rule cannot win here — this class is
     later and more specific — so the arrow is declared where it will actually
     apply, rather than somewhere it loses politely. */
  background-color: var(--bone-deep);
  background-image: url("data:image/svg+xml,%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' stroke='%23656A67' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding: 12px 34px 12px 16px; margin: 0 0 -1px 0;
  color: var(--slate); font-size: 0.9rem; cursor: pointer;
}
.tel-prefix {
  /* Furniture. Its own edges, its own fill, and a cursor that says "not here". */
  flex: 0 0 auto; display: flex; align-items: center;
  padding: 13px 12px;
  border: 1px solid var(--hairline); background: var(--bone-deep);
  margin-right: -1px;
  color: var(--slate); font-size: 0.95rem; white-space: nowrap;
  cursor: default; user-select: none;
}
.tel-field input[type=tel] {
  /* The box you type in, and it looks like one. */
  flex: 1 1 0; min-width: 0;
  border: 1px solid var(--hairline); background: #fff;
  padding: 13px 16px;
}
.tel-field input[type=tel]:focus { outline: 2px solid var(--meridian); outline-offset: -1px; z-index: 1; }
.tel-field.is-invalid input[type=tel],
.tel-field.is-invalid .tel-prefix,
.tel-field.is-invalid .tel-country { border-color: #9c3122; }
.tel-field.is-invalid .tel-prefix { color: #7a2618; }

.field-hint {
  display: block; margin-top: 6px;
  font-size: 0.8rem; color: var(--slate);
}
/*
 * :empty is what keeps this from reserving a blank line under every field. The
 * element is in the markup from load — never inserted on demand — because a
 * role="alert" added to the DOM after the fact is not reliably announced.
 */
.field-error {
  display: none; margin-top: 6px;
  font-size: 0.8rem; color: #7a2618;
}
.field-error:not(:empty) { display: block; }
/* Colour is never the only signal: the message says what is wrong in words. */
.tel-field.is-invalid { border-color: #9c3122; }
.tel-field.is-invalid .tel-prefix { color: #7a2618; }
@media (max-width: 820px) { .form-grid { grid-template-columns: 1fr; } }
.form-note { font-size: 0.85rem; color: var(--slate); margin-top: 14px; }
.form-success {
  display: none; background: #fff; border: 1px solid var(--hairline);
  border-left: 3px solid var(--meridian); padding: 22px 26px; margin-top: 26px;
}
.form-error {
  display: none; background: #fff; border: 1px solid var(--hairline);
  border-left: 3px solid #9c3122; padding: 18px 22px; margin-top: 22px;
  color: #7a2618; font-size: 0.95rem;
}

/*
 * "Sally filled in your name below."
 *
 * A LIVE REGION, so it is on the page from load and simply empty until there
 * is something to say — never display:none in the markup, or the announcement
 * is not reliably made when it finally appears. :empty is what stops an empty
 * paragraph pushing the first field down by a line.
 *
 * Deliberately quieter than .form-error: this is the site being helpful, not
 * the site being wrong. Same meridian edge as Sally's own confirmation card,
 * so it reads as coming from her.
 */
.form-shared {
  background: #fff; border: 1px solid var(--hairline);
  border-left: 3px solid var(--meridian); padding: 12px 18px; margin-bottom: 26px;
  font-size: 0.92rem; color: var(--ink-soft);
}
.form-shared:empty { display: none; }

/* ---------- Closing band ---------- */
.closing { text-align: center; }
.closing .serif-lede { max-width: 640px; margin: 0 auto 12px; }
.closing .btn { margin-top: 26px; }

/* ---------- Footer ---------- */
footer.site {
  background: var(--ink); color: rgba(247,244,238,0.6);
  padding: 46px 0; font-size: 0.88rem;
}
footer .foot-grid {
  display: flex; justify-content: space-between; align-items: center;
  max-width: 1080px; margin: 0 auto; padding: 0 24px; gap: 20px; flex-wrap: wrap;
}
footer nav { display: flex; gap: 20px; flex-wrap: wrap; }
/* Footer links are real navigation on a phone — give them a thumb target. */
@media (max-width: 820px) {
  footer nav { gap: 4px 20px; }
  footer nav a { padding: 11px 0; }
}
footer a { color: rgba(247,244,238,0.75); text-decoration: none; }
footer a:hover { color: var(--bone); }

/* ---------- Press wall (DC Plate 05 — identical treatment, identical weight) ---------- */
.press-wall { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
.press img { border: 1px solid var(--hairline); width: 100%; height: auto; }
.press figcaption {
  font-size: 0.82rem; color: var(--slate); letter-spacing: 0.04em;
  padding-top: 10px; line-height: 1.5;
}
.press-wall .press:nth-child(n+3) { }
@media (max-width: 820px) { .press-wall { grid-template-columns: 1fr; } }
.press-wall { grid-template-columns: repeat(6, 1fr); }
.press-wall .press.lead { grid-column: span 3; }
.press-wall .press:not(.lead) { grid-column: span 2; }
@media (max-width: 820px) {
  .press-wall { grid-template-columns: 1fr; }
  .press-wall .press.lead, .press-wall .press:not(.lead) { grid-column: span 1; }
}

/* Folkes distinguished panel with photograph */
.folkes-grid { display: grid; grid-template-columns: 300px 1fr; gap: 34px; align-items: center; }
.folkes-grid img { border: 1px solid var(--hairline-ink); width: 100%; height: auto; }
@media (max-width: 820px) { .folkes-grid { grid-template-columns: 1fr; } }

/* menu CTA keeps button colours despite nav link rule */
nav.main a.btn { color: var(--bone); border-bottom: none; }
nav.main a.btn:hover { border-bottom: none; }

/* ---------- Trusted-by strip ---------- */
.trusted-by {
  text-align: center; margin-top: 30px; padding-top: 24px;
  border-top: 1px solid var(--hairline);
}
.tb-label {
  display: block; font-size: 0.72rem; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--slate); margin-bottom: 10px;
}
.tb-names {
  font-family: var(--serif); font-size: 1.05rem; letter-spacing: 0.04em; color: var(--ink-soft);
}

/* ---------- Outcome stat row ---------- */
.stat-row {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; margin-top: 34px;
  border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline);
}
.stat-row div { padding: 26px 20px; text-align: center; border-left: 1px solid var(--hairline); }
.stat-row div:first-child { border-left: none; }
.stat-row strong {
  display: block; font-family: var(--serif); font-weight: 400; font-size: 1.6rem; margin-bottom: 4px;
}
.stat-row span { font-size: 0.85rem; color: var(--slate); line-height: 1.5; display: block; max-width: 26ch; margin: 0 auto; }
@media (max-width: 820px) {
  .stat-row { grid-template-columns: 1fr; }
  .stat-row div { border-left: none; border-top: 1px solid var(--hairline); }
  .stat-row div:first-child { border-top: none; }
}

/* ---------- Magnet form ---------- */
.magnet-form { margin-top: 28px; position: relative; }
.magnet-form input { background: rgba(246,244,239,0.06); border-color: rgba(246,244,239,0.3); color: var(--bone); }
.magnet-form input::placeholder { color: rgba(246,244,239,0.5); }
.magnet-form input:focus { outline-color: var(--bone); }

/* ---------- The direct line in the header (George's ruling, 12 Aug) ---------- */
/* It sits on the right, before the CTA. On a phone the CTA leaves the bar and
   the number stays — a number is one tap to a call, which is the whole point
   of putting it there. */
.site-phone {
  font-family: var(--sans); font-size: 0.86rem; letter-spacing: 0.04em;
  color: var(--ink); text-decoration: none; white-space: nowrap;
  padding: 11px 0; margin-left: auto;
}
.site-phone:hover { text-decoration: underline; }
/* Below the menu breakpoint the bar carries wordmark + number + hamburger, and
   the 40px gap it was built with pushes the hamburger clean off a 390 screen.
   Measured, not guessed: the page scrolled sideways by 29px until this. */
@media (max-width: 1400px) {
  .site-bar { gap: 14px; }
  .site-phone { margin-left: auto; }
}
@media (max-width: 520px) {
  .site-bar { gap: 10px; padding-left: 18px; padding-right: 18px; }
  .wordmark { font-size: 1.12rem; }
  .site-phone { font-size: 0.78rem; }
}
/* Pre-dates the direct line, found while measuring it: "Book your 20-Minute
   Clarity Session" is 366px and set nowrap, so on a 320px phone every page
   scrolled sideways by 70px. It may wrap down there. */
@media (max-width: 380px) {
  .btn { white-space: normal; }
  .site-bar { padding-left: 14px; padding-right: 14px; }
}
/* A grid item will not shrink below its own min-content unless told it may.
   Without this the hero column held itself at 318px inside a 272px screen.
   The frame needs it too — it is a flex child of the mount, same rule. */
.hero-grid > * { min-width: 0; }

/* ── WHAT CLIENTS SAY — the hero's right column when Sally is not in it ──────
 *
 * George: "the Sally chat box goes and the area behind it is all blank dark —
 * something should be there instead — maybe 3 of the best testimonials?" His
 * three, his order: Jim Poignand, Brett W, Steve Folkes.
 *
 * ★ BOTH LIVE IN THE SAME GRID CELL. The mount IS the second column — there is
 * no wrapper — so the panel is placed into that same cell rather than becoming
 * a third child that would wrap onto a new row. `hidden` decides which of them
 * the visitor sees, and `hidden` is exactly what sally.js already toggles:
 * false when she mounts in the hero, true when she leaves it.
 *
 * ★★ AND IT COVERS THE CASE NOBODY ASKED ABOUT: with JavaScript off the mount
 * stays hidden and never had anything behind it — the column was simply empty.
 * Now it carries three testimonials. The fallback is better than the thing it
 * falls back from being absent.
 *
 * The quotes are verbatim opening sentences, not trimmed paraphrases: a
 * client's words are theirs, and shortening testimony to fit a column is
 * editing what somebody said. The link carries a reader to the rest.
 */
.sally-mount, .hero-proof { grid-column: 2; grid-row: 1; }
.hero-proof {
  display: flex; flex-direction: column; gap: 26px;
  align-self: start; padding: 4px 0;
}
/* She is in the hero, so the panel stands down. */
.sally-mount:not([hidden]) ~ .hero-proof { display: none; }

.hero-proof-kicker {
  font-family: var(--mono, var(--sans)); font-size: 0.7rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: rgba(247, 244, 238, 0.55); margin: 0;
}
.hero-proof-item { margin: 0; }
.hero-proof blockquote {
  margin: 0 0 8px; padding-left: 16px;
  border-left: 2px solid rgba(247, 244, 238, 0.28);
  font-family: var(--serif); font-style: italic; font-size: 0.98rem;
  line-height: 1.5; color: var(--bone);
}
.hero-proof-who {
  margin: 0 0 0 18px; font-size: 0.78rem; letter-spacing: 0.01em;
  color: rgba(247, 244, 238, 0.6);
}
.hero-proof-more {
  align-self: start; margin-top: 2px;
  font-size: 0.82rem; letter-spacing: 0.02em;
  color: var(--bone); border-bottom: 1px solid rgba(247, 244, 238, 0.4);
  text-decoration: none; padding-bottom: 2px;
}
.hero-proof-more:hover { border-bottom-color: var(--bone); }

/* One column on a phone: both share row 2 instead of column 2. */
@media (max-width: 820px) {
  .sally-mount, .hero-proof { grid-column: 1; grid-row: auto; }
}
.sally-mount > .sally-frame { min-width: 0; }

/* Also pre-existing, same measuring pass: two ledger columns need 203px each
   and a 320px phone offers 136. It goes to one column down there. */
@media (max-width: 520px) {
  .ledger { grid-template-columns: 1fr; }
  .ledger div,
  .ledger div:nth-child(3) { border-left: none; border-top: 1px solid var(--hairline); }
  .ledger div:first-child { border-top: none; }
}

/* ============================================================
   SALLY — George Helou's AI personal assistant
   Built to DC's ruling of 2026-08-12. Behaviour lives in js/sally.js.

   Two surfaces, one frame: an open window in the home hero's right column,
   and a docked pill on every other page that opens the same window centred.

   ONE DELIBERATE DEPARTURE FROM THE BRIEF, recorded rather than silent:
   DC specifies the frame background as Bone AND the greeting text as Bone,
   which would set the words invisible against their own background; line 4 is
   given as #AFB3AF, which reads at 1.9:1 on Bone. The rest of the brief settles
   it — Sally's bubbles are #EAE7E0 with the VISITOR's in Ink, so the thread is
   a light room. The greeting is therefore Ink, and its subordinate line is
   Slate #656A67, which is the colour §11 itself reserves for text. Flagged to
   DC; if the intent was a dark thread, the two bubble colours move, not these.
   ============================================================ */

.sally-frame {
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  background: var(--bone); color: var(--ink);
  border: 1px solid #CFCBC2; border-radius: 6px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.24);
  height: 100%;
}

/* She rises a beat after the hero lands, so the eye reads the name first. */
@keyframes sallyRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.hero .sally-frame { animation: sallyRise 620ms cubic-bezier(.22, .61, .36, 1) 300ms both; }
/* THE PANEL MUST NOT GROW WITH THE CONVERSATION.
   Found by screenshotting a real booking: after six exchanges the hero panel
   was 2,006px tall on a 900px screen and 2,108px on a phone. It had pushed the
   hero open, run under the header and taken the page with it. The thread has
   always had `overflow-y: auto`, but a scroll region only scrolls inside a
   BOUNDED box, and the mount had a min-height and no ceiling — so the frame
   simply grew and there was nothing to scroll within.
   Taking the frame out of flow fixes it at the root: it can no longer
   contribute its own content height to the row, so the row is sized by the
   left column exactly as DC intended, and the thread scrolls inside it. */
.sally-mount { min-height: 440px; display: flex; position: relative; }

.sally-mount > .sally-frame { position: absolute; inset: 0; }

/* ---- header row ---- */
.sally-head {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 14px; border-bottom: 1px solid #DCD8CF; flex: 0 0 auto;
}
.sally-mark {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid #C7C3BA; display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-size: 0.85rem; line-height: 1; color: var(--ink);
}
.sally-who { min-width: 0; }
.sally-name { font-family: var(--serif); font-weight: 400; font-size: 1rem; line-height: 1.15; }
/* The AI disclosure. It is here, in the panel header on every page, and in her
   own second spoken line. Three places, none of them apologetic — do not
   remove any of them. */
.sally-role {
  font-size: 8px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--slate); margin-top: 3px; line-height: 1.3;
}
.sally-x {
  position: relative;
  margin-left: auto; flex: 0 0 auto; background: none; border: none; cursor: pointer;
  color: var(--slate); font-size: 1.15rem; line-height: 1; padding: 13px;
}
.sally-x::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.sally-x:hover { color: var(--ink); }

/* ---- thread ---- */
.sally-thread {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 18px 16px; display: flex; flex-direction: column; gap: 11px;
}

.sally-greeting { display: flex; flex-direction: column; gap: 7px; }
.sally-greeting p {
  font-family: var(--serif); font-weight: 300; font-size: 1.25rem;
  line-height: 1.35; color: var(--ink);
}
.sally-greeting p.g4 {
  font-size: 1.03rem; line-height: 1.5; color: var(--slate); margin-top: 5px;
}

/* The caret blinks at the end of the line being typed, then moves into the
   compose field and keeps blinking there. That handoff IS the invitation. */
.sally-caret {
  display: inline-block; width: 2px; height: 1em; background: var(--ink);
  vertical-align: -0.12em; margin-left: 2px;
  animation: sallyBlink 1s step-end infinite;
}
@keyframes sallyBlink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

/* ---- bubbles: no speaker labels, no timestamps, no per-message avatars ---- */
.sally-msg {
  font-family: var(--serif); font-weight: 300; font-size: 0.97rem; line-height: 1.6;
  padding: 10px 15px; white-space: pre-wrap; overflow-wrap: anywhere;
}
.sally-msg.from-sally {
  align-self: flex-start; max-width: 88%;
  background: #EAE7E0; color: var(--ink); border-radius: 20px 20px 20px 4px;
}
.sally-msg.from-them {
  align-self: flex-end; max-width: 84%;
  background: var(--ink); color: var(--bone); border-radius: 20px 20px 4px 20px;
}

/* A booking, given its own face.
   Everything else in the thread is a message; this is the one moment where
   something real happened. It sat in a grey bubble reading exactly like
   "would either of those suit?", which is how a confirmed appointment ends up
   being re-read three times or checked by phone. Meridian, because that is the
   colour this site already uses for the things that count, and full width
   because it is not somebody's turn to speak. */
.sally-booked {
  align-self: stretch; margin: 4px 0;
  background: #fff; border: 1px solid var(--meridian);
  border-left-width: 3px; border-radius: 4px;
  padding: 13px 16px 15px;
}
.sally-booked-label {
  display: block; font-family: var(--sans);
  font-size: 8px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--meridian); margin-bottom: 6px;
}
.sally-booked-when {
  font-family: var(--serif); font-size: 1.08rem; line-height: 1.3; color: var(--ink);
}
.sally-booked-where {
  font-family: var(--sans); font-size: 0.86rem; color: var(--slate); margin-top: 3px;
}

.sally-thinking { align-self: flex-start; display: flex; gap: 5px; padding: 13px 15px; }
.sally-thinking i {
  width: 5px; height: 5px; border-radius: 50%; background: var(--meridian);
  animation: sallyDot 1.3s infinite;
}
.sally-thinking i:nth-child(2) { animation-delay: 0.16s; }
.sally-thinking i:nth-child(3) { animation-delay: 0.32s; }
@keyframes sallyDot {
  0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
  30%           { opacity: 1;    transform: translateY(-3px); }
}

/* ---- compose ---- */
.sally-compose {
  flex: 0 0 auto; display: flex; align-items: center; gap: 9px;
  padding: 12px 14px; border-top: 1px solid #DCD8CF;
}
.sally-compose input {
  flex: 1 1 auto; width: auto; background: #fff;
  border: 1px solid #DCD8CF; border-radius: 999px;
  padding: 11px 16px; font-family: var(--sans);
  min-height: 44px;  /* a comfortable target, not just a readable one */
  font-size: 16px;   /* 16px exactly, or iOS zooms the page on focus */
}
.sally-compose input:focus { outline: 2px solid var(--meridian); outline-offset: -1px; }
/* THE TARGET IS BIGGER THAN THE BUTTON.
   DC drew a 38px disc and it should stay a 38px disc — but 38px is under the
   44px a thumb needs, and the audit flagged it on the primary action of the
   whole panel. The answer is not to redraw it: visual size and touch size are
   different things. The disc keeps its dimensions and the tappable area is
   pushed out to 44×44 with a transparent pseudo-element, so nothing moves and
   nobody misses. Same trick on the close X. */
.sally-send {
  position: relative;
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%; border: none;
  background: var(--meridian); color: var(--bone); cursor: pointer;
  font-size: 1rem; line-height: 1;
}
.sally-send::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.sally-send:hover { background: #26504d; }
.sally-send:disabled { opacity: 0.45; cursor: default; }
.sally-send:disabled:hover { background: var(--meridian); }
/* While she is answering, the field stays readable but will not take a second
   question — the guard itself is in js/sally.js; this is only how it looks. */
.sally-compose.busy input { opacity: 0.6; }

/* The greeting's fallback copy, and the strings the interior pages read.
   Present in the markup so George can edit them at /admin; never displayed. */
.sally-strings { display: none; }

/* ---- the docked pill: it sits still and waits ---- */
/* No badge, no notification dot, no bounce, no tooltip firing on scroll. */
.sally-pill {
  position: fixed; right: 32px; bottom: 32px; z-index: 60;
  display: inline-flex; align-items: center; gap: 11px;
  width: 217px; height: 58px; padding: 0 16px;
  background: var(--ink); color: var(--bone);
  border: 1px solid var(--hairline-ink); border-radius: 999px;
  font-family: var(--sans); font-size: 0.9rem; letter-spacing: 0.02em;
  cursor: pointer; text-align: left;
}
.sally-pill .sally-mark { border-color: rgba(247, 244, 238, 0.35); color: var(--bone); }
.sally-pill-label { flex: 1 1 auto; }
.sally-pill-x { flex: 0 0 auto; color: rgba(247, 244, 238, 0.5); font-size: 1.1rem; padding: 0 2px; }
.sally-pill-x:hover { color: var(--bone); }

/* RESERVED SPACE FOR THE PILL — SC's Block 1, and the correct instinct.
   A fixed pill that merely stacks on top still covers whatever is under it:
   measured at 390px, it sat exactly on the enquiry form's submit button. The
   page gives up the room rather than the two of them fighting over z-index.
   The class is added by js/sally.js only when a pill actually exists, so pages
   without one keep their own footing. 58px pill + 32px offset + breathing. */
html.sally-has-pill body { padding-bottom: 108px; }
@media (max-width: 640px) {
  /* The pill is nearly full width down here, so the clearance is the same but
     it matters far more — this is where the collision was real. */
  html.sally-has-pill body { padding-bottom: 96px; }
}

/* AT 390px THE PILL IS NOT DRAWN WHILE SHE IS OPEN — SC's Block 1.
   On a phone the sheet fills the screen; a pill behind it is invisible clutter
   that still takes a tap target and still reads out to a screen reader. */
@media (max-width: 640px) {
  body:has(.sally-modal) .sally-pill { display: none; }
}

/* Closed pill retires here. She never reopens herself. */
.sally-tab {
  position: fixed; right: 0; top: 50%; transform: translateY(-50%); z-index: 60;
  writing-mode: vertical-rl; background: var(--ink); color: var(--bone);
  border: 1px solid var(--hairline-ink); border-right: none; border-radius: 6px 0 0 6px;
  padding: 15px 9px; cursor: pointer;
  font-family: var(--sans); font-size: 0.76rem; letter-spacing: 0.14em; text-transform: uppercase;
}

/* ---- the open window ---- */
.sally-scrim { position: fixed; inset: 0; background: rgba(27, 31, 30, 0.34); z-index: 70; }
.sally-modal {
  position: fixed; left: 50%; top: 50%; z-index: 71;
  width: 560px; height: 620px;
  max-width: calc(100vw - 40px); max-height: calc(100vh - 80px);
  transform: translate(-50%, -50%);
  animation: sallyPanelIn 460ms cubic-bezier(.22, .61, .36, 1) both;
}
/* Both keyframes carry the centring translate. A plain translateY keyframe
   overrides the transform above and throws the panel off-centre — DC shipped
   that bug once and caught it; this note is why you will not. */
@keyframes sallyPanelIn {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 12px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}

/* THE CARRIED WINDOW — she opens herself because the visitor was mid-conversation
   and changed page (George's ruling of 14 Aug), rather than because they clicked.
   She opens WHERE SHE ALREADY WAS, bottom-right, growing out of the docked pill.
   The centred window is for a deliberate click; landing on a page you asked for
   and finding a panel across the middle of it is not the same act, and it covers
   the thing you came to read. This one also has no scrim, for the same reason —
   see openModal(carried) in js/sally.js. */
.sally-modal.carried {
  left: auto; right: 32px; top: auto; bottom: 32px;
  transform: none;
  /* Shorter than the clicked window. She is a presence the visitor is carrying,
     not the thing they came for — at the clicked window's 620 she reaches up
     into the headline of the page they just chose. The thread scrolls, so a
     shorter window loses nothing but the empty room under it. */
  height: 460px;
  max-height: calc(100vh - 96px);
  animation: sallyCarriedIn 460ms cubic-bezier(.22, .61, .36, 1) both;
}
@keyframes sallyCarriedIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* Phone: a sheet from 118px down, so the wordmark and the top of the hero stay
   visible. Never a full-screen takeover on landing. */
@media (max-width: 640px) {
  .sally-modal {
    left: 0; top: 118px; transform: none;
    width: 100%; max-width: 100%;
    height: auto; bottom: 0; max-height: none;
    border-radius: 6px 6px 0 0;
    animation: sallySheetIn 460ms cubic-bezier(.22, .61, .36, 1) both;
  }
  @keyframes sallySheetIn {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: none; }
  }
  /* On a phone the sheet is the only sane form, carried or clicked — there is
     no room to sit in a corner. This needs its own rule because `.carried`
     outranks the sheet rule above and would otherwise keep her bottom-right. */
  .sally-modal.carried {
    left: 0; right: auto; top: 118px; bottom: 0;
    animation-name: sallySheetIn;
  }
  .sally-modal .sally-frame { border-radius: 6px 6px 0 0; }
  /* ★ ON A PHONE THE LAUNCHER BECOMES A CIRCLE, which is what every chat
     widget does at this width and for a good reason: the pill is 217px wide,
     and on a 390px screen that is FIFTY-SIX PER CENT OF THE VIEWPORT floating
     over the page. Measured at nine scroll positions it covered real body text
     on every content page — testimonials, the seven laws, the contact chips.
     SC's fix for this was bottom padding on the page. That only helps at the
     very END of a document, and 108px of it is already there; a position:fixed
     element sits over the content at every scroll position in between, which is
     exactly where George saw it. Shrinking the launcher is the fix that
     addresses where the problem actually is.
     Sally stays — George ruled that explicitly ("SO IT STAYS"). She just stops
     taking half the screen to say so. */
  .sally-pill {
    right: 16px; bottom: 16px;
    width: 56px; height: 56px; padding: 0;
    justify-content: center; gap: 0; border-radius: 50%;
  }
  .sally-pill .sally-pill-label { display: none; }
  .sally-mount { min-height: 380px; }
}

@media (prefers-reduced-motion: reduce) {
  .hero .sally-frame,
  .sally-modal { animation: none; }
  .sally-caret { animation: none; }
  .sally-thinking i { animation: none; opacity: 0.5; }
}
/* Keyboard entry point and comfortable footer targets. */
.skip-link{position:absolute;left:-9999px;top:8px;z-index:9999;background:var(--ink);color:var(--bone);padding:12px 18px}
.skip-link:focus{left:8px}
footer.site a{display:inline-flex;min-height:44px;align-items:center}
