/* Galil (dry-cleaner layout 1): one stylesheet for the home page and every sub-page.

   The concept (2026-10-04 redesign): the posted price board and the torn claim-ticket stub.
   The prices are the page: a full-width ink board where every group is a column of rows with
   dot leaders from name to price, under a reversed caption bar, set in tabular condensed figures.
   The hours and address are the stub torn off it: a square kraft ticket with a perforation and
   two bitten notches. Everything else is plain paper and one hard-cropped photo that runs off the
   edge with nothing laid on it. Square corners throughout (DESIGN.md).

   Type: League Gothic for the lettering, Libre Franklin for the reading. League Gothic is a revival of
   Alternate Gothic No. 1 (ATF, 1903), the condensed gothic of storefront fascias, claim tickets and
   job-printed price boards: forty price lines fit a narrow board because the capitals are narrow. It is
   single-weight, so nothing here is ever set bold: hierarchy comes from size, case and tracking, and
   font-synthesis is off for it. Libre Franklin is the genuine Franklin Gothic lineage (ATF, 1902), the text
   gothic that stood beside Alternate Gothic in the same printers' cases, and it was Galil's original face.
   Neither is Oswald (Endor), Barlow (Hermon) or Public Sans (Arbel). The faces are the --display and --body
   tokens, so the studio can swap them; a swapped-in face has weights of its own and nothing depends on
   League Gothic's narrowness to fit (sizes are clamped, long words break).

   The five :root names below are the ones agency-site/app.js injects for the showroom palette
   picker; everything else derives from them with color-mix(). The board is --ink with --paper
   type (the pair the studio already guarantees at 4.5:1, as the footer uses it); prices on paper
   are --sr-green / --accent-green (guaranteed against the paper); the ticket is --field, which
   keeps text and Main readable on it. No colour is written twice. */
:root {
  --ink: #14294d;
  --paper: #ffffff;
  --accent-green: #2864d7;
  --accent-brass: #cfa66c;
  --text: #162233;
  /* The --sr-* names are the studio's (agency-site/app.js): it writes one only where a custom palette would leave the
     sheet's own colour under 4.5:1 (a mid-tone Background, a dark theme), so a preset draws the fallback after it. */
  --field: var(--sr-card, color-mix(in srgb, var(--accent-brass) 50%, var(--paper)));
  --mist: var(--sr-mist, color-mix(in srgb, var(--ink) 10%, var(--paper)));
  --quiet: var(--sr-quiet, color-mix(in srgb, var(--text) 72%, var(--paper)));
  --price: var(--sr-green, var(--accent-green));
  --radius: 0px;
  --max: 1120px;
  --gutter: 24px;
  /* The header, the price board and the footer run the window's full width and share one gutter: the page's
     exactly, until the page below stops filling the window and starts centring in --max, and then a twelfth of
     the margin that opens up, to 76px. The text sections sit in the --max column. */
  --head-gutter: clamp(var(--gutter), calc(var(--gutter) + (100vw - var(--max) - 2 * var(--gutter)) / 12), 76px);
  --display: 'League Gothic', 'Arial Narrow', Impact, sans-serif;
  --body: 'Libre Franklin', Arial, Helvetica, sans-serif;
  --dw: 400;
  --rule: color-mix(in srgb, var(--ink) 55%, var(--paper));
}

* { box-sizing: border-box; }
/* In-page links land below the pinned header, not under it. --head-pin is its height; the
   showroom adds its announcement bar to it, or zeroes it when a client has the header scroll away. */
html { --head-pin: 88px; scroll-behavior: smooth; scroll-padding-top: var(--head-pin); scrollbar-color: var(--ink) var(--paper); }
body { margin: 0; background: var(--paper); color: var(--text); font: 400 17px/1.55 var(--body); -webkit-font-smoothing: antialiased; accent-color: var(--ink); }
/* A page too short to fill the window (Terms and Privacy over a placeholder) keeps the footer at the bottom of it, not above a strip of the page's white. */
body { display:flex; flex-direction:column; min-height:100vh; min-height:100svh; }
body > main { flex:1 0 auto; }
p { text-wrap: pretty; }
h1, h2 { text-wrap: balance; }
a { color: inherit; }
:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
::selection { background: var(--ink); color: var(--paper); }
input, textarea { caret-color: var(--ink); }

/* The lettering voice: every headline, the brand, the board's captions. One weight; never synthesised bold. */
h1, h2, h3, .brand, .price-table caption, .answers dt { font-family: var(--display); font-weight: var(--dw); font-synthesis: none; letter-spacing: .015em; }

.skip-link { position: absolute; left: 20px; top: -80px; z-index: 50; padding: 10px 14px; background: var(--ink); color: var(--paper); font-weight: 600; text-decoration: none; }
.skip-link:focus { top: 16px; }
.preview-return { background: var(--ink); color: var(--paper); }
.preview-return a { display: flex; align-items: center; gap: 10px; min-height: 48px; max-width: var(--max); margin: auto; padding: 0 var(--gutter); font-size: 14px; font-weight: 600; text-decoration: none; }
.preview-return svg { width: 18px; height: 18px; transition: transform .16s ease; }
.preview-return a:hover svg { transform: translateX(-4px); }
.embedded-preview .preview-return { display: none; }
/* A live site's holiday-closing line, which site-live.js puts just above the header on every page:
   the ticket's kraft under the page's ink, since it is an hours fact, on the header's own left
   edge. It scrolls away with the page and the sticky header stays; the home page re-measures its fold
   when it arrives. `body >` outranks social-links.css, which loads after this file and centres it. */
