/* ===========================================================================
   DURHAM CLEAN — DESIGN SYSTEM
   ---------------------------------------------------------------------------
   The identity comes from Amy's own flyers and logo: a black serif wordmark
   with a script accent, an olive sprig, three sparkles, mocha bands on cream.

   Every colour below was SAMPLED from her logo file rather than guessed.

   ── THE CONTRAST TRAP ──────────────────────────────────────────────────
   Her mocha (#AE9C90) is 2.35:1 on cream. That is decorative-only. Her
   flyers set small white text on that mocha band at roughly 2.1:1, which is
   fine at poster size held in your hand and genuinely unreadable at body
   size on a phone. So, without changing how her brand looks:

     · mocha stays for fills, rules and large display type
     · --mocha-text (5.77:1) is used wherever mocha has to be READ
     · text on a mocha fill is INK, never white

   ── WHY IT LOOKS LIKE PRINT, NOT LIKE A WEBSITE ───────────────────────
   Everything Amy has ever handed a customer is printed matter: a flyer, a
   price list, a checklist. That is the material this is built from, and it
   is also the fastest way to not look like every other small-business site.

   The house style of a template — rounded cards, drop shadows, an icon in a
   circle above every heading, three columns of equal boxes, everything
   centred — is what makes those sites blur into one another. None of it is
   here. Instead:

     · hairline rules do the dividing, not boxes and shadows
     · corners are square; the only curves are on form controls, where a
       radius genuinely helps them read as tappable
     · type sizes are far apart, the way a printed page sets a headline
       against a caption, rather than everything hovering around 16–24px
     · layouts are asymmetric and left-aligned, because a centred column of
       short paragraphs is the single most recognisable "template" tell
     · the ornament is hers — her sparkles, her sprig, her monogram — and it
       is used sparingly enough to still mean something

   She sells on quality and charges accordingly — "cleaning is a luxury
   service and it shouldn't be disregarded on price". A cheap-looking site
   would argue against her.
   ======================================================================== */

:root {
  /* --- Surfaces, sampled from the logo ---------------------------------- */
  --paper: #f5f1ee;        /* her exact background cream */
  --paper-warm: #efe9e4;   /* recessed panels */
  --card: #fffdfb;         /* raised surfaces, a touch lighter than the page */
  --espresso: #3e3229;     /* the single dark surface */

  /* --- Ink -------------------------------------------------------------- */
  --ink: #2f2f2f;          /* 11.92:1 on paper — body text */
  --ink-soft: #58524d;     /*  7.03:1 — secondary text */
  --ink-faint: #7b736c;    /*  4.62:1 — small print, still AA at body size */

  /* --- Mocha ------------------------------------------------------------ */
  --mocha: #ae9c90;        /* 2.35:1 — DECORATIVE ONLY, never small text */
  --mocha-band: #b4a193;   /* her flyer band fill */
  --mocha-pale: #e7ded7;   /* tints, chips, hovers */
  --mocha-text: #6f5a4b;   /* 5.77:1 — mocha that has to be readable */
  --mocha-deep: #55443a;   /* 8.34:1 — headings that want warmth */

  --rule: #e2d9d2;         /* the hairline that does most of the work */
  --rule-strong: #cdbfb5;

  /* --- State ------------------------------------------------------------
     Deliberately not mocha: a confirmation and an error must be tellable
     apart from the brand and from each other, and neither relies on colour
     alone — there is always an icon or a word alongside. */
  --good: #2f7d5d;         /* 5.12:1 on paper */
  --good-pale: #e4f0ea;
  --bad: #a5372c;          /* 6.68:1 on paper */
  --bad-pale: #f8e7e4;

  /* --- Type ------------------------------------------------------------- */
  --display: 'Bodoni Moda', 'Didot', Georgia, serif;
  --body: 'Jost', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --script: 'Sacramento', 'Snell Roundhand', cursive;

  /* --- Shape ------------------------------------------------------------
     Near-zero. A 2px radius reads as a cut edge; an 18px one reads as a
     web card, which is the look this is deliberately avoiding. Form
     controls are the exception and get --r-field. */
  --r: 2px;
  --r-field: 4px;

  /* Shadows survive only where something genuinely floats above the page —
     the mobile call bar, the nav drawer. Never on a static block. They are
     warm rather than grey: a neutral shadow on cream looks like dirt, which
     is an unfortunate thing to evoke on a cleaning website. */
  --shadow-float: 0 -2px 10px rgba(62, 50, 41, .10);
  --shadow-drawer: 0 18px 40px rgba(62, 50, 41, .18);

  --gutter: 1.25rem;
  --ease: cubic-bezier(.22, 1, .36, 1);
}
@media (width >= 48rem) { :root { --gutter: 2rem; } }
@media (width >= 90rem) { :root { --gutter: 3rem; } }

/* ==========================================================================
   BASE
   ========================================================================== */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
  -webkit-text-size-adjust: 100%;
  background: var(--paper);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 500;
  line-height: 1.08;
  margin: 0;
  color: var(--ink);
  text-wrap: balance;
}

/* A wide range deliberately. Her wordmark is high-contrast serif, and
   Bodoni only looks like Bodoni when it is set large — the thin strokes
   disappear at 20px. So headlines go big and captions go small, with very
   little in between. */
