:root {
  /**
   * THE SALON'S PALETTE. Sonia, 31 August 2026: "the red we are using for the booking is not
   * on brand."
   *
   * It was var(--merlot) — a bright pillar-box red, not the brand's merlot. The SALON DESK was
   * already correct (public/admin.css has used #5A2435 since it was built), so the two halves
   * of the same product had been showing clients and staff different reds.
   *
   * Every value below is from the brand list. Nothing here is invented, and the hardcoded
   * hexes that used to sit in the week-picker and slot rules now reference these instead, so
   * a future colour change is one line rather than eleven.
   */
  --merlot: #5A2435;
  --merlot-dark: #38121F;
  /**
   * THE PINKS, read off https://toucheeorganicsalon.com/assets/css/touchee.css so they are the
   * salon's actual values, not something approximated by eye.
   *   --pink        #B88490  the underline under every "Book" on the service menu
   *   --accent      #B0304A  what that underline turns on hover — the site's live accent
   *   --merlot-100  #EBDDE1  a pale wash, one step warmer than --tint
   */
  --accent: #B0304A;
  --pink: #B88490;
  --merlot-100: #EBDDE1;
  --oyster: #EEEAE4;
  --paper: #FAF8F4;
  --white-warm: #FDFBF8;      /* off-white; true #fff is not in the palette */
  --tint: #F4ECEF;            /* the palest merlot wash, for a chosen row */
  --ink: #2C2A28;
  /**
   * The salon site's own button vocabulary, read off toucheeorganicsalon.com on
   * 1 September 2026 rather than guessed at.
   *
   * --hot #9E2340 is the colour a Touchée button turns when you are on it, and the colour of
   * the shade behind every CTA. It is NOT in the palette Sonia sent earlier — it only exists on
   * the live site — but it is unmistakably hers, and without it the buttons are a near-miss.
   *
   * --mono is the bigger of the two. The uppercase letter-spaced mono is what makes a button
   * read as Touchée; the corners and the fill are secondary.
   */
  --hot: #9E2340;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --muted: #766D69;
  /* A darker sibling of --muted, for text sitting on --oyster rather than on the page.
     --muted on oyster measures 4.21:1, under AA; this measures 5.29:1. Used by disabled
     buttons, where the whole point is that the label stays readable. */
  --muted-strong: #665E5A;
  --line: #E5E0D8;

  /**
   * THE ONLY COLOURS HERE THAT ARE NOT ON THE BRAND LIST, AND WHY.
   *
   * A caution must not be the brand colour. If "this needs a moment of attention" is rendered
   * in merlot, it reads as decoration and is not seen; the whole point of a warning is that it
   * does not match. The same argument is why the Salon Desk keeps its own amber.
   *
   * --muted is the mid-grey for secondary text. The brand list has no mid-tone between #2C2A28
   * and #E5E0D8, and body text needs one that clears 4.5:1 on paper. #766D69 measures 4.76.
   * Worth agreeing deliberately rather than leaving it invented here.
   */
  --warn-ink: #50451C;
  --warn-bg: #FBF3CC;
  --warn-line: #E7D891;
  --warn-edge: #B49A42;
  --butter: #f0d979;
  --sage: #a8ad9f;
  --white: #FDFBF8;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
body { margin: 0; color: var(--ink); background: var(--oyster); }
/* Buttons keep `font: inherit` so they match the page. Inputs deliberately do NOT: inheriting
   pulled every field down to its label's 13px, which is what made iOS zoom the page on focus.
   They get an explicit 16px further down — see the note there. */
button { font: inherit; cursor: pointer; }
.brandbar { height: 76px; display: flex; align-items: center; justify-content: space-between; padding: 0 5vw; border-bottom: 1px solid rgba(38,35,35,.12); background: rgba(243,239,232,.94); position: sticky; top: 0; z-index: 10; backdrop-filter: blur(12px); }
.brand { font-family: Georgia, 'Times New Roman', serif; font-style: italic; font-size: 30px; color: var(--merlot); text-decoration: none; }
.location { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
/**
 * WELCOME ON TOP, FORM AND SUMMARY BELOW IT.
 *
 * The columns used to be [intro | form]. The sentence explaining the page sat in the narrow
 * column, and the form — the part with photographs in it — got the rest. Now the welcome spans
 * the full width and the form takes the wide column.
 *
 * `has-summary` is added by app.js when the appointment card appears. Without it the form spans
 * both columns, because an empty 260px column beside the service list reads as a broken page,
 * and the card is hidden until a service is chosen. Done with a class rather than :has() so it
 * behaves the same on an older iPad, which is what the salon actually uses.
 */
.booking-shell { max-width: 1180px; margin: 0 auto; padding: 56px 24px 80px; display: grid;
  grid-template-columns: minmax(0, 1fr); gap: 0 54px; }
.booking-shell.has-summary { grid-template-columns: minmax(520px, 1.25fr) minmax(260px, .75fr); }
.booking-intro { grid-column: 1 / -1; padding: 0 4px 26px; max-width: 68ch; }
.booking-side { display: none; }
.booking-shell.has-summary .booking-side { display: block; }
.eyebrow, .step-kicker { margin: 0 0 12px; text-transform: uppercase; letter-spacing: .13em; font-size: 11px; font-weight: 700; color: var(--merlot); }
h1, h2 { font-family: Georgia, 'Times New Roman', serif; font-weight: 400; line-height: 1.03; margin: 0; }
/**
 * The page has exactly one h1 and it is the welcome. It used to be up to 68px because it lived
 * in a narrow column where it read as a column heading; full width across the top on
 * 1 September 2026 that size filled the entire first screen and pushed the service list below
 * the fold — the client had to scroll before seeing anything she could book.
 *
 * It is a banner now, not a hero. The working heading on each step is the h2 inside the card,
 * and this must not outshout it.
 */
h1 { font-size: clamp(28px, 3vw, 40px); max-width: 22ch; line-height: 1.15; }
h2 { font-size: clamp(30px, 4vw, 44px); }
.lede, .step-copy { color: var(--muted); line-height: 1.65; font-size: 16px; }
.lede { max-width: 60ch; margin-top: 12px; }
.booking-card { background: var(--paper); border: 1px solid var(--line); border-radius: 26px; padding: 34px; min-height: 620px; box-shadow: 0 18px 50px rgba(48, 34, 34, .08); }
.progress { display: flex; align-items: center; margin-bottom: 38px; }
.progress-dot { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; border: 1px solid var(--line); color: var(--muted); font-size: 12px; background: var(--paper); }
.progress-dot.active, .progress-dot.done { background: var(--merlot); border-color: var(--merlot); color: white; }
.progress-line { height: 1px; background: var(--line); flex: 1; }
.step { display: none; }
.step.active { display: block; animation: rise .22s ease both; }
@keyframes rise { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
.service-list { display: grid; gap: 10px; margin-top: 28px; }
.service-option { width: 100%; text-align: left; border: 1px solid var(--line); background: #fff; border-radius: 16px; padding: 18px; display: grid; grid-template-columns: 1fr auto; gap: 8px 16px; transition: .16s ease; color: var(--ink); }
.service-option:hover, .service-option.selected { border-color: var(--merlot); transform: translateY(-1px); box-shadow: 0 7px 20px rgba(90,36,53,.10); }
.service-option.selected { background: var(--tint); }
.service-option strong { font-family: Georgia, 'Times New Roman', serif; font-size: 20px; font-weight: 400; }
.service-option p { margin: 0; color: var(--muted); line-height: 1.45; grid-column: 1 / -1; }
.service-meta { color: var(--merlot); font-size: 13px; white-space: nowrap; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 28px; }
label { display: grid; gap: 8px; font-size: 13px; font-weight: 650; }
label.wide { grid-column: 1 / -1; }
/**
 * 16px MINIMUM ON EVERY FIELD SHE TYPES INTO. NOT A STYLE CHOICE.
 *
 * FOUND BY PRESSURE TEST, 31 August 2026. `font: inherit` meant every input inherited its
 * label's 13px, and iOS Safari ZOOMS THE PAGE when a focused input is under 16px — then does
 * not zoom back out. She taps her name, the page lurches in, and she finishes the booking
 * scrolling sideways to reach the fields. On the confirm step, which is the last thing between
 * her and a booked appointment.
 *
 * The label stays 13px; only the control is raised.
 */
/**
 * TEXT FIELDS ONLY — NOT CHECKBOXES.
 *
 * FOUND FROM A SCREENSHOT, 31 August 2026. Sonia: "the anything to add is cut off."
 *
 * This rule said `input`, which includes checkboxes. So every checkbox was given
 * `width: 100%`, a 12px padding and a 12px radius — and in the add-on rows, which are flex,
 * a checkbox 100% of the row wide pushed the label off to the right edge, where "Blow Dry ·
 * 45 min · +$55" wrapped one or two words per line and ran off the bottom.
 *
 * It looked like a text-overflow problem. It was a checkbox eating the row.
 *
 * The same rule hit the booking-terms box and the marketing-consent box, which is why those
 * sat oddly too. admin.css already had this reset; this stylesheet never did.
 */
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  width: 100%;
  font-size: 16px;
  font-family: inherit;
  border: 1px solid var(--line); background: white; border-radius: 12px;
  padding: 12px 13px; color: var(--ink); outline: none;
}
input:focus, select:focus, textarea:focus { border-color: var(--merlot); box-shadow: 0 0 0 3px rgba(90,36,53,.10); }
textarea { resize: vertical; }
.actions { display: flex; justify-content: space-between; gap: 12px; margin-top: 30px; }
/**
 * BUTTONS THAT LOOK LIKE BUTTONS.
 *
 * Sonia, 31 August 2026: "the button needs to have a wider border to feel more like buttons."
 *
 * They were a 1px hairline with 13px padding — on a phone, against a cream background, that
 * reads as a link someone has drawn a box around. A client who is not sure what is tappable
 * hesitates, and hesitation on the last screen of a booking is the expensive kind.
 *
 * 2px border, more padding, heavier label. Deliberately NOT a colour change: the merlot is the
 * salon's, and the fix is weight and edge, not shouting.
 *
 * The height also matters. 15px of vertical padding plus a 16px line box clears the 44px
 * minimum tap target on iOS, which the old 13px did not reliably do.
 */
/**
 * BUILT FROM THE SALON'S OWN BUTTONS. Sonia, 1 September 2026: "can you design the buttons more
 * like these," with screenshots of toucheeorganicsalon.com.
 *
 * Taken from the real rules rather than the screenshots — `.tsd-tab` and the blow-dry CTAs:
 *
 *     border-radius: 0            sharp, not the 2px I had
 *     border: 2px solid
 *     box-shadow: 3px 3px 0       an offset block, which the site already did
 *     transform on :active        pressing moves onto the shade, which it also already did
 *     IBM Plex Mono, uppercase, letter-spaced
 *
 * TWO THINGS I HAD BACKWARDS.
 *
 * The shade behind is HOTTER, not deeper — rgba(158,35,64,.45), a brighter red under merlot.
 * I had reasoned my way to a darker one, and her site had already answered the question.
 *
 * And the type is the whole thing. Sentence-case Inter at 17px was never going to look like her
 * site no matter what I did to the corners.
 *
 * ONE DELIBERATE DEPARTURE: her CTAs are 11–12px. Here they are 13px. This is the button an
 * older client presses to commit to an appointment, on a phone, possibly outdoors — and the
 * salon's owner has poor eyesight. The character survives two points; certainty does not.
 */
.primary, .ghost {
  border-radius: 0;
  border: 2px solid var(--merlot);
  padding: 15px 26px;
  min-height: 52px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  box-shadow: 3px 3px 0 rgba(158, 35, 64, .45);
  transition: background .18s ease, color .18s ease, border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.primary { margin-left: auto; background: var(--merlot); color: var(--white-warm); }

/* The quieter one, exactly as the site does its tabs: no fill, carried by edge and shade. */
.ghost { background: transparent; color: var(--merlot); border-color: rgba(90, 36, 53, .55); }
.ghost:hover, .ghost:focus-visible {
  border-color: var(--hot); color: var(--hot); background: transparent;
  box-shadow: 3px 3px 0 rgba(158, 35, 64, .28);
}

/* Onto its own shade, the way her buttons do. */
.primary:active, .ghost:active { transform: translate(3px, 3px); box-shadow: none; }

.primary:focus-visible, .ghost:focus-visible, .slot:focus-visible, .feature-jump:focus-visible {
  outline: 2px solid var(--hot);
  outline-offset: 2px;
}

.primary:disabled, .ghost:disabled {
  opacity: 1;
  cursor: not-allowed;
  background: var(--oyster);
  color: var(--muted-strong);   /* 5.29:1 on oyster. --muted gave 4.21, which is under AA. */
  border-color: var(--line);
  box-shadow: none;
  transform: none;
}
/* A FILL, not transparency — this is the button that was disappearing. */
/* The quieter of the two: same rectangle and the same deep edge, a pale fill instead of a
   solid one. Never transparent — that is what made it vanish on a cream page. */
.ghost { background: var(--tint); color: var(--merlot-dark); }
.ghost:hover, .ghost:focus-visible { background: var(--merlot-100); color: var(--merlot-dark); }
.date-label { max-width: 240px; margin-top: 24px; }
.slots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 18px; }
/* Time slots are buttons too, and they are what she is choosing between. Same treatment,
   one step quieter — a grid of 2px merlot boxes would be louder than the thing that confirms. */
/**
 * Times, in the same language one step quieter. Mono suits a clock anyway — the digits line up
 * in a column instead of drifting, which is the whole reason tabular figures exist.
 *
 * Kept at 15px rather than the site's 11.5px: a client scanning twenty times on a phone is
 * reading, not admiring.
 */
.slot { border: 2px solid rgba(90, 36, 53, .55); background: transparent; padding: 14px 12px;
        border-radius: 0; font-family: var(--mono); font-size: 15px; font-weight: 400;
        letter-spacing: .08em; text-transform: uppercase; min-height: 48px; color: var(--merlot);
        box-shadow: 3px 3px 0 rgba(90, 36, 53, .20);
        transition: transform .18s ease, box-shadow .18s ease, background .18s ease; }
.slot:active { transform: translate(3px, 3px); box-shadow: none; }
.slot:hover, .slot.selected { border-color: var(--merlot); color: var(--merlot); background: var(--tint); }
/* The chosen time should be unmistakable — she is about to confirm it. */
.slot.selected { background: var(--hot); border-color: var(--hot); color: #FFFFFF;
                 box-shadow: 3px 3px 0 rgba(90, 36, 53, .28); }
.muted { color: var(--muted); font-size: 13px; line-height: 1.5; margin-top: 14px; }
.summary-card { margin-top: 34px; border-left: 3px solid var(--merlot); padding: 16px 18px; background: rgba(255,255,255,.48); display: grid; gap: 7px; }
.summary-label { text-transform: uppercase; letter-spacing: .12em; font-size: 10px; color: var(--muted); }
.summary-card strong { font-family: Georgia, serif; font-size: 20px; font-weight: 400; }
.summary-card span:not(.summary-label) { color: var(--muted); font-size: 13px; }
.deposit-box { margin: 24px 0 18px; padding: 18px; border: 1px solid var(--line); border-radius: 15px; background: white; display: grid; grid-template-columns: 1fr auto; gap: 5px 15px; }
.deposit-box strong { font-family: Georgia, serif; color: var(--merlot); font-size: 24px; }
.deposit-box small { grid-column: 1 / -1; color: var(--muted); }
#card-container { min-height: 90px; }
.consent { display: flex; align-items: flex-start; gap: 10px; font-weight: 450; line-height: 1.4; margin-top: 12px; }
.consent input { width: auto; margin-top: 2px; }
.alert { border-radius: 12px; padding: 12px 14px; margin: 0 0 18px; font-size: 13px; line-height: 1.45; }
.alert.error { color: var(--merlot-dark); background: var(--tint); border: 1px solid var(--merlot); }
.alert.notice { color: var(--warn-ink); background: var(--warn-bg); border: 1px solid var(--warn-line); }
.success-step { text-align: center; padding: 42px 6px; }
.success-mark { width: 58px; height: 58px; border-radius: 50%; display: grid; place-items: center; margin: 0 auto 18px; background: var(--merlot); color: white; font-size: 28px; }
.confirmation-card { max-width: 430px; margin: 28px auto; padding: 22px; background: white; border: 1px solid var(--line); border-radius: 16px; display: grid; gap: 8px; }
.confirmation-card strong { font-family: Georgia, serif; font-size: 22px; font-weight: 400; }
.confirmation-card span { color: var(--muted); }
@media (max-width: 860px) {
  /* One column on a phone, summary above the form where it is read first. */
  .booking-shell, .booking-shell.has-summary { grid-template-columns: 1fr; gap: 18px; padding-top: 24px; }
  .booking-intro { padding: 0 2px 16px; }
  /**
   * Explicit order, all three. `order: -1` on the side alone outranked the welcome as well as
   * the form, so on a phone the appointment card leapt above the page heading the moment a
   * service was picked. `order` is global inside a grid, not relative to one sibling.
   */
  .booking-intro { order: 0; }
  .booking-shell.has-summary .booking-side { order: 1; }
  .booking-card { order: 2; }
  .booking-card { border-radius: 20px; padding: 24px 18px; }
  h1 { font-size: 30px; }
}
@media (max-width: 560px) {
  .brandbar { height: 64px; padding: 0 18px; }
  .location { display: none; }
  .booking-shell { padding-left: 12px; padding-right: 12px; }
  .form-grid { grid-template-columns: 1fr; }
  label.wide { grid-column: auto; }
  .slots { grid-template-columns: repeat(2, 1fr); }
  .actions { position: sticky; bottom: 0; padding: 14px 0 2px; background: linear-gradient(transparent, var(--paper) 25%); }
}

/* Service catalog grouping */
.service-group{display:grid;gap:12px;margin:0 0 28px}
.service-group-title{margin:4px 0 2px;font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--merlot)}
.service-note{display:block;margin-top:8px;color:var(--merlot);font-size:12px;line-height:1.5;font-style:italic}

.provider-label { max-width: 360px; margin-top: 24px; }
.service-group-title { padding-left: 10px; border-left: 4px solid var(--category-border, var(--merlot)); color: var(--category-text, var(--merlot)); }
.service-option { border-left: 4px solid var(--category-border, var(--line)); }
.service-option:hover { background: var(--category-bg, var(--tint)); }
.slot small { display:block; margin-top:4px; color:var(--muted); font-size:10px; }


/* Starting-price caveat shown before the client confirms. Added 25 Aug 2026. */
.pricing-note {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-left: 3px solid #5A2435;
  background: var(--tint);
  font-size: 13px;
  line-height: 1.55;
  color: var(--merlot-dark);
}


/* The salon-will-call notice on the confirmation screen. Reads as attentiveness, not as a
   warning: nothing has gone wrong, the salon simply wants to get the time and the price right.
   Shown only on high-severity bookings — see src/review-flags.js. */
.notice-call {
  margin: 0 0 14px;
  padding: 12px 14px;
  background: var(--paper);
  border-left: 3px solid var(--merlot);
  font-size: 0.95rem;
  line-height: 1.6;
}

/* ADD-ONS at step 1. Chosen before any times are shown, because the appointment has to be the
   right length before the calendar can find a slot for it. */
.addon-box {
  margin: 18px 0 0;
  padding: 14px 16px;
  border: 1px solid var(--line, var(--line));
  border-radius: 8px;
  background: var(--oyster, var(--oyster));
}
.addon-box > b { display: block; margin-bottom: 2px; }
.addon-note { margin: 0 0 10px; font-size: 0.86rem; color: var(--muted, var(--muted)); }
.addon {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 0; cursor: pointer; border-top: 1px solid rgba(0,0,0,0.06);
}
.addon:first-child { border-top: 0; }
.addon input { margin-top: 4px; flex: none; }
.addon span b { display: block; font-weight: 600; }
.addon span small { color: var(--muted, var(--muted)); font-size: 0.84rem; }


/* A way back to the salon's own site. A client who opened the booking page from a link on her
   phone may have no back button at all. */
.back-to-site {
  /* NO margin-left:auto. .brandbar is justify-content: space-between and an auto margin
     consumes the free space before space-between is applied — which pushed "Juno Beach,
     Florida" off the right edge and flush against the wordmark. */
  font-size: 0.92rem;
  color: var(--muted, var(--muted));
  text-decoration: none;
  padding: 10px 4px;          /* a real touch target, not just text */
  white-space: nowrap;
}
.back-to-site:hover, .back-to-site:focus-visible { color: var(--merlot, var(--merlot)); }
@media (max-width: 520px) { .back-to-site { font-size: 0.86rem; } }


/* WHAT THE CLIENT IS QUOTED, AND WHY — shown at the summary just before she confirms.
   Sonia, 29 August: "say that we are trying to be transparent and try to remove confusion."
   Styled as attentiveness, not as a warning: nothing has gone wrong, the salon is using the
   answers she gave. Deliberately quieter than .notice-call, which is a promise to phone. */
.estimate-note {
  margin: 10px 0 0;
  padding: 10px 12px;
  background: var(--paper);
  border-left: 3px solid var(--warn-edge);
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--ink);
}
.estimate-note.second-visit { border-left-color: var(--merlot); }

/* A WEEK AT A TIME. Sonia, 29 August: "i wanted them to see a week at a time not the entire
   month so they can easily see available appointments."

   The count is the whole point of the strip: a day with nothing free has to look different
   from a day nobody has clicked yet, or the client is still guessing. */
.week { margin: 0 0 18px; }
.week-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
}
.week-range { font-size: 0.95rem; font-weight: 500; letter-spacing: 0.01em; }
.week-nav {
  border: 1px solid var(--line, var(--line)); background: transparent; border-radius: 999px;
  width: 34px; height: 34px; font-size: 1rem; line-height: 1; cursor: pointer; color: inherit;
}
.week-nav:hover:not(:disabled) { background: var(--oyster); }
.week-nav:disabled { opacity: 0.3; cursor: default; }
.week-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.week-day {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 9px 2px 8px; border: 1px solid var(--line, var(--line)); border-radius: 10px;
  background: white; cursor: pointer; font: inherit; color: inherit; text-align: center;
  min-height: 68px; justify-content: center;
}
.week-day .wd { font-size: 0.68rem; letter-spacing: 0.09em; text-transform: uppercase; opacity: 0.6; }
.week-day .dd { font-size: 1.1rem; font-weight: 500; font-variant-numeric: tabular-nums; }
.week-day .ct { font-size: 0.68rem; opacity: 0.72; }
.week-day:hover:not(:disabled) { border-color: var(--merlot); }
.week-day[aria-pressed="true"] { background: var(--merlot); border-color: var(--merlot); color: white; }
.week-day[aria-pressed="true"] .wd,
.week-day[aria-pressed="true"] .ct { opacity: 0.85; }
/* Nothing free, and closed days, must read as different from "not looked at yet". */
.week-day:disabled { cursor: default; opacity: 0.45; background: var(--oyster); }
.week-day.loading .ct { opacity: 0.4; }
.date-label.jump { display: block; margin-top: 12px; font-size: 0.82rem; opacity: 0.75; }
@media (max-width: 560px) {
  .week-days { gap: 4px; }
  .week-day { min-height: 60px; padding: 7px 1px; }
  .week-day .dd { font-size: 1rem; }
  .week-day .ct { font-size: 0.62rem; }
}