body > .live-closed { padding: 10px var(--head-gutter); background: var(--field); color: var(--ink); font-size: 15px; text-align: left; }

.notes, .contact { max-width: var(--max); margin-left: auto; margin-right: auto; padding-left: var(--gutter); padding-right: var(--gutter); }

/* ---- the header ------------------------------------------------------------------------------------
   Full window width, the name at the left edge, the call button at the right. It stays on screen while
   the page scrolls (sticky, not fixed: it keeps its place in the flow, so the home page's --above fold
   maths is unchanged). The 3px ink edge is the board's top rail. */
.masthead { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 28px; min-height: 88px; background: var(--paper); padding-left: var(--head-gutter); padding-right: var(--head-gutter); border-bottom: 3px solid var(--ink); }
.brand { margin-right: auto; color: var(--ink); font-size: 2.05rem; line-height: 1; letter-spacing: .03em; text-transform: uppercase; text-decoration: none; white-space: nowrap; }
/* Logo slot (PROJECT.md §4): the generator sets src/width/height and removes `hidden`; alt stays empty
   because the name stays in the span for assistive tech, visually hidden while the logo shows. */
.brand-logo { display: block; height: 36px; width: auto; max-width: 220px; }
.brand-logo[hidden] { display: none; }
.brand:has(.brand-logo:not([hidden])) { display: inline-flex; align-items: center; }
.brand:has(.brand-logo:not([hidden])) [data-business-name] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

.site-nav { display: flex; gap: 26px; font-weight: 600; }
.site-nav a { padding: 6px 0; color: var(--ink); text-decoration: none; white-space: nowrap; }
.site-nav a:hover { text-decoration: underline; text-underline-offset: 8px; text-decoration-thickness: 3px; }
.site-nav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 8px; text-decoration-thickness: 3px; }
.nav-toggle { display: none; flex-shrink: 0; width: 44px; height: 44px; padding: 0; border: 2px solid var(--ink); border-radius: var(--radius); background: transparent; color: var(--ink); }

.button { display: inline-flex; align-items: center; justify-content: center; min-height: 48px; padding: 0 22px; border: 2px solid var(--ink); border-radius: var(--radius); background: var(--ink); color: var(--paper); font-weight: 700; text-decoration: none; white-space: nowrap; transition: background-color .16s ease, border-color .16s ease, color .16s ease; }
.button:hover { background: var(--sr-green, var(--accent-green)); border-color: var(--sr-green, var(--accent-green)); }
.button.secondary { background: transparent; color: var(--ink); }
.button.secondary:hover { background: var(--mist); border-color: var(--ink); }
.masthead .button { flex-shrink: 0; min-height: 44px; padding: 0 18px; white-space: nowrap; }

/* Header states. nav.js measures the row and sets these on <html> the moment the
   row would wrap: `nav-tight` drops the number from the Call button, `nav-snug`
   closes the links up a little, `nav-collapsed` moves them behind the menu button. No name, font, or width wraps the header;
   the only thing ever allowed to break is the name itself, behind the menu button
   on a phone. Without JS the links wrap under the name at phone widths instead. */
html.nav-tight .masthead .button { padding: 0 14px; }
html.nav-tight .masthead .button span { display: none; }
html.nav-snug .masthead { gap: 18px; }
html.nav-snug .site-nav { gap: 18px; font-size: 16px; }
html.nav-collapsed .masthead { gap: 12px; }
html.nav-collapsed .brand { white-space: normal; min-width: 0; }
html.nav-collapsed .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
html.nav-collapsed .site-nav { position: absolute; top: 100%; right: var(--head-gutter); z-index: 10; display: none; min-width: 220px; padding: 10px 18px 12px; border: 3px solid var(--ink); border-top: 0; background: var(--paper); }
html.nav-collapsed .site-nav.nav-open { display: grid; gap: 2px; max-height: calc(100vh - 100%); max-height: calc(100svh - 100%); overflow-y: auto; overscroll-behavior: contain; }   /* twelve links on a phone held sideways: the panel scrolls inside the window instead of running off it */
html.nav-collapsed .site-nav a { padding: 9px 0; }

/* ---- the opening ------------------------------------------------------------------------------------
   Three tracks: the page's left edge (the same one the --max column starts at, worked out from the
   container's own width so a scrollbar cannot move it), the copy, and the photo, which is the last
   track and so runs off the window's top-right edge. Nothing is laid on the photo: no scrim, no card.
   The stub (the hours) is the last thing in the copy column. --above is what sits above <main>
   (the demo's return bar, a live site's holiday-closing line, the masthead), measured by the home
   page's inline script so the opening ends exactly at the fold; 88px is the masthead's own height (its 3px rail included),
   the fallback without JS. */
.opening { display: grid; grid-template-columns: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))) minmax(0, 1fr) clamp(260px, 34vw, 600px); min-height: calc(100vh - var(--above, 88px)); min-height: calc(100svh - var(--above, 88px)); }
.hero-photo { grid-column: 3; grid-row: 1; margin: 0; background: var(--mist); min-height: 0; }
.hero-photo picture { display: block; width: 100%; height: 100%; }
.hero-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hero-copy { grid-column: 2; grid-row: 1; display: flex; flex-direction: column; justify-content: space-between; gap: 44px; min-width: 0; padding: clamp(36px, 5vw, 72px) clamp(28px, 4vw, 64px) clamp(32px, 4vw, 56px) 0; }
.opening h1 { max-width: 11em; margin: 0; color: var(--ink); font-size: clamp(3.6rem, 9.4vw, 6rem); line-height: .92; letter-spacing: .01em; overflow-wrap: break-word; }
.lede { max-width: 40ch; margin: 24px 0 0; font-size: clamp(1.1rem, 1.5vw, 1.25rem); line-height: 1.45; }
.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }
/* Two buttons in a row read as one balanced unit, not one wide and one narrow -- matched
   to the widest sibling's own content width, not a guessed pixel value, so it still works
   if a client's button text is longer or shorter. Mobile keeps its own full-width stack
   below (the flex:1 1 auto rule under 760px), so this only applies once there's a row. */