h1 { font-size: clamp(2.4rem, 1.2rem + 5vw, 5rem); letter-spacing: -.01em; }
h2 { font-size: clamp(1.85rem, 1.1rem + 3vw, 3.2rem); letter-spacing: -.005em; }
h3 { font-size: clamp(1.2rem, 1rem + .8vw, 1.55rem); }

p { margin: 0 0 1em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--mocha-text); text-decoration-thickness: 1px; text-underline-offset: .18em; }
a:hover { color: var(--mocha-deep); }

img, svg, video { max-width: 100%; height: auto; display: block; }

/* Focus ring is never removed, only styled. Espresso on cream is 9.4:1, so
   it is clearly visible wherever it lands. */
:focus-visible {
  outline: 3px solid var(--mocha-deep);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection { background: var(--mocha-pale); color: var(--ink); }

/* One rule that prevents a whole family of bugs: anything given the hidden
   attribute is hidden, whatever else a stylesheet says about it. */
[hidden] { display: none !important; }

/* ==========================================================================
   LAYOUT
   ========================================================================== */

.wrap {
  width: 100%;
  max-width: 78rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap-narrow { max-width: 46rem; }
.wrap-wide { max-width: 92rem; }

.section { padding-block: clamp(3.5rem, 2rem + 6vw, 7.5rem); }
.section-tint { background: var(--paper-warm); }
.section-dark { background: var(--espresso); color: #f3ece6; }
.section-dark h1, .section-dark h2, .section-dark h3 { color: #fffdfb; }
.section-dark a { color: #e3cdbb; }
.section-tight { padding-block: clamp(2.5rem, 1.5rem + 4vw, 5rem); }

.centred { text-align: center; }
.stack > * + * { margin-top: 1rem; }

/* ==========================================================================
   EDITORIAL FURNITURE
   ==========================================================================
   The devices that carry the "printed" feel. A section opener here is a
   rule, a small caps label and a large headline — the way a magazine opens
   a section — rather than a centred eyebrow-plus-heading-plus-divider
   stack, which is the template default.
   ========================================================================== */

.eyebrow {
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--mocha-text);
  margin: 0 0 .9rem;
}

/* Section opener: hairline above, label and heading below, left aligned.
   The number is optional and set in the margin, like a chapter mark. */
.opener {
  border-top: 1px solid var(--rule-strong);
  padding-top: 1.4rem;
  margin-bottom: clamp(2rem, 1rem + 3vw, 3.5rem);
  display: grid;
  gap: .5rem;
}
.opener .eyebrow { margin-bottom: 0; }
.opener h2 { max-width: 20ch; }
.opener .lede { max-width: 52ch; margin-top: .6rem; }

/* Centred variant, used sparingly — only where a section genuinely is a
   single statement rather than a column of content. */
.opener-centred { text-align: center; justify-items: center; }
.opener-centred h2, .opener-centred .lede { max-width: 34ch; margin-inline: auto; }

.script {
  font-family: var(--script);
  font-size: clamp(1.9rem, 1.2rem + 2.6vw, 3rem);
  line-height: 1;
  color: var(--mocha-text);
  font-weight: 400;
}

/* Her three sparkles, from the logo. Used as a section break, and only
   where a break is genuinely needed — an ornament that appears every
   300px stops being an ornament. */
.sparkles {
  display: flex;
  gap: .5rem;
  align-items: center;
  justify-content: center;
  color: var(--mocha);
  font-size: .6rem;
  letter-spacing: .3em;
  margin-block: 2rem;
}
.sparkles::before, .sparkles::after {
  content: '';
  width: min(4rem, 12vw);
  height: 1px;
  background: currentColor;
  opacity: .6;
}

.lede { font-size: clamp(1.0625rem, 1rem + .4vw, 1.3rem); color: var(--ink-soft); line-height: 1.55; }
.lede-sm { font-size: 1.0625rem; color: var(--ink-soft); max-width: 46rem; }

/* ==========================================================================
   BUTTONS
   ==========================================================================
   Square, with a fine border and wide letter-spaced caps. Pills read as
   "app"; this reads as the button on a printed reply card.

   Ink on mocha, never white on mocha — see the contrast note at the top.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  min-height: 3.4rem;
  padding: .9rem 2.2rem;
  border: 1px solid transparent;
  border-radius: var(--r);
  font-family: var(--body);
  font-size: .8125rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .3s var(--ease), color .3s, border-color .3s;
}

.btn-primary { background: var(--espresso); color: #fffdfb; border-color: var(--espresso); }
.btn-primary:hover { background: #16110d; border-color: #16110d; color: #fffdfb; }

.btn-secondary { background: transparent; border-color: var(--rule-strong); color: var(--mocha-text); }
.btn-secondary:hover { background: var(--espresso); border-color: var(--espresso); color: #fffdfb; }

.btn-block { width: 100%; }

.section-dark .btn-primary { background: #fffdfb; color: var(--espresso); border-color: #fffdfb; }
.section-dark .btn-primary:hover { background: #e3cdbb; border-color: #e3cdbb; color: #2a211a; }
.section-dark .btn-secondary { border-color: #6d5d51; color: #e3cdbb; }
.section-dark .btn-secondary:hover { background: #fffdfb; border-color: #fffdfb; color: var(--espresso); }

/* A text link with a rule under it that extends on hover — used instead of
   a "Learn more →" button, which is a template staple. */
.linkline {
  display: inline-block;
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--mocha-text);
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--rule-strong);
  transition: border-color .3s, color .3s;
}
.linkline:hover { color: var(--espresso); border-bottom-color: var(--espresso); }

/* ==========================================================================
   HEADER
   ==========================================================================
   NOTE: no backdrop-filter, transform, filter or contain on .site-header.
   Any of those makes it the containing block for a position:fixed drawer,
   and the mobile menu then opens inside the header strip instead of over
   the page.
   ========================================================================== */

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--espresso); color: #fff; padding: .8rem 1.2rem;
}
.skip:focus { left: 0; top: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}
.header-inner { display: flex; align-items: center; gap: 1rem; padding-block: .7rem; }