/* JUMP TO A GROUP. Sonia, 29 August: "we should have on the top the different categories -
   hair treatment - haircut and so forth so they can jump to it."

   Thirty-one services is a long scroll, and someone who came in for a haircut should not have
   to read every colour service to find it. */
.main-first { margin-top: -4px; }
.cat-jump {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 14px 0 18px; padding-bottom: 14px;
  border-bottom: 1px solid var(--line, var(--line));
}
/**
 * THE GROUP BUTTONS. Sonia, 1 September 2026: "make into rectangle — and can they be another
 * colour so they can pop a bit more — add wide border around them."
 *
 * They were 1px outlines on a transparent background at 0.84rem: the same cream-on-cream
 * problem the main buttons had, in miniature, and about 28px tall, which is under the 44px a
 * fingertip needs. Nobody was going to find them.
 *
 * Colour: the pink accent (#B0304A) rather than merlot. Merlot is the colour of Confirm, and a
 * row of merlot buttons above the service list competes with the one button that matters. The
 * accent is the same pink as the underline under every "Book" on the salon's own menu, so it
 * reads as navigation rather than as a decision.
 */
/**
 * THE SAME BUTTON AS `.tsd-tab` ON THE SERVICES PAGE. Copied from the live rule, not the
 * screenshot — transparent fill, 2px merlot at 55%, sharp corners, a 3px offset shade, and
 * IBM Plex Mono uppercase at .15em.
 *
 * A NOTE ON THE TRANSPARENT FILL. I argued against this two rounds ago — transparent on cream
 * is how the first buttons got lost, and I wrote a test forbidding it. The site shows the rule
 * was too blunt: transparent is fine when a 2px edge AND an offset shade carry the shape. It is
 * only fatal when the fill is the ONLY thing meant to do the work. The test now says that
 * instead, which is what it should have said the first time.
 */