@media (min-width: 761px) {
  .actions { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
}

/* ---- the stub -------------------------------------------------------------------------------------
   The hours and the address, torn off the board: a kraft ticket (--field), square, with a perforation
   where it tears and a half-circle bitten out of each end of the tear in the page's own ground.
   Anywhere a table is followed by something (the address, a note), a dashed tear runs across; in the
   opening, at desktop widths, the stub is the other way round and the tear runs down. */
.door { --pad: 22px; --tear: color-mix(in srgb, var(--ink) 60%, var(--field)); --row-ground: var(--field); position: relative; padding: var(--pad); background: var(--field); color: var(--text); }
.door .rows { max-width: 440px; margin-bottom: 26px; }
.door .rows th { white-space: nowrap; }
.door h2 { margin: 0 0 6px; color: var(--ink); font-size: 2.1rem; line-height: 1; letter-spacing: .04em; text-transform: uppercase; }
.door .rows + * { position: relative; margin-top: 0; }
.door .rows + *::before { content: ""; position: absolute; top: -22px; left: calc(-1 * var(--pad)); right: calc(-1 * var(--pad)); height: 18px; pointer-events: none;
  background:
    radial-gradient(circle at 0 50%, var(--paper) 8.5px, transparent 9px),
    radial-gradient(circle at 100% 50%, var(--paper) 8.5px, transparent 9px),
    linear-gradient(90deg, var(--tear) 0 5px, transparent 5px 9px) 12px calc(50% - 1px) / 9px 2px repeat-x; }
.door-address { margin: 0; font-size: 1.2rem; font-weight: 700; line-height: 1.3; }
.door-phone { margin: 6px 0 0; }
.door-phone a { color: inherit; font-weight: 700; text-underline-offset: 4px; }
.door-phone a, .door-email a { overflow-wrap: anywhere; }
.door-email { margin: 4px 0 0; }
.door-email a { color: inherit; text-underline-offset: 4px; }
.door-parking, .door-note, .door-holiday { max-width: 44ch; margin: 14px 0 0; }
.door-note, .door-holiday { font-weight: 500; }
.door .actions { margin-top: 22px; }

.rows { width: 100%; border-collapse: collapse; }
/* The dot leader from a name to its price: dots on the name cell at the first line's baseline, and the first
   line of the name drawn in the row's own ground so no dot runs through a word. The rows' ground is
   --row-ground (kraft on the stub, ink on the board, paper elsewhere). */
.rows th { --dot: color-mix(in srgb, currentColor 58%, transparent); padding: 7px 10px 7px 0; font-weight: 500; text-align: left; vertical-align: top; line-height: 1.3;
  background: radial-gradient(circle at 50% 50%, var(--dot) 0 1px, transparent 1.5px) 0 calc(.98em - 3px) / 6px 6px repeat-x; background-origin: content-box; background-clip: content-box; }
.rows th::first-line { background: var(--row-ground, var(--paper)); }
.rows td { width: 1%; padding: 7px 0; font-weight: 700; font-variant-numeric: tabular-nums lining-nums; text-align: right; vertical-align: top; white-space: nowrap; }

/* ---- the price board -------------------------------------------------------------------------------
   The densest thing on the site, and wider than the page column: it runs to the header's own gutters.
   Groups flow in columns (they are different lengths, and a posted tariff is ragged), each under a
   reversed caption bar. On the home page the title and the way to the full list hang to the left of it. */
.board, .prices { --row-ground: var(--ink); background: var(--ink); color: var(--paper); }
.board :focus-visible, .prices :focus-visible, footer :focus-visible { outline-color: var(--paper); }
.board ::selection, .prices ::selection { background: var(--paper); color: var(--ink); }
.prices { display: grid; grid-template-columns: minmax(200px, 3fr) minmax(0, 9fr); align-items: start; column-gap: clamp(32px, 5vw, 88px); padding: clamp(56px, 7vw, 104px) var(--head-gutter) clamp(32px, 4vw, 56px); }
.prices .section-title { grid-column: 1; grid-row: 1; color: var(--paper); font-size: clamp(4.2rem, 9vw, 6rem); line-height: .88; letter-spacing: .02em; text-transform: uppercase; }
.prices .note { grid-column: 1; grid-row: 2; max-width: 30ch; margin: 22px 0 0; font-size: 1rem; }
.prices .price-groups { grid-column: 2; grid-row: 1 / span 3; margin: 0; }
.prices .actions { grid-column: 1; grid-row: 3; margin-top: 26px; }
.board-link { display: inline-flex; align-items: center; min-height: 48px; padding: 0 2px; color: var(--paper); font-weight: 700; text-decoration: underline; text-decoration-thickness: 3px; text-underline-offset: 8px; }
.board-link:hover { text-decoration-thickness: 6px; }

.price-groups { margin-top: 32px; }
.price-groups.sheet { display: block; columns: 2 300px; column-gap: clamp(32px, 4vw, 68px); }
.price-table { width: 100%; margin: 0 0 40px; break-inside: avoid; }
.price-table caption { padding: 8px 12px 9px; margin-bottom: 8px; background: var(--ink); color: var(--paper); font-size: 1.5rem; line-height: 1.1; letter-spacing: .06em; text-align: left; text-transform: uppercase; caption-side: top; }
.board .price-table caption, .prices .price-table caption { background: var(--paper); color: var(--ink); }
.price-table th { padding: 8px 12px 8px 0; }
.price-table th small { display: block; margin-top: 2px; color: var(--quiet); font-size: 14px; font-weight: 400; line-height: 1.35; }
.board .price-table th small, .prices .price-table th small { color: var(--paper); }
.price-table td { padding: 8px 0; color: var(--sr-green, var(--accent-green)); font-family: var(--display); font-size: 1.55rem; font-weight: var(--dw); font-synthesis: none; letter-spacing: .03em; line-height: 1; }
.board .price-table td, .prices .price-table td { color: var(--paper); }

/* One authored moment: the first time the board comes into view its caption bars are drawn across, once.
   Everything is on the page before then; if the script never runs, nothing waits for it. */
@keyframes bar-in { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
html.js .prices.is-visible .price-table caption, html.js .board.is-visible .price-table caption { animation: bar-in .55s cubic-bezier(.16, 1, .3, 1) both; }

/* The same-day line: after the board, almost nothing. The words get a measure and a great deal of ground. */
.notes { display: grid; grid-template-columns: minmax(150px, 4fr) minmax(0, 8fr); gap: clamp(20px, 5vw, 80px); padding-top: clamp(88px, 12vw, 176px); padding-bottom: clamp(88px, 12vw, 176px); }
.notes p { max-width: 30em; margin: 0; font-size: clamp(1.25rem, 2.3vw, 1.85rem); line-height: 1.32; font-weight: 500; }
.section-title { margin: 0; color: var(--ink); font-size: clamp(2.8rem, 5vw, 4rem); line-height: .92; }

/* ---- find us --------------------------------------------------------------------------------------
   The map runs off the left edge, the way the photo runs off the right in the opening, and the details
   are set as the fill-in lines of a ticket. */
.contact { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr) max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))); align-items: start; max-width: none; padding: 0; margin: 0; column-gap: clamp(28px, 5vw, 72px); }
.contact-grid { display: contents; }
.contact .section-title { grid-column: 2; grid-row: 1; padding-top: clamp(40px, 5vw, 72px); }
.map-frame { position: relative; grid-column: 1; grid-row: 1 / span 2; align-self: stretch; min-height: clamp(360px, 38vw, 560px); overflow: hidden; background: var(--mist); }
.map-embed { position: absolute; inset: 0; display: block; width: 100%; height: 100%; border: 0; pointer-events: none; }
.map-link { position: absolute; inset: 0; }
.map-link:focus-visible { outline-offset: -4px; }
.contact-grid > div:last-child { grid-column: 2; grid-row: 2; padding-top: 24px; padding-bottom: clamp(48px, 6vw, 88px); }
.details { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0; margin: 0; }
.details dt, .details dd { margin: 0; padding: 10px 0; }
.details dt { padding-right: 28px; color: var(--quiet); font-weight: 500; }
.details dd { font-weight: 700; }
.details a { color: var(--ink); text-underline-offset: 4px; }
.contact .actions { margin-top: 28px; }