.brand { display: flex; align-items: center; gap: .75rem; text-decoration: none; margin-right: auto; }
.brand-mark { width: 2.7rem; height: 2.7rem; flex: none; }
.brand-words { display: grid; }
.brand-name {
  font-family: var(--display);
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.1;
}
.brand-tag {
  font-family: var(--script);
  font-size: 1.05rem;
  color: var(--mocha-text);
  line-height: 1;
  margin-top: .1rem;
}

.nav-toggle {
  display: grid; place-items: center;
  width: 2.9rem; height: 2.9rem;
  background: none; border: 1px solid var(--rule-strong); border-radius: var(--r);
  cursor: pointer;
}
.nav-bars { display: grid; gap: 4px; }
.nav-bars i { display: block; width: 18px; height: 1.5px; background: var(--ink); transition: transform .3s, opacity .3s; }
.nav-toggle[aria-expanded="true"] .nav-bars i:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-bars i:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-bars i:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

#primary-nav ul { list-style: none; margin: 0; padding: 0; }
#primary-nav a {
  font-family: var(--body);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-decoration: none;
  padding-block: .4rem;
  border-bottom: 1px solid transparent;
  transition: color .25s, border-color .25s;
}
#primary-nav a:hover { color: var(--espresso); border-bottom-color: var(--espresso); }
#primary-nav a[aria-current="page"] { color: var(--espresso); border-bottom-color: var(--mocha); }

/* Phone: a drawer that comes down over the page. */
@media (width < 60rem) {
  #primary-nav {
    position: fixed; inset: 0 0 auto; top: 0;
    background: var(--paper);
    box-shadow: var(--shadow-drawer);
    padding: 5.5rem var(--gutter) 2rem;
    transform: translateY(-100%);
    transition: transform .4s var(--ease);
    visibility: hidden;
  }
  #primary-nav.is-open { transform: none; visibility: visible; }
  #primary-nav ul { display: grid; gap: .2rem; }
  #primary-nav li { border-bottom: 1px solid var(--rule); }
  #primary-nav a { display: block; padding: 1rem 0; font-size: .95rem; letter-spacing: .1em; }
}
@media (width >= 60rem) {
  .nav-toggle { display: none; }
  #primary-nav ul { display: flex; gap: 1.9rem; align-items: center; }
}

/* ==========================================================================
   FOOTER
   ========================================================================== */