.cat-jump button {
  position: relative;
  flex: 0 0 auto;
  min-height: 44px;
  padding: 12px 19px;
  border: 2px solid rgba(90, 36, 53, .55);
  border-radius: 0;
  background: transparent;
  color: var(--merlot);
  box-shadow: 3px 3px 0 rgba(90, 36, 53, .20);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .15em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.cat-jump button:hover, .cat-jump button:focus-visible {
  border-color: var(--hot); color: var(--hot); background: transparent;
  box-shadow: 3px 3px 0 rgba(158, 35, 64, .28);
}
.cat-jump button:focus-visible { outline: 2px solid var(--hot); outline-offset: 2px; }
/* The group you are in fills, so the row says where you ARE, not only where you can go. */
.cat-jump button[aria-current="true"] {
  background: var(--hot); border-color: var(--hot); color: #FFFFFF;
  box-shadow: 3px 3px 0 rgba(90, 36, 53, .28);
}
.cat-jump button:active { transform: translate(3px, 3px); box-shadow: none; }

/* The group heading must not slide under the chips when jumped to. */
.service-group { scroll-margin-top: 12px; }
@media (max-width: 560px) {
  .cat-jump { gap: 8px; }
  /* Narrower, never shorter — 44px is a fingertip, not a style choice. */
  .cat-jump button { padding: 11px 13px; font-size: 11px; letter-spacing: .12em; min-height: 44px; }
}


/* The optional marketing box. Deliberately quieter than the required terms box above it —
   the eye should land on the one that blocks the booking, not the one that does not. */
.consent-optional { margin-top: .55rem; opacity: .8; }
.consent-optional span { font-size: .84rem; line-height: 1.5; }
.consent-optional:hover, .consent-optional:focus-within { opacity: 1; }

/**
 * HIDDEN MEANS HIDDEN. THIS RULE MUST STAY THE LAST THING IN THIS FILE.
 *
 * FOUND BY PRESSURE TEST, 31 August 2026 — the worst defect in the client-facing app.
 *
 * The browser's own stylesheet says [hidden] { display: none }, but that rule has the lowest
 * priority there is, and this file sets `label { display: grid }` and `.summary-card
 * { display: grid }`. Author rules win. So `label.hidden = true` set the attribute, changed
 * nothing on screen, and every client saw ALL THIRTEEN hair questions on EVERY service.
 *
 * A woman booking a $55 blow-dry was asked how much grey she has and to describe her chemical
 * history for the last two years — the exact friction the express blow-dry links exist to
 * remove.
 *
 * And it was worse than noise. consultationData() skips fields whose `hidden` ATTRIBUTE is
 * set, which was working correctly. So she could type a paragraph about her colour history
 * into a visible box and it was silently thrown away: never stored, never shown to Nancy,
 * never seen by the review-flag scorer.
 *
 * WHY LAST, AND WHY !important, AND WHY THIS COMMENT IS ONE BLOCK
 *
 * !important wins in a real browser whatever the order. jsdom, which the tests use, applies
 * last-wins and ignores !important — so putting it last makes the fix both correct in the
 * browser and provable in the suite.
 *
 * Two earlier attempts at this comment broke the stylesheet outright. The first left a stray
 * unopened comment-close above the rule; the second WROTE THE CLOSING SEQUENCE INSIDE THE
 * PROSE while explaining the first. Either one is a CSS parse error, and a browser stops
 * reading the file at that point — so the rule below was never parsed and the fix was inert
 * while looking perfectly correct in the editor.
 *
 * Never write the comment-terminator inside a comment. Caught only by counting the rules
 * jsdom actually parsed, which is why the test below asserts that count.
 */
/**
 * The checkbox itself. Left as the browser's own control on purpose — a hand-built one is a
 * thing screen readers and older phones get wrong — but sized so it can be hit.
 */
input[type="checkbox"], input[type="radio"] {
  width: 20px; height: 20px; flex: none; accent-color: var(--merlot); margin: 0;
}

/**
 * THE BLOW DRY BAR BUTTON.
 *
 * Deliberately the loudest thing on step 1 after the heading: it is the booking the salon most
 * wants to be easy, and the four one-tap links on the website point at the same four services.
 * Merlot fill so it reads as an invitation rather than another option in the list.
 */
.feature-jump {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 2px 14px;
  text-align: left;
  margin: 6px 0 18px;
  padding: 16px 20px;
  border: 2px solid var(--merlot);
  border-radius: 0;
  background: var(--merlot);
  box-shadow: 3px 3px 0 rgba(158, 35, 64, .45);
  color: #fff;
  min-height: 56px;
  transition: .16s ease;
}
.feature-jump:hover, .feature-jump:focus-visible {
  background: var(--merlot-dark);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(90,36,53,.24);
}
/**
 * EVERY PIECE PLACED EXPLICITLY. Found 31 August 2026 by rendering the button for the first
 * time in a real browser: the arrow was on the LEFT and "Blow Dry Bar" was shoved to the far
 * right, out of line with its own subtitle. It had shipped that way.
 *
 * Cause: .fj-go had a definite grid-row (1/3) and no column. CSS grid places items with a
 * definite row BEFORE auto-placed ones, so the arrow claimed column 1 and the label was pushed
 * into column 2. Nothing about the markup or the rule suggests that; the order in the HTML is
 * label, sub, arrow, and it reads correctly. Only the rendered layout shows it.
 *
 * Placing all three explicitly removes the algorithm from the decision.
 */
.fj-label { grid-column: 1; grid-row: 1; font-family: Georgia, 'Times New Roman', serif; font-size: 21px; font-weight: 400; }
/* 14px, not smaller: this line carries the price, which is the reason to tap. */
.fj-sub { grid-column: 1; grid-row: 2; font-family: var(--mono); font-size: 11px;
         letter-spacing: .1em; text-transform: uppercase; opacity: .88; line-height: 1.4; }
.fj-go { grid-column: 2; grid-row: 1 / 3; font-size: 22px; opacity: .9; }

/**
 * A SERVICE ROW, LAID OUT LIKE THE SALON'S OWN MENU.
 *
 * Picture on the left, name and description in the middle, price and time on the right —
 * the same shape as https://toucheeorganicsalon.com/services/, so a client who has just come
 * from the menu recognises what she is looking at.
 *
 * The image column collapses to nothing when there is no picture (`.no-img`), so a service
 * without one is a tidy two-column row rather than a gap where a photograph should be.
 */
.service-option {
  grid-template-columns: 120px 1fr auto;
  align-items: center;
  gap: 8px 16px;
}
.service-option.no-img { grid-template-columns: 1fr auto; }

.service-img {
  grid-row: 1 / -1;
  width: 120px; height: 120px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--oyster);
}
.service-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