/* Live from the shop's account (agency-site/site-live.js): the buttons and links the owner switched
   on in Integrations, under the block's own buttons (Find us on the home page, the stub on
   Contact). Both slots ship hidden and empty; only the lists inside them carry a display. Buttons are
   the outline one, so the filled Call / Get directions stays the block's own call, and a label up to
   40 characters wraps inside its button rather than running out of it. On a wide screen they fill the
   hours table's 440px measure: buttons that fit side by side split a row evenly (the same balanced pair
   as .actions), a long one takes the row. Links sit one to a line in the details' ink; Zelle has no
   address, so it arrives as a span and reads as a plain fact. */
.live-actions { margin-top: 12px; }
.live-buttons { display: flex; flex-wrap: wrap; gap: 12px; max-width: 440px; }
.live-buttons .button { max-width: 100%; padding-top: 10px; padding-bottom: 10px; line-height: 1.3; white-space: normal; text-align: center; text-wrap: balance; }
@supports (min-width: fit-content) {
  .live-buttons .button { flex: 1 1 0; min-width: fit-content; }
  /* Find us: the block's own Call / Get directions pair takes the same sizing, so the two rows'
     edges line up and the pair wraps instead of running past the narrow column (761-900px). */
  @media (min-width: 761px) {
    .contact .actions { display: flex; flex-wrap: wrap; max-width: 440px; }
    .contact .actions .button { flex: 1 1 0; min-width: fit-content; }
  }
}
/* On the stub the card's own Get directions sits at its natural width, so the live buttons
   do too; stretched, they would outweigh the one filled call. */