.site-footer { background: var(--espresso); color: #e8ddd4; padding-block: 4rem 2.4rem; }

/* The mobile call bar is position:fixed at the bottom of the viewport, so it
   floats OVER whatever the page ends with. The footer's 2.4rem of bottom
   padding was less than the bar is tall, which buried the last row — and the
   last row is where Amy's "Site settings" link lives, so on a phone she
   could not reach her own page at all.

   Nothing in the layout can know a fixed element is there; it has to be
   compensated for explicitly. --callbar-h is declared next to the bar itself
   so the two cannot drift apart. */
@media (width < 60rem) {
  .site-footer {
    padding-bottom: calc(2.4rem + var(--callbar-h) + env(safe-area-inset-bottom));
  }
}
.footer-inner { display: grid; gap: 2.4rem; }
.footer-top { display: grid; gap: 2.4rem; }
@media (width >= 48rem) {
  .footer-top { grid-template-columns: 1.2fr 1fr 1fr; gap: 3rem; }
}
.footer-name {
  font-family: var(--display); font-size: 1.5rem; letter-spacing: .1em;
  text-transform: uppercase; color: #fffdfb; display: block;
}
.footer-tag { display: block; font-family: var(--script); font-size: 1.5rem; color: #d9c3b0; margin-top: .3rem; }
.footer-blurb { font-size: .9rem; color: #bcaa9c; margin-top: 1rem; max-width: 32ch; }
.footer-h {
  font-family: var(--body); font-size: .6875rem; font-weight: 500;
  letter-spacing: .24em; text-transform: uppercase; color: #a2917f;
  margin: 0 0 1rem;
}
.footer-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.footer-list a { color: #e8ddd4; text-decoration: none; font-size: .95rem; }
.footer-list a:hover { color: #fff; text-decoration: underline; }
.footer-base {
  border-top: 1px solid #55463a; padding-top: 1.6rem;
  display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; justify-content: space-between;
  font-size: .75rem; color: #a2917f;
}
.footer-base a { color: #a2917f; }

/* Amy's way into her own settings.

   Two goes at this. First it was invisible and she could not find it; then
   it was a bordered button, which was findable but looked like a broken
   admin link on a customer's page. This is the middle: plain small text on
   the legal line, underlined on hover so it is obviously a link when you go
   looking. She knows it is there, and it is password gated regardless. */
.footer-admin { margin: .6rem 0 0; font-size: .6875rem; }
.footer-admin a {
  /* inline-block + padding so it is a real tap target. As bare 11px text it
     was about 9px tall — technically present, practically unhittable with a
     thumb, which is no use to the one person who needs it. */
  display: inline-block;
  padding: .7rem .9rem .7rem 0;
  color: #8d7a6b;
  text-decoration: none;
  letter-spacing: .06em;
}
.footer-admin a:hover,
.footer-admin a:focus-visible { color: #c3b3a6; text-decoration: underline; }

/* ==========================================================================
   AREAS COVERED
   ==========================================================================
   Her fifteen towns, set as a list of names rather than a paragraph. Names
   are scannable — somebody is looking for their own town and nothing else —
   and each one is a phrase people actually search for.
   ========================================================================== */

.areas {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: .1rem 2rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
.areas li {
  padding: .7rem 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--display);
  font-size: 1.15rem;
  color: var(--ink);
}

/* ==========================================================================
   HERO
   ==========================================================================
   One big photograph, with the words on a cream panel that overlaps its
   bottom edge — the way a magazine cover plate sits over an image. The
   overlap is the whole point: a hero with the text neatly beside the photo
   in two equal columns is the shape every template ships with.

   The photo is portrait (every one of Amy's is — they came off a phone), so
   on desktop it is cropped to a wide band and the panel hangs off it. On a
   phone the panel simply sits underneath, because overlapping panels on a
   375px screen just squash the words.
   ========================================================================== */

.hero { position: relative; padding-top: 0; }

.hero-figure { position: relative; margin: 0; background: var(--paper-warm); overflow: hidden; }
.hero-figure img {
  width: 100%;
  height: clamp(20rem, 58vh, 40rem);
  object-fit: cover;
  object-position: center 42%;
}
/* A soft wash at the foot of the image so the panel edge does not cut
   hard against a busy photograph. */
.hero-figure::after {
  content: '';
  position: absolute; inset: auto 0 0 0; height: 45%;
  background: linear-gradient(to bottom, transparent, rgba(245, 241, 238, .55));
  pointer-events: none;
}

.hero-panel {
  position: relative;
  background: var(--paper);
  padding: clamp(2rem, 1rem + 4vw, 3.5rem) 0 0;
}

.hero-eyebrow {
  display: flex; align-items: center; gap: .9rem;
  font-family: var(--body); font-size: .6875rem; font-weight: 500;
  letter-spacing: .28em; text-transform: uppercase; color: var(--mocha-text);
  margin: 0 0 1.2rem;
}
.hero-eyebrow::after { content: ''; flex: 1; height: 1px; background: var(--rule-strong); }

.hero h1 { margin-bottom: 1.4rem; }
.hero-script {
  display: block;
  font-family: var(--script);
  font-size: clamp(2.8rem, 1.4rem + 6vw, 6rem);
  color: var(--mocha-text);
  line-height: .9;
  margin-top: .1em;
}
.hero-lede { max-width: 46ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 2rem; }

/* The three points sit on a rule under the hero, as a strip of facts —
   not as ticked bullets, which reads as a feature list. */
.hero-facts {
  list-style: none; margin: clamp(2.5rem, 1.5rem + 3vw, 4rem) 0 0; padding: 1.4rem 0 0;
  border-top: 1px solid var(--rule-strong);
  display: grid; gap: 1.2rem;
}
@media (width >= 48rem) { .hero-facts { grid-template-columns: repeat(3, 1fr); gap: 2.5rem; } }
.hero-facts li { display: grid; gap: .3rem; }
.hero-facts b {
  font-family: var(--body); font-size: .6875rem; font-weight: 500;
  letter-spacing: .22em; text-transform: uppercase; color: var(--mocha-text);
}
.hero-facts span { font-size: .95rem; color: var(--ink-soft); line-height: 1.45; }

@media (width >= 62rem) {
  .hero-inner { position: relative; }
  .hero-panel {
    margin-top: clamp(-9rem, -6vw, -4rem);
    padding: clamp(2.5rem, 1.5rem + 3vw, 4rem) clamp(2.5rem, 2rem + 3vw, 4.5rem) 0;
    max-width: 54rem;
  }
  .hero-panel::before {
    /* Her monogram, very faint, behind the headline — a watermark on the
       page rather than a logo stuck in a corner. */
    content: '';
    position: absolute; right: 1.5rem; top: 1.5rem;
    width: 11rem; height: 11rem;
    background: url('../photos/monogram-512.png') no-repeat center / contain;
    opacity: .06;
    pointer-events: none;
  }
}

/* ==========================================================================
   THE INDEX  (services)
   ==========================================================================
   A contents page, not a row of cards. Number in the margin, name set large
   in the serif, description alongside, a hairline between each. This is the
   single biggest departure from the template look, and it also happens to
   suit four items far better than a four-across grid does on a phone.
   ========================================================================== */

.index { border-top: 1px solid var(--rule-strong); }

.index-item {
  display: grid;
  gap: .4rem 1.5rem;
  grid-template-columns: auto 1fr;
  align-items: start;
  padding: clamp(1.6rem, 1rem + 2vw, 2.6rem) 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: background-color .35s var(--ease);
}
.index-item:hover { background: var(--paper-warm); }
.index-item:hover .index-name { color: var(--mocha-text); }

.index-no {
  font-family: var(--display);
  font-size: .875rem;
  color: var(--mocha);
  letter-spacing: .1em;
  padding-top: .55em;
  font-variant-numeric: tabular-nums;
}
.index-name {
  font-family: var(--display);
  font-size: clamp(1.5rem, 1.1rem + 2vw, 2.4rem);
  font-weight: 500;
  line-height: 1.05;
  transition: color .3s;
}
.index-body { grid-column: 2; display: grid; gap: .8rem; }
.index-blurb { color: var(--ink-soft); max-width: 58ch; margin: 0; font-size: 1rem; }
.index-more {
  font-family: var(--body); font-size: .7rem; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; color: var(--mocha-text);
}

/* A thumbnail on the right on wide screens. Portrait, narrow, more like a
   plate in a book than a card image. */
.index-thumb { display: none; }
@media (width >= 62rem) {
  .index-item { grid-template-columns: auto 1fr 12rem; align-items: center; }
  .index-body { grid-column: 2; }
  .index-thumb {
    display: block; grid-column: 3; grid-row: 1 / -1;
    margin: 0; overflow: hidden; background: var(--paper-warm);
  }
  .index-thumb img {
    width: 100%; aspect-ratio: 4/5; object-fit: cover;
    filter: saturate(.9);
    transition: transform .8s var(--ease), filter .5s;
  }
  .index-item:hover .index-thumb img { transform: scale(1.05); filter: saturate(1); }
}

/* ==========================================================================
   THE CLEAN LISTS
   ==========================================================================
   Her flyers list every task by room. That list IS the product — it is what
   a customer is buying and what she is promising — so it gets proper space
   rather than being hidden behind "and more".

   Set as columns of a printed specification: a small caps room heading over
   a rule, then the tasks. No boxes. It reads as a document, which is
   exactly what it is.
   ========================================================================== */

.rooms {
  display: grid;
  gap: clamp(2rem, 1rem + 3vw, 3.2rem) clamp(2rem, 1rem + 3vw, 4rem);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.room > h3 {
  margin: 0 0 1rem;
  padding-bottom: .6rem;
  border-bottom: 1px solid var(--rule-strong);
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--mocha-text);
}
.room ul { list-style: none; margin: 0; padding: 0; }
.room li {
  position: relative;
  padding-left: 1.5rem;
  margin-bottom: .6rem;
  font-size: .9375rem;
  line-height: 1.5;
  color: var(--ink-soft);
}
.room li:last-child { margin-bottom: 0; }
/* A drawn tick, so the list reads as "included" rather than as bullet points */
.room li::before {
  content: '';
  position: absolute;
  left: 0;
  top: .42em;
  width: .68rem;
  height: .36rem;
  border-left: 1.6px solid var(--mocha);
  border-bottom: 1.6px solid var(--mocha);
  transform: rotate(-45deg);
}

/* ==========================================================================
   THE TARIFF
   ==========================================================================
   Prices set as a price card, with dot leaders running from the name to the
   figure. It is the oldest device in printed price lists and it does two
   useful things here: it makes a short list look considered, and it makes
   "Price on request" sit comfortably in the same column as a number, so the
   gaps in Amy's pricing do not look like broken rows.
   ========================================================================== */

.tariff { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule-strong); }
.tariff li {
  display: flex; align-items: baseline; gap: .75rem;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}
.tariff-name { font-size: 1.0625rem; color: var(--ink); }
.tariff-dots {
  flex: 1 1 2rem;
  border-bottom: 1px dotted var(--rule-strong);
  transform: translateY(-.3em);
  min-width: 1.5rem;
}
.tariff-price {
  font-family: var(--display);
  font-size: 1.35rem;
  color: var(--mocha-deep);
  font-variant-numeric: tabular-nums;
}
.tariff-ask {
  font-family: var(--body); font-size: .8125rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-faint);
}
.tariff-note { flex-basis: 100%; font-size: .875rem; color: var(--ink-faint); margin: .2rem 0 0; }

/* The headline rate, set as a specimen. */
.rate { display: grid; gap: .3rem; }
.rate-figure {
  font-family: var(--display);
  font-size: clamp(3.5rem, 2rem + 8vw, 7rem);
  line-height: .9;
  color: var(--espresso);
  font-variant-numeric: tabular-nums;
}
.rate-unit {
  font-family: var(--body); font-size: .75rem; letter-spacing: .24em;
  text-transform: uppercase; color: var(--ink-faint);
}
.rate-when { font-size: .9375rem; color: var(--ink-soft); margin-top: .5rem; }

/* ==========================================================================
   THE ARGUMENT  (her "luxury service" flyer)
   ==========================================================================
   Her own words, set as an editorial spread with a hanging quote mark. She
   wrote something genuinely persuasive; the design's only job is to make it
   feel like it was worth writing.
   ========================================================================== */

.argument { position: relative; }
.argument::before {
  content: '\201C';
  position: absolute;
  left: -.08em; top: -.42em;
  font-family: var(--display);
  font-size: clamp(7rem, 4rem + 12vw, 15rem);
  line-height: 1;
  color: var(--mocha);
  opacity: .28;
  pointer-events: none;
}
.argument-body { position: relative; display: grid; gap: 1.4rem; }
.argument-body p {
  font-family: var(--display);
  font-size: clamp(1.15rem, 1rem + .8vw, 1.55rem);
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}
/* The opening line carries the weight; the rest support it. */
.argument-body p:first-child { font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2.1rem); line-height: 1.35; }
.argument-close {
  margin-top: 2.4rem; padding-top: 1.6rem;
  border-top: 1px solid var(--rule-strong);
  font-family: var(--script);
  font-size: clamp(2rem, 1.3rem + 2.6vw, 3.2rem);
  line-height: 1.1;
  color: var(--mocha-text);
}

/* ==========================================================================
   THE STEPS
   ==========================================================================
   A sequence separated by rules, with the number set small in the margin.
   Numbers in filled circles are the most-copied component on the web and
   the fastest way to look like everyone else.
   ========================================================================== */

.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; border-top: 1px solid var(--rule-strong); }
.steps li {
  counter-increment: step;
  display: grid;
  gap: .3rem 1.4rem;
  grid-template-columns: 2.5rem 1fr;
  padding: clamp(1.4rem, 1rem + 1.5vw, 2.2rem) 0;
  border-bottom: 1px solid var(--rule);
}
.steps li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--display);
  font-size: .875rem;
  color: var(--mocha);
  letter-spacing: .1em;
  padding-top: .35em;
  font-variant-numeric: tabular-nums;
}
.steps h3 { margin: 0 0 .4rem; }
.steps p { grid-column: 2; color: var(--ink-soft); margin: 0; max-width: 56ch; font-size: 1rem; }

/* ==========================================================================
   BEFORE / AFTER
   ==========================================================================
   The best thing on the site — the same room, two states, one handle.

   ── Why it is built on a range input ──────────────────────────────────
   The obvious build is a div you drag with pointer events. That gives you
   something no keyboard can operate and no screen reader can describe. An
   <input type="range"> gets dragging, arrow keys, Home/End, touch, and a
   proper announced value for free — so it is a real slider styled to look
   like a handle, rather than a handle pretending to be a slider.

   Both images are always in the DOM at full opacity; only a clip-path
   moves, so dragging costs no layout and stays smooth on a cheap phone.
   ========================================================================== */

.ba-grid { display: grid; gap: clamp(2.5rem, 1.5rem + 4vw, 4.5rem); }
@media (width >= 60rem) { .ba-grid { grid-template-columns: 1fr 1fr; } }

.ba { margin: 0; }
.ba-stage {
  position: relative;
  overflow: hidden;
  background: var(--paper-warm);
  aspect-ratio: 3 / 4;
  isolation: isolate;
  cursor: ew-resize;

  /* Hands vertical swipes back to the browser so the page still scrolls
     when a thumb happens to land on a photo, while horizontal movement
     comes through to the drag handler. Without this the slider ate every
     scroll gesture that started over an image. */
  touch-action: pan-y;

  /* A drag across the stage must not start selecting the caption text. */
  -webkit-user-select: none;
  user-select: none;
}
.ba-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The before image is clipped to the left of the split; the after sits
   underneath, so what you see to the right is always the finished room. */
.ba-before { clip-path: inset(0 calc(100% - var(--split)) 0 0); }

.ba-tag {
  position: absolute; top: 1rem; z-index: 3;
  font-family: var(--body); font-size: .625rem; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase;
  padding: .4rem .7rem;
  background: rgba(255, 253, 251, .92);
  color: var(--ink);
  pointer-events: none;
}
.ba-tag-before { left: 1rem; }
.ba-tag-after { right: 1rem; }

.ba-handle {
  position: absolute; top: 0; bottom: 0; z-index: 4;
  left: var(--split);
  width: 2px; margin-left: -1px;
  background: rgba(255, 253, 251, .95);
  pointer-events: none;
}
.ba-grip {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 2.9rem; height: 2.9rem;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 50%;
  display: grid; place-items: center;
}
/* Two chevrons drawn with borders — no icon font, no SVG request. */
.ba-grip::before, .ba-grip::after {
  content: '';
  position: absolute;
  width: .42rem; height: .42rem;
  border-top: 1.5px solid var(--mocha-text);
  border-right: 1.5px solid var(--mocha-text);
}
.ba-grip::before { transform: rotate(-135deg) translate(-.2rem, -.2rem); }
.ba-grip::after  { transform: rotate(45deg) translate(-.2rem, -.2rem); }

/* The real control — kept for the keyboard and for screen readers, which is
   the whole reason this is an <input type="range"> and not a bare div.

   POINTER-EVENTS: NONE is the important line. The input used to sit over the
   entire photo and take the touches itself, which brought the browser's
   native "tap the track and the thumb jumps there" behaviour with it. That
   is right for a volume slider and wrong for this: on a phone every tap on
   the picture snapped the wipe across. Pointers are handled on the stage
   now (see site.js); the input still receives focus and key presses. */
.ba-range {
  position: absolute; inset: 0; z-index: 5;
  width: 100%; height: 100%; margin: 0;
  opacity: 0;
  pointer-events: none;
  -webkit-appearance: none; appearance: none; background: none;
}
/* Focus has to be shown on the handle, since the input itself is invisible. */
.ba-range:focus-visible { outline: none; }
.ba-range:focus-visible ~ .ba-handle .ba-grip { outline: 3px solid var(--mocha-deep); outline-offset: 3px; }

.ba figcaption {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--rule);
  font-size: .9375rem;
  color: var(--ink-soft);
}
.ba-hint {
  margin-top: 2rem;
  font-size: .8125rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ==========================================================================
   GALLERY
   ==========================================================================
   Deliberately uneven. Every photo is the same shape and the same size in a
   template grid; a printed portfolio varies the plate size to give the page
   a rhythm, and it stops ten portrait phone photos reading as a contact
   sheet.
   ========================================================================== */

.gallery { display: grid; gap: clamp(.6rem, .3rem + 1vw, 1.1rem); grid-template-columns: repeat(2, 1fr); }
@media (width >= 48rem) { .gallery { grid-template-columns: repeat(3, 1fr); } }
@media (width >= 75rem) { .gallery { grid-template-columns: repeat(4, 1fr); } }

.shot { margin: 0; overflow: hidden; background: var(--paper-warm); }
.shot img {
  width: 100%; height: 100%; object-fit: cover;
  aspect-ratio: 3/4;
  transition: transform .9s var(--ease);
}
.shot:hover img { transform: scale(1.04); }
/* Every fourth and seventh plate runs tall, so the grid never settles into
   a repeating block. */
@media (width >= 48rem) {
  .shot:nth-child(4n + 1) { grid-row: span 2; }
  .shot:nth-child(4n + 1) img { aspect-ratio: 3/5; }
}

/* ==========================================================================
   FAQ
   ==========================================================================
   Real <details> elements: they work with JavaScript off, are keyboard
   operable for free, and modern browsers can find text inside a closed one.
   ========================================================================== */

.faq { border-top: 1px solid var(--rule-strong); }
.faq-item { border-bottom: 1px solid var(--rule); }
.faq-item summary {
  display: flex; align-items: baseline; gap: 1rem;
  padding: 1.4rem 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--display);
  font-size: clamp(1.1rem, 1rem + .5vw, 1.35rem);
  color: var(--ink);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '';
  flex: none; margin-left: auto;
  width: .55rem; height: .55rem;
  border-right: 1.5px solid var(--mocha-text);
  border-bottom: 1.5px solid var(--mocha-text);
  transform: rotate(45deg) translateY(-.15rem);
  transition: transform .3s var(--ease);
}
.faq-item[open] summary::after { transform: rotate(-135deg) translateY(-.15rem); }
.faq-item summary:hover { color: var(--mocha-text); }
.faq-answer { padding: 0 0 1.5rem; color: var(--ink-soft); max-width: 62ch; }