.service-main { display: block; min-width: 0; }
.service-main strong { display: block; }
/* The description sits under the name now, inside the middle column, rather than spanning
   the whole row — otherwise it runs under the photograph. */
.service-option .service-main p { grid-column: auto; margin: 4px 0 0; }

@media (max-width: 430px) {
  /* On a phone the picture shrinks rather than the words: a 92px image would leave the name
     about 150px to live in, which is where the add-on rows went wrong earlier today. */
  .service-option { grid-template-columns: 88px 1fr auto; gap: 6px 12px; }
  .service-img { width: 88px; height: 88px; }
}

/* The discount code row. Quiet by default — most clients do not have one and should not have to
   read past it — and unmistakable once a code lands. */
.promo-row { margin: 18px 0 6px; }
.promo-row > label { display: block; font-weight: 600; margin-bottom: 8px; }
.promo-row .optional { font-weight: 400; color: var(--muted); font-size: 14px; }
/* The same marker on a hair question. Says it before she starts typing, not after she has
   stared at an empty box wondering whether she has to fill it in. */
label .optional {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  font-weight: 500; color: var(--muted); margin-left: 6px; white-space: nowrap;
}
.promo-entry { display: flex; gap: 10px; align-items: stretch; flex-wrap: wrap; }
.promo-entry input {
  flex: 1 1 190px; min-width: 0; text-transform: uppercase;
  font-family: var(--mono); letter-spacing: .1em;
}
.promo-entry .ghost { flex: 0 0 auto; margin: 0; }
.promo-result { margin: 10px 0 0; font-size: 15px; line-height: 1.5; }
.promo-result.ok  { color: var(--merlot-dark); font-weight: 600; }
.promo-result.no  { color: var(--muted-strong); }

[hidden] { display: none !important; }