@media (min-width: 761px) {
  .door .live-buttons .button { flex: 0 1 auto; }
}
.door .live-buttons .button:hover { background: var(--paper); }
.live-links { margin-top: 24px; }
.live-link-list { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
.live-link-list li { margin: 0; }
.live-link-list a { color: var(--ink); font-weight: 600; text-underline-offset: 4px; }
.live-link-list span { font-weight: 600; }
@media (max-width: 760px) {
  .live-buttons .button { flex: 1 1 auto; min-width: auto; }
}

/* ---- the footer: left-aligned, one dense strip, on the board's own gutters --------------------------- */
footer { background: var(--ink); color: var(--paper); }
.footer-inner { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 36px; padding: 40px var(--head-gutter); font-size: 14px; }
.footer-inner p { margin: 0; }
.footer-inner .footer-credit { margin-left: auto; font-size: 13px; color: var(--sr-credit, color-mix(in srgb, currentColor 72%, transparent)); }
.footer-credit a { color: var(--sr-credit-link, #8fb3ff); }
footer a { text-underline-offset: 3px; }

/* Nothing waits on the script: sections are on the page from the first paint. The reveal class only
   marks the sections whose arrival is the one authored moment (the board's caption bars, above). */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.js .prices.is-visible .price-table caption, html.js .board.is-visible .price-table caption { animation: none; }
  .button, .preview-return svg { transition: none; }
}

/* Six links (About joined 2026-09-19) need 922px at the masthead's 26px gaps and 946px in the showroom's
   Poppins; just above the phone menu the gaps close to 18px (862 / 887px). */
@media (min-width: 901px) and (max-width: 1000px) {
  .masthead { gap: 18px; }
  .site-nav { gap: 18px; }
}

/* The stub inside the opening, at desktop widths: hours to the left of the tear, the address to the right of it. */
@media (min-width: 901px) {
  .opening .door { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: calc(var(--pad) * 2); max-width: 600px; }
  .opening .door h2 { grid-column: 1; }
  .opening .door .rows { grid-column: 1; margin-bottom: 0; }
  .opening .door .rows + * { grid-column: 2; grid-row: 1 / span 2; max-width: 16ch; }
  .opening .door .rows + *::before { top: calc(-1 * var(--pad)); bottom: calc(-1 * var(--pad)); left: calc(-1 * var(--pad) - 9px); right: auto; width: 18px; height: auto;
    background:
      radial-gradient(circle at 50% 0, var(--paper) 8.5px, transparent 9px),
      radial-gradient(circle at 50% 100%, var(--paper) 8.5px, transparent 9px),
      linear-gradient(180deg, var(--tear) 0 5px, transparent 5px 9px) calc(50% - 1px) 12px / 2px 9px repeat-y; }
}

@media (max-width: 900px) {
  html { --head-pin: 76px; }
  .masthead { min-height: 76px; }
  /* Without JS the links wrap under the name, too tall to keep on screen, so it scrolls away. */
  html:not(.js) .masthead { position: relative; flex-wrap: wrap; gap: 12px; padding-bottom: 12px; }
  html:not(.js) .masthead .button { padding: 0 14px; }
  html:not(.js) .masthead .button span { display: none; }
  html:not(.js) .site-nav { flex-wrap: wrap; flex-basis: 100%; order: 3; gap: 16px; min-width: 0; }
  /* Stacked: the copy, then the stub (hours and address on the first screen), then the photo (running off the right edge only). */
  .opening { display: flex; flex-direction: column; min-height: 0; }
  .hero-copy { display: contents; }
  .opening-copy { order: 1; padding: 36px var(--gutter) 32px; }
  .hero-photo { order: 3; margin: 36px 0 0 var(--gutter); height: clamp(260px, 62vw, 460px); }
  .door { order: 2; margin: 0 var(--gutter); }
  .prices { display: block; }
  .prices .price-groups { margin-top: 36px; }
  .contact { display: block; }
  .map-frame { min-height: 0; aspect-ratio: 4 / 3; }
  .contact .section-title { padding: 40px var(--gutter) 0; }
  .contact-grid > div:last-child { padding: 24px var(--gutter) 56px; }
  .contact-grid { display: block; }
}

@media (max-width: 760px) {
  .brand { font-size: 1.7rem; }
  :root { --gutter: 20px; }
  .actions .button { flex: 1 1 auto; }
  .door { --pad: 20px; }
  .opening h1 { font-size: clamp(3.4rem, 17vw, 5rem); }
  .prices { padding-top: 52px; padding-bottom: 28px; }
  .price-groups.sheet { columns: 1; }
  .price-table { margin-bottom: 32px; }
  .notes { display: block; padding-top: 72px; padding-bottom: 80px; }
  .notes .section-title { margin-bottom: 14px; }
  .footer-inner { padding-top: 32px; padding-bottom: 32px; }
  .footer-inner .footer-credit { margin-left: 0; }
}

/* ---- sub-pages ------------------------------------------------------------------------------------ */
.page, .page-head { max-width: var(--max); margin-left: auto; margin-right: auto; padding-left: var(--gutter); padding-right: var(--gutter); }
.page-head { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: end; column-gap: clamp(24px, 5vw, 80px); padding-top: clamp(36px, 5vw, 72px); padding-bottom: clamp(28px, 4vw, 52px); }
.page-head h1 { grid-column: 1; max-width: 11em; margin: 0; color: var(--ink); font-size: clamp(3.8rem, 9vw, 6rem); line-height: .92; letter-spacing: .01em; overflow-wrap: break-word; }
.page-head .page-note, .page-head .lede, .page-head .actions { grid-column: 2; }
.page-head .lede { margin: 0; font-size: 1.1rem; }
.page-note { max-width: 56ch; margin: 0; color: var(--quiet); font-size: 15px; }
.page-head .actions { margin-top: 18px; }
.sheet-notes { display: grid; grid-template-columns: minmax(150px, 4fr) minmax(0, 8fr); gap: clamp(20px, 5vw, 80px); padding-top: clamp(88px, 11vw, 160px); padding-bottom: clamp(24px, 4vw, 48px); }
.sheet-notes h2 { margin: 0; }
.sheet-notes p { max-width: 36em; margin: 0; font-size: clamp(1.2rem, 2vw, 1.6rem); line-height: 1.35; font-weight: 500; }
.sheet-notes a { color: var(--ink); font-weight: 700; text-underline-offset: 4px; }
.page-end { padding-top: clamp(40px, 5vw, 64px); padding-bottom: clamp(56px, 7vw, 96px); }
.page-end .actions { margin-top: 0; }

/* The prices page: the board is the page, straight under the head and out to the header's gutters. */
.price-groups.sheet.board { margin: 0; padding: clamp(40px, 5vw, 72px) var(--head-gutter) clamp(16px, 2vw, 24px); columns: 3 310px; }

/* Photos that run off the right edge (About, Delivery, the map on Hours): a slot with its right end past the window. */
.photo-band { padding-left: max(var(--gutter), calc(50% - var(--max) / 2 + var(--gutter))); }
/* The slot keeps its own height (no shift while the photo loads) and the mist ground as the loading
   state; the photo is cut to the slot with object-fit. A sample photo whose focus sits off center
   (photos/manifest.json) carries its own object-position as an inline style on its <img>, never here
   keyed by slot: the generator swaps the whole <picture> for the client's photo, and a crop written for
   shirts-line or a tall gown would then cut the client's storefront or van at the same offset. */
.photo-slot { position: relative; margin: 0; height: clamp(280px, 36vw, 540px); overflow: hidden; background: var(--mist); }
.photo-slot picture { display: contents; }
.photo-slot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* About: the head keeps air before the photo; the questions are one more label-left / value-right sheet. */
.about-head { padding-bottom: clamp(40px, 5vw, 64px); }
.answers { padding-top: clamp(64px, 8vw, 112px); }
.answers dl { display: grid; grid-template-columns: minmax(150px, 4fr) minmax(0, 8fr); gap: 40px clamp(20px, 5vw, 80px); margin: 36px 0 0; }
.answers dt { color: var(--ink); font-size: 2.1rem; line-height: 1; }
.answers dd { max-width: 58ch; margin: 0; font-size: 1.05rem; }
.answers dd .rows { max-width: 440px; margin-top: 14px; }
.answers a { color: var(--ink); font-weight: 600; text-underline-offset: 4px; }

/* Photos: a twelve-column hand-set page. Cells are cut with object-fit to a fixed shape, because the generator
   puts the client's photos into these eight cells in order and any shape of photo has to fill its cell. Captions
   sit under the photo at its left edge, not on it. Stagger and spans break the grid on purpose. */
.gallery { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: clamp(28px, 3.4vw, 52px) clamp(14px, 1.6vw, 24px); align-items: start; max-width: none; padding: 8px var(--head-gutter) clamp(56px, 7vw, 104px); }
.gallery .photo-slot { display: flex; flex-direction: column; height: auto; overflow: visible; background: none; }
.gallery .photo-slot picture { display: block; position: relative; aspect-ratio: var(--shape, 3 / 2); background: var(--mist); }
.gallery .photo-slot figcaption { max-width: 36ch; padding-top: 10px; font-size: 14px; line-height: 1.4; }
.gallery .g1 { grid-column: 1 / span 8; --shape: 3 / 2; }
.gallery .g2 { grid-column: 9 / span 4; --shape: 2 / 3; }
.gallery .g3 { grid-column: 1 / span 5; --shape: 4 / 3; margin-top: clamp(0px, 4vw, 64px); }
.gallery .g4 { grid-column: 6 / span 7; --shape: 16 / 9; }
.gallery .g5 { grid-column: 1 / span 3; --shape: 2 / 3; }
.gallery .g6 { grid-column: 4 / span 5; --shape: 3 / 2; margin-top: clamp(0px, 5vw, 80px); }
.gallery .g7 { grid-column: 9 / span 4; --shape: 1 / 1; }
.gallery .g8 { grid-column: 4 / span 9; --shape: 21 / 9; }

/* Contact reads as one column at every width, per E (2026-09-24): map, then the stub, then the note form,
   then reviews. The track stops at 760px so the map stays a sensible height. */
.contact-top { display: grid; grid-template-columns: minmax(0, 760px); gap: 28px; align-items: start; padding-top: 8px; }
.contact-top .map-frame { grid-column: auto; grid-row: auto; aspect-ratio: 16 / 9; min-height: 0; }
.contact-top .door { margin: 0; }
.contact-bottom { display: grid; grid-template-columns: minmax(0, 760px); gap: clamp(40px, 5vw, 64px); padding-top: clamp(64px, 8vw, 112px); padding-bottom: clamp(56px, 7vw, 96px); }
.contact-bottom h2 { margin: 0 0 16px; }
.form { display: grid; gap: 16px; max-width: 520px; }
.form label { display: block; margin-bottom: 6px; font-weight: 600; }
.form input, .form textarea { display: block; width: 100%; padding: 12px 14px; border: 1.5px solid var(--sr-field-line, color-mix(in srgb, var(--text) 60%, var(--paper))); border-radius: var(--radius); background: var(--paper); color: var(--text); font: inherit; }
.form textarea { min-height: 120px; resize: vertical; }
.form input:focus-visible, .form textarea:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; border-color: var(--ink); }
.form .button { justify-self: start; font: inherit; font-weight: 700; }
.form-note { margin: 12px 0 0; color: var(--quiet); font-size: 15px; }
.form-status { margin: 0; color: var(--ink); font-weight: 600; }
/* Honeypot: parked off-screen, never display:none, so a bot fills it and a person never sees it. */
.form-hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
/* No-JS result: the relay sends the visitor back to #sent or #not-sent (the id beats the UA [hidden] rule). */
#sent:target, #not-sent:target { display: block; }
.reviews p { margin: 0; max-width: 48ch; }
.reviews a { color: var(--ink); font-weight: 600; text-underline-offset: 4px; }

/* Hours: the stub leads with the whole week, the address beside it, then the map running off the right edge. */
.hours-top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(28px, 5vw, 72px); align-items: start; padding-top: 8px; padding-bottom: clamp(48px, 6vw, 80px); }
.hours-top .door { margin: 0; }
.where { padding-top: 6px; }
.where h2 { margin: 0 0 12px; color: var(--ink); font-size: 2.5rem; line-height: 1; }
.where .actions { margin-top: 24px; }
.parking { margin-top: clamp(32px, 4vw, 48px); }
.parking h2 { font-size: 1.9rem; }
.parking p { max-width: 48ch; margin: 0; }
.hours-map .map-frame { grid-column: auto; aspect-ratio: auto; min-height: 0; height: clamp(280px, 34vw, 480px); margin-bottom: clamp(56px, 7vw, 96px); }