/* ==========================================================================
   FORMS
   ========================================================================== */

.field { margin-bottom: 1.35rem; }
.field > label, .field > legend {
  display: block;
  margin-bottom: .5rem;
  padding: 0;
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--mocha-text);
}
.field .hint {
  display: block;
  margin-top: .4rem;
  font-size: .8125rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-faint);
}
.hint-inline { font-size: .75rem; color: var(--ink-faint); text-transform: none; letter-spacing: 0; }
.field input, .field select, .field textarea {
  width: 100%;
  padding: .9em 1em;
  font-family: var(--body);
  font-size: 1rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-field);
  transition: border-color .2s, box-shadow .2s;
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--mocha-text);
  box-shadow: 0 0 0 3px var(--mocha-pale);
  outline: none;
}
.field fieldset { border: 0; margin: 0; padding: 0; }
.req { color: var(--bad); }
.two-up { display: grid; gap: 0 1.1rem; }
@media (width >= 40rem) { .two-up { grid-template-columns: 1fr 1fr; } }

/* Chunky, thumb-friendly choices — most people will fill this in on a phone,
   one-handed, probably standing in the room they want cleaned. */
.choices { display: grid; gap: .5rem; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }
.choices label {
  position: relative;
  display: block;
  padding: .9rem 1rem;
  background: var(--card);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-field);
  font-size: .9375rem;
  cursor: pointer;
  transition: border-color .2s, background-color .2s;
}
.choices input { position: absolute; opacity: 0; pointer-events: none; }
.choices label:has(input:checked) {
  border-color: var(--mocha-text);
  background: var(--mocha-pale);
  font-weight: 500;
}
.choices label:hover { border-color: var(--mocha); }
.choices input:focus-visible + span { outline: 3px solid var(--mocha-deep); outline-offset: 4px; }