/* Delivery: the steps beside the stub (the area we cover), then the photo, the same-day note, and the request form. */
.pickup-top { display: grid; grid-template-columns: minmax(0, 7fr) minmax(280px, 5fr); gap: clamp(40px, 6vw, 96px); align-items: start; padding-top: 8px; padding-bottom: clamp(40px, 5vw, 64px); }
.pickup-top .door { margin: 0; }
.steps { max-width: 46ch; margin: 20px 0 0; padding-left: 0; font-size: 1.1rem; list-style: none; }
.steps li + li { margin-top: 14px; }
.door-area, .door-terms { max-width: 40ch; margin: 12px 0 0; }
.door-terms { font-size: 15px; }
.pickup-form { padding-top: clamp(48px, 6vw, 80px); padding-bottom: clamp(56px, 7vw, 96px); }
.pickup-form h2 { margin: 0 0 16px; }

/* Alterations: the sheet beside the stub, the counter's words on the stub. */
.alterations-top { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(280px, .8fr); gap: clamp(28px, 5vw, 72px); align-items: start; padding-top: 8px; }
.alterations-top .page-note { margin: 0 0 18px; }
.alterations-top .door { margin: 0; }
.door .counter p { max-width: 38ch; margin: 12px 0 0; }
.door .door-note { max-width: 38ch; margin: 22px 0 0; font-size: 15px; }
/* The tear is drawn on the block under the rows, so that block spans the whole ticket (edge to edge) and the text keeps its
   measure as padding. The opening's stub, which tears vertically, sets its own max-width above and wins. */