.enquiry {
  background: var(--card);
  border: 1px solid var(--rule);
  padding: clamp(1.4rem, 1rem + 2vw, 2.6rem);
}

.form-status { padding: 1rem 1.2rem; font-size: .9375rem; border: 1px solid; margin-bottom: 1rem; }
.form-status.is-good { background: var(--good-pale); border-color: var(--good); color: #1e5a41; }
.form-status.is-bad  { background: var(--bad-pale);  border-color: var(--bad);  color: #7d281f; }
.form-status.is-working { background: var(--paper-warm); border-color: var(--rule-strong); color: var(--ink-soft); }
.form-small { font-size: .8125rem; color: var(--ink-faint); margin-top: .9rem; text-align: center; }

.notice {
  border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--mocha-text);
  background: var(--paper-warm);
  padding: 1.1rem 1.3rem;
  font-size: .9375rem;
  color: var(--ink-soft);
}
.notice strong { color: var(--ink); }

/* ==========================================================================
   BOOKING ROUTE
   ==========================================================================
   The three stages, stated above the form. Booking a cleaner is not a
   checkout — there is a quote in the middle — and saying so before someone
   starts typing stops them expecting to pay on this page.
   ========================================================================== */

.booking-route {
  list-style: none; margin: 0 0 2.5rem; padding: 1.4rem 0 0;
  border-top: 1px solid var(--rule-strong);
  display: grid; gap: 1rem;
}
@media (width >= 44rem) { .booking-route { grid-template-columns: repeat(3, 1fr); gap: 1.5rem; } }
.booking-route li { display: flex; align-items: baseline; gap: .7rem; }
.booking-route b {
  font-family: var(--display); font-size: .8125rem; color: var(--mocha);
  font-variant-numeric: tabular-nums; flex: none;
}
.booking-route span { font-size: .9375rem; color: var(--ink-soft); line-height: 1.4; }

/* Honeypot. Hidden from people, left in the DOM for bots to fill in.
   Not display:none — some bots skip anything that is display:none, and some
   screen readers would still announce it. Pushed off-screen and taken out of
   the tab order instead, with aria-hidden on the wrapper in the markup. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ==========================================================================
   ABOUT
   ==========================================================================
   Words and a single tall plate, at uneven widths. Two equal columns is the
   template arrangement and it makes the photo compete with the text.
   ========================================================================== */

.about-grid { display: grid; gap: clamp(2rem, 1rem + 4vw, 4rem); }
@media (width >= 62rem) {
  .about-grid { grid-template-columns: 1.25fr 1fr; align-items: start; }
}
.about-figure { margin: 0; overflow: hidden; background: var(--paper-warm); }
.about-figure img { width: 100%; aspect-ratio: 4/5; object-fit: cover; }

/* ==========================================================================
   DIRECT CONTACT / MISC
   ========================================================================== */

.direct-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; justify-content: center; }
.direct-list a { font-family: var(--display); font-size: 1.3rem; }

.rules-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
.rules-list li { position: relative; padding-left: 1.8rem; color: var(--ink-soft); }
.rules-list li::before {
  content: ''; position: absolute; left: 0; top: .55em;
  width: .5rem; height: .5rem; border: 1px solid var(--mocha); border-radius: 50%;
}

.cancel-table { width: 100%; border-collapse: collapse; margin-top: 1.5rem; }
.cancel-table th, .cancel-table td { text-align: left; padding: 1rem 0; border-bottom: 1px solid var(--rule); }
.cancel-table th {
  font-family: var(--body); font-size: .6875rem; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase; color: var(--mocha-text);
  border-bottom-color: var(--rule-strong);
}
.cancel-table td:last-child { text-align: right; font-family: var(--display); font-size: 1.15rem; color: var(--mocha-deep); }

/* ==========================================================================
   MOBILE CALL BAR
   ==========================================================================
   A trades business lives on phone calls. On a phone, once somebody has
   scrolled past the header there is nothing to tap — this puts calling and
   enquiring permanently one thumb away.

   Manners: it stays out of the way until you are past the first screen, it
   never appears on the enquiry page (which already ends in an action), and
   it is a bar rather than a pop-up so nothing is covered but a strip.
   ========================================================================== */

/* Height of the bar, so the footer can leave room for it. Kept here beside
   the thing it measures — a magic number in the footer rules would silently
   go wrong the moment this padding or font size changed.
   1.05rem padding top + bottom, plus roughly 1.2rem of line box. */