.door .rows + * { max-width: none; padding-right: max(0px, calc(100% - 44ch)); }

/* Specialty care: each band is the opening's grid with the photo running off the window's edge, alternating sides. */
.specialties { display: grid; row-gap: clamp(56px, 7vw, 112px); padding-top: 8px; }
.specialty { display: grid; grid-template-columns: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))) minmax(0, 1fr) minmax(0, 1fr); column-gap: clamp(32px, 5vw, 80px); align-items: start; }
.specialty .copy { grid-column: 2; grid-row: 1; padding-right: clamp(0px, 2vw, 32px); }
.specialty .photo-slot { grid-column: 3; grid-row: 1; height: auto; min-height: clamp(300px, 30vw, 460px); align-self: stretch; }
.specialty:nth-child(even) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))); }
.specialty:nth-child(even) .copy { grid-column: 2; padding-left: 0; }
.specialty:nth-child(even) .photo-slot { grid-column: 1; }
.specialty h2 { margin: 0; color: var(--ink); font-size: clamp(2.8rem, 5vw, 4rem); line-height: .92; }
.specialty .copy p { max-width: 52ch; margin: 16px 0 0; font-size: 1.05rem; }
.specialty .rows { max-width: 440px; margin-top: 22px; }
.specialty .turnaround td { color: var(--text); white-space: normal; font: 700 1rem/1.3 var(--body); letter-spacing: 0; text-align: right; }
.specialty .rows td [data-token="template_note"] { color: var(--quiet); font-weight: 400; white-space: normal; }
.specialties + .page .sheet-notes { padding-top: clamp(72px, 9vw, 120px); }

/* Questions: label-left / value-right, six pairs straight under the head. */
.questions { padding-top: 8px; }
.questions dl { margin-top: 0; }
/* Each pair is wrapped for the generator (data-token-item); the wrapper stays out of the grid. */
.questions [data-token-item] { display: contents; }
.questions dt { font-size: 1.9rem; }
.questions dd [data-token="template_note"] { color: var(--quiet); }

/* Reviews: slips in two columns; the name is signed under the quote. No stars, no counts: each slot holds
   what the Google listing shows, or nothing. */
.review-sheet { padding-top: 8px; }
.review-sheet [data-token="reviews"], .review-sheet > div:has(> .review) { columns: 2 340px; column-gap: clamp(28px, 5vw, 72px); }
/* google-reviews.js strips the list's data-token when it draws a Google summary, so the columns also key on what the list holds.
   Its rating line is a quiet header here, in the sheet's single-weight lettering, with the stars in the price colour. */