:root { --callbar-h: 3.3rem; }

.callbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  display: grid; grid-template-columns: 1fr 1fr;
  background: var(--paper);
  border-top: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-float);
  transform: translateY(110%);
  transition: transform .4s var(--ease);
  padding-bottom: env(safe-area-inset-bottom);
}
.callbar.is-up { transform: none; }
.callbar a {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  padding: 1.05rem .5rem;
  font-family: var(--body); font-size: .75rem; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; text-decoration: none;
}
.callbar-call { color: var(--ink); }
.callbar-quote { background: var(--espresso); color: #fffdfb; }
@media (width >= 60rem) { .callbar { display: none; } }

/* ==========================================================================
   REVEAL ON SCROLL
   ==========================================================================
   Applied by JavaScript only, and only where IntersectionObserver exists, so
   content is never hidden by an effect that cannot un-hide it.
   ========================================================================== */

.reveal { opacity: 0; transform: translateY(14px); }
.reveal-in {
  opacity: 1;
  transform: none;
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}

/* ==========================================================================
   REDUCED MOTION
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ==========================================================================
   PRINT
   ==========================================================================
   Her clean lists are the thing people actually print — a customer sticks
   one on the fridge, and Amy hands them out. So they print properly: no
   navigation, no call bar, no dark panels eating a cartridge.
   ========================================================================== */

@media print {
  .no-print, .site-header, .site-footer, .callbar, .hero-figure,
  .btn, .ba-range, .ba-handle { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .section { padding-block: 1rem; }
  .section-tint, .section-dark { background: #fff !important; color: #000 !important; }
  .rooms { grid-template-columns: repeat(2, 1fr); }
  .room { break-inside: avoid; }
  a { text-decoration: none; color: #000; }
}