.review-sheet .gr-summary { margin-bottom: clamp(24px, 3vw, 36px); }
html .gr-score { font-weight: var(--dw); font-synthesis: none; letter-spacing: .03em; }
.review-sheet .gr-score { font-size: 2rem; }
html .gr-stars .gr-on { color: var(--price); }
.review { break-inside: avoid; margin: 0 0 clamp(28px, 3.4vw, 44px); }
.review-quote { margin: 0; font-size: 1.12rem; line-height: 1.45; }
.review-name { margin: 8px 0 0; color: var(--ink); font: 400 1.5rem/1.1 var(--display); letter-spacing: .06em; text-transform: uppercase; }
.review [data-token="template_note"] { color: var(--quiet); }
.review-sheet .page-note { margin-top: clamp(24px, 4vw, 48px); }
/* The demo's own line over its sample reviews (a template note: never on a client's site), there for a visitor with no script. */
.review-sheet .sample-note { margin: 0 0 clamp(20px, 3vw, 32px); }

/* What's included: label-left / value-right, the end date as the fine print. */
.offers { display: grid; gap: 40px; margin-top: 8px; }
.offer { display: grid; grid-template-columns: minmax(150px, 4fr) minmax(0, 8fr); gap: clamp(20px, 5vw, 80px); align-items: start; }
.offer h2 { margin: 0; color: var(--ink); font-size: clamp(2.4rem, 4vw, 3.3rem); line-height: .95; }
.offer p { max-width: 58ch; margin: 0; font-size: 1.05rem; }
.offer .expires { margin-top: 8px; color: var(--quiet); font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* Cannot ship by accident: the protocol's hot-pink placeholder for text that does not exist yet. */
.todo-block { max-width: 62ch; margin: 8px 0 clamp(56px, 7vw, 96px); padding: 22px 24px; border-radius: var(--radius); background: #C4007A; color: #fff; font-size: 1.05rem; line-height: 1.5; }
.todo-block strong { display: block; margin-bottom: 8px; font-size: 1.15rem; }

@media (max-width: 900px) {
  .page-head { display: block; }
  .page-head h1 { margin-bottom: 16px; }
  .hours-top, .pickup-top, .alterations-top { display: block; }
  .where { margin-top: 40px; padding-top: 0; }
  .pickup-top .door { margin-top: 28px; }
  .alterations-top .door { margin-top: 32px; }
  .gallery { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .gallery .g1, .gallery .g4, .gallery .g8 { grid-column: 1 / -1; }
  .gallery .g2 { grid-column: 4 / span 3; }
  .gallery .g3 { grid-column: 1 / span 3; margin-top: 0; }
  .gallery .g5 { grid-column: 1 / span 3; }
  .gallery .g6 { grid-column: 4 / span 3; margin-top: 0; }
  .gallery .g7 { grid-column: 1 / span 3; }
  .specialty, .specialty:nth-child(even) { display: block; }
  .specialty .copy, .specialty:nth-child(even) .copy { padding: 0 var(--gutter); }
  .specialty .photo-slot, .specialty:nth-child(even) .photo-slot { margin: 28px 0 0 var(--gutter); height: clamp(220px, 60vw, 320px); min-height: 0; }
  .specialty:nth-child(even) .photo-slot { margin: 28px var(--gutter) 0 0; }
}

@media (max-width: 760px) {
  .photo-slot { height: clamp(220px, 60vw, 320px); }
  .sheet-notes { display: block; padding-top: 64px; }
  .sheet-notes h2 { margin-bottom: 14px; }
  .about-head { padding-bottom: 36px; }
  .answers { padding-top: 56px; }
  .answers.questions { padding-top: 8px; }
  .answers dl { display: block; margin-top: 24px; }
  .answers dt + dd { margin-top: 6px; }
  .answers dd + dt { margin-top: 28px; }
  .questions [data-token-item] { display: block; }
  .questions [data-token-item] + [data-token-item] { margin-top: 28px; }
  .offers { gap: 28px; }
  .offer { display: block; }
  .offer h2 + div { margin-top: 6px; }
  .review-sheet [data-token="reviews"], .review-sheet > div:has(> .review) { columns: 1; }
  .gallery { grid-template-columns: minmax(0, 1fr); padding-bottom: 56px; }
  .gallery .photo-slot { grid-column: 1 / -1 !important; margin-top: 0 !important; }
  .gallery .g2, .gallery .g5 { --shape: 4 / 5; }
  .contact-bottom { padding-top: 48px; padding-bottom: 64px; }
  .page-end { padding-top: 32px; padding-bottom: 64px; }
  .price-groups.sheet.board { padding-top: 36px; }
  .hours-map .map-frame { height: clamp(240px, 64vw, 320px); margin-bottom: 64px; }
  .pickup-form { padding-top: 40px; padding-bottom: 64px; }
  .details { grid-template-columns: minmax(0, 1fr); }
  .details dt { padding-bottom: 0; }
  .details dd { padding-top: 2px; }
}

/* A printed or saved page shows every section; the board's bars simply stand drawn. */
@media print {
  html.js .prices .price-table caption, html.js .board .price-table caption { animation: none; }
}

/* Footer links on a phone: a hit area of at least 24px, the line height and the look unchanged (padding on an inline link adds no height to its line). */
@media (max-width: 640px) {
  footer p a { padding-block: 5px; }
}
