/* ==========================================================================
   mobile.css — the phone build.

   Loaded ONLY at 767px and below, via a media-qualified <link> in each page's
   head. Desktop never parses a line of this file's rules, and this file never
   imports, extends or patches site.css — it re-declares what the phone needs
   from scratch. That separation is the point: mobile decisions live here, in
   one place, and cannot leak upward into the desktop layout.

   WHY THIS AND NOT A SEPARATE /m/ TREE

   The usual pattern for a dedicated mobile build is a parallel directory of
   HTML plus a UA redirect. It is the wrong trade for this site specifically:
   every page hard-codes prices, and tests/copy-rules.test.js exists precisely
   because "a rate edit leaves hardcoded strings stating the OLD number while
   the calculator quotes the new one". Duplicating eight pages would double
   that drift surface for the one class of bug that actually costs money here.
   A static host also has no server-side UA detection, so the redirect would be
   a JavaScript round trip in front of every mobile page load — a real Core Web
   Vitals cost incurred in the name of mobile performance.

   One HTML source, one price, one canonical URL. The mobile DESIGN is separate;
   the mobile CONTENT is not duplicated.

   WHAT WAS ACTUALLY WRONG, MEASURED AT 390px BEFORE ANY OF THIS

     index      4382px   6.6 screens      hero 1415px, services 2012px
     services   5103px   7.7 screens
     quote      3958px   6.0 screens
     about      3005px   4.5 screens
     footer      859px   on every page
     13 tap targets under 44px, 12 text elements under 14px

   The site was not "responsive" so much as vertically stacked: a four-column
   desktop grid became four full-height cards, and a four-column footer became
   four stacked lists. Nothing was broken; everything was simply desktop
   density delivered to a phone.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Type and rhythm, retuned for the device.

   Same scale, different footing. 13px is a legible caption on a monitor at
   60cm and a squint at arm's length on a phone, so the two smallest steps come
   up. Section rhythm comes DOWN: 64px of air between sections is generous on a
   1440px canvas and is a third of a phone screen spent on nothing.
   -------------------------------------------------------------------------- */
:root {
  --ms-text-xs: 0.875rem;    /* 13 -> 14px  */
  --ms-text-sm: 0.9375rem;   /* 15px, unchanged — already fine */
  --ms-text-lg: 1.25rem;     /* 22 -> 20px  */
  --ms-text-xl: 1.5rem;      /* 28 -> 24px  */

  --ms-rhythm-section: var(--ms-space-7);   /* 64 -> 48px between sections */
  --ms-rhythm-related: var(--ms-space-5);   /* 32 -> 24px head to content  */

  /* Safe-area insets, so notches and home indicators are addressable by name
     rather than repeated inline. Zero on anything that has none. */
  --ms-safe-top: env(safe-area-inset-top, 0px);
  --ms-safe-bottom: env(safe-area-inset-bottom, 0px);
}

/* --------------------------------------------------------------------------
   Touch behaviour.
   -------------------------------------------------------------------------- */

/* Removes the ~300ms click delay browsers keep for double-tap-to-zoom, without
   disabling zoom itself (which would be a WCAG 1.4.4 failure). */
a, button, label, select, input, summary, [role="button"] {
  touch-action: manipulation;
}

/* The grey flash on tap is an Android Chrome default that fires on the whole
   tile, including the parts of a card that are not the link. The focus and
   active states below carry the feedback instead. */
a, button { -webkit-tap-highlight-color: transparent; }

/* --------------------------------------------------------------------------
   The fixed nav pill, and the space reserved for it.

   body's padding-top has to clear the pill, and on a notched phone the pill
   itself has to clear the status bar. Both numbers move together or the first
   heading on every page ends up underneath something.
   -------------------------------------------------------------------------- */
body { padding-top: calc(6rem + var(--ms-safe-top)); }

.ms-header { top: calc(var(--ms-space-3) + var(--ms-safe-top)); }

[id] { scroll-margin-top: calc(6rem + var(--ms-safe-top) + var(--ms-rhythm-tight)); }

/* The header's compact CTA label was 12px — the smallest text on the site, on
   its most important control. */
.ms-header__cta {
  font-size: var(--ms-text-xs);
  min-height: 44px;
  padding-inline: var(--ms-space-4);
}

.ms-nav__summary { min-height: 44px; min-width: 44px; }

/* Both were a couple of pixels under: the logo lockup at 40px and the skip
   link at 42px. Neither is a rounding error to a thumb. */
.ms-logo { min-height: 44px; }
.ms-skip-link { min-height: 44px; display: flex; align-items: center; }

/* --------------------------------------------------------------------------
   Hero — 1415px was two and a bit screens before a single service was named.
   -------------------------------------------------------------------------- */
.ms-hero { padding-block: var(--ms-space-4) var(--ms-space-6); }

.ms-hero__inner {
  padding: var(--ms-space-5);
  gap: var(--ms-space-5);
}

/* 4:3 costs about 290px of a 664px viewport. 16:9 keeps the room legible as a
   room and gives 110px of it back to the thing the page is actually for. */
.ms-hero__media { aspect-ratio: 16 / 9; }

.ms-hero__visual { gap: var(--ms-space-5); }

/* Not shown on a phone. With it, "Get your exact price" landed about 30px
   below an 844px fold — the one button the hero exists for, just out of
   sight. The figure is on About and in the footer of every page. */
.ms-hero__stat { display: none; }

/* --------------------------------------------------------------------------
   Service tiles — 2012px, four cards deep, on the page a visitor lands on.

   Each tile carried its full four-item tick list, which is right on a desktop
   grid where all four sit side by side and the lists read as a comparison. In
   a single column they are simply four screens of small print between the
   visitor and the next thing.

   The ticks are hidden here, not deleted: they stay in the DOM (so they are
   still indexed and still available to a screen reader following the page in
   order), and "What's included" — a link that was already there, already
   promising exactly this — carries the visitor to the same list in full on
   services.html. That is progressive disclosure using a route that already
   existed, not content thrown away.
   -------------------------------------------------------------------------- */
.ms-service-tile__list { display: none; }

.ms-service-tile {
  padding: var(--ms-space-4) var(--ms-space-5);
  gap: var(--ms-space-1);
}

.ms-service-tile__more {
  padding-top: var(--ms-space-3);
  min-height: 44px;
  align-items: center;
}

.ms-service-grid { gap: var(--ms-space-3); }

.ms-services { padding-block: var(--ms-space-5) var(--ms-rhythm-section); }

/* The tile title reserves two lines on desktop so four columns align. In one
   column there is nothing to align to, and the reserve is just a gap under
   every single-line title. */
.ms-service-tile .ms-card__title { min-height: 0; }

/* --------------------------------------------------------------------------
   The pill note under the tiles, and any inline link that was text-sized.
   -------------------------------------------------------------------------- */
.ms-services__note {
  padding: var(--ms-space-4) var(--ms-space-5);
  border-radius: var(--ms-radius-lg);
  line-height: var(--ms-leading-normal);
}
/* Three lines, no separators. As one sentence with " · " between the parts
   it wrapped with a lone dot at a line end and a 40px gap under it. */
.ms-services__note .ms-services__sep { display: none; }
.ms-services__note .ms-services__part { display: block; }

/* --------------------------------------------------------------------------
   Sections and headings.
   -------------------------------------------------------------------------- */
.ms-section { padding-block: var(--ms-rhythm-section); }
.ms-section__head { margin-bottom: var(--ms-rhythm-related); }
.ms-page-head { padding-block: var(--ms-space-6) var(--ms-space-5); }

/* --------------------------------------------------------------------------
   Forms — the quote page is the product, and it is used on a phone.
   -------------------------------------------------------------------------- */

/* 16px is the threshold below which iOS Safari zooms the viewport on focus and
   leaves it zoomed. The tokens already land on 16px; this states it as a floor
   so a future type-scale change cannot quietly cross it. */
.ms-field input,
.ms-field select,
.ms-field textarea { font-size: max(16px, var(--ms-text-base)); min-height: 48px; }

/* The two add-on quantity spinners carry their own styling rather than
   .ms-field's, so the 16px floor above missed them entirely: both shipped at
   15px and 42px tall. On the one page where a customer types numbers, that
   meant tapping the window count zoomed iOS Safari in and left it zoomed. */
.ms-addon-row__count-label input {
  font-size: max(16px, var(--ms-text-base));
  min-height: 44px;
  min-width: 4.5rem;
}

/* Radio beside the name, not above it. As a flex column the input sat alone
   on its own row over the service name, and the four service tiles ran to
   about 500px of screen for four choices. Two columns: the control, then
   everything else; the rate line drops under the name in the second column. */
.ms-option {
  min-height: 56px;
  padding: var(--ms-space-4);
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--ms-space-3);
  row-gap: var(--ms-space-1);
  align-items: center;
}
.ms-option .ms-option__meta { grid-column: 2; }
.ms-option--compact { min-height: 48px; }
.ms-addon-row { min-height: 56px; }
.ms-toggle { min-height: 44px; align-items: center; }

/* The counted add-on ("Interior windows") is the one row that is a <div>
   holding two labels rather than being a <label> itself, so sizing the ROW
   does not size the thing you actually tap. The row was 56px and the label
   inside it 276x24 — a target that LOOKS like the full row and responds on
   the middle 24px of it, which is worse than an honestly small one, because
   the misses read as the page being broken rather than as bad aim.

   align-self, not just min-height: it fills whatever height the row settles
   at instead of guessing one, so the tappable area and the visible row stay
   the same shape if either changes. min-height is the floor underneath it. */
.ms-addon-row--counted .ms-addon-row__check {
  align-self: stretch;
  min-height: 44px;
}

.ms-quote-form { gap: var(--ms-space-5); }
.ms-quote-step { padding: var(--ms-space-5); }

/* Full-width buttons: a phone has one thumb column, and a centred pill with
   dead space either side of it is a smaller target for no reason. */
.ms-quote-cta .ms-btn { flex: 1 1 100%; }

/* --------------------------------------------------------------------------
   Footer — 859px on every page, or one and a third screens of links.

   The four columns stacked into four full-width lists, each with its own
   heading, inside 128px of vertical padding. Two-up puts the three link
   columns into two rows instead of three, the padding comes down, and every
   link becomes a real 44px target rather than a 24px line of text — which is
   where most of this page's failing tap targets were.
   -------------------------------------------------------------------------- */
.ms-footer {
  padding-block: var(--ms-space-6);
  padding-bottom: calc(var(--ms-space-6) + var(--ms-safe-bottom));
}

.ms-footer__grid {
  grid-template-columns: 1fr 1fr;
  gap: var(--ms-space-5) var(--ms-space-4);
  text-align: left;
}

/* The brand block keeps the full width — a tagline squeezed into half a phone
   screen wraps to five lines. */
.ms-footer__col--brand { grid-column: 1 / -1; text-align: center; }

.ms-footer__col { text-align: left; }
.ms-footer h2 { margin-bottom: var(--ms-space-1); }
.ms-footer ul { align-items: stretch; gap: 0; }

/* Contact spans the full width. In a half column "City of Toronto — all M
   postal codes" and "Inner & outer GTA for a travel fee" each broke over two
   lines, and it was the odd column out on its own row anyway, leaving the
   other half of that row empty. */
.ms-footer__grid > .ms-footer__col:last-child { grid-column: 1 / -1; }

/* The ANCHOR is the target, and it fills its row. Sizing the <li> instead
   leaves a 44px row containing a link only as wide as its own text — which is
   how "Deep" ended up a 36px-wide tap target inside a correctly-sized row. */
.ms-footer__col li { display: block; line-height: var(--ms-leading-snug); }

.ms-footer__col li a {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
}

/* Rows that are plain facts rather than links need no target height, and
   giving them one adds ~90px of footer for nothing. */
.ms-footer__col li:not(:has(a)) { padding-block: var(--ms-space-2); }

/* The phone and email rows are an icon plus a link; the row is the flex
   container, so the anchor inside it must not also try to be full width.

   Qualified with .ms-footer__col, and that is load-bearing. As a bare
   `.ms-footer__icon-row` (0,1,0) this lost to `.ms-footer__col li` (0,1,1)
   above it no matter what order the rules sat in, so the row computed as
   `display: block` and the icon fell onto its own line with the phone number
   stranded underneath it. Same story one line down: `.ms-footer__icon-row a`
   (0,1,1) lost `width` to `.ms-footer__col li a` (0,1,2).

   Specificity, not source order — the third time this stylesheet has been
   caught by a single-class override sitting under a two-class rule. */
.ms-footer__col li.ms-footer__icon-row {
  display: flex;
  align-items: center;
  /* Left, like the three plain lines under it; the phone and email rows were
     the only centred lines in a left-aligned column. */
  justify-content: flex-start;
  gap: var(--ms-space-2);
}

.ms-footer__col li.ms-footer__icon-row a { width: auto; }

.ms-footer__bottom { margin-top: var(--ms-space-5); padding-top: var(--ms-space-4); }

/* --------------------------------------------------------------------------
   Services page — the longest on the site at 7.4 screens.

   Its tick lists STAY. This is the page the home page's "What's included"
   links point at, so hiding the detail here would send a visitor round a loop
   to nothing. What comes out is the packaging: 32px between panels and 24px
   of padding inside each, four times over, is most of a screen spent on air
   around content the visitor came for.
   -------------------------------------------------------------------------- */
.ms-service-list { gap: var(--ms-space-4); }

.ms-service-panel {
  padding: var(--ms-space-4) var(--ms-space-5);
  gap: var(--ms-space-3);
}

.ms-check-list { gap: var(--ms-space-1) var(--ms-space-4); }
.ms-check-list li { line-height: var(--ms-leading-snug); padding-block: 2px; }

/* --------------------------------------------------------------------------
   Pricing page.
   -------------------------------------------------------------------------- */
.ms-footnotes { margin-top: var(--ms-space-5); gap: var(--ms-space-4); }
.ms-cta-band { padding: var(--ms-space-6) var(--ms-space-5); }
.ms-cta-band__duck { width: 3.5rem; height: 3.5rem; }
.ms-cta-band .ms-btn { width: 100%; }

/* --------------------------------------------------------------------------
   Prose and info layouts.
   -------------------------------------------------------------------------- */
.ms-prose p { font-size: var(--ms-text-base); }
.ms-info-grid { gap: var(--ms-space-5); }

/* The estimate panel's table on a phone. The panel is about 340px wide and
   the amount column carries "From $325 CAD" unbroken, so with 20px of cell
   padding a side the item column was left about 70px and wrapped "Regular
   Cleaning — 1000 sq ft" onto four lines in the one place that shows the
   price. Tighter cells, and the amount column takes only what it needs. (The
   table's own shadow is removed at every width in site.css.) */
.ms-quote-summary .ms-table th,
.ms-quote-summary .ms-table td { padding: var(--ms-space-3); }
.ms-quote-summary .ms-table td:last-child,
.ms-quote-summary .ms-table th:last-child { width: 1%; }

/* The "still needed" items are links to their steps; each is its own tap
   target, so it gets the full 44px like any other. */
.ms-quote-outcome__reasons a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* On a phone the portrait comes first: it introduces the person before her
   words, and it stops a 460px photo landing between the "See pricing" button
   and the Get in touch section it was pushing away. */
.ms-info-grid .ms-portrait { order: -1; }
.ms-contact-list__item { padding: var(--ms-space-4); }

/* Any standalone call-to-action button gets the full thumb column. */
.ms-prose__action .ms-btn,
.ms-404__actions .ms-btn { width: 100%; justify-content: center; }

/* ==========================================================================
   Second pass — the three things the first pass left behind.

   Measured on the live site after pass one: index 3390px, services 4754px
   (7.2 screens, the clear outlier), and one tap target still under 44px.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The hero put its own price widget below the fold.

   Measured at 390px: heading 159, lead 173, stat 73, then the photograph 165,
   and only then the quote card. The card — the one thing on this page that
   answers "what will it cost", and the reason the page exists — started at
   roughly 658px on a 664px viewport. It was, precisely, one pixel of it.

   Reordering costs nothing and moves it up by the height of the photograph
   plus a gap. The photograph is warmth; the card is the product. On a screen
   this size the product goes first and the warmth follows it.

   order on flex children only, so the DOM order is untouched: a screen reader
   and a crawler still read heading, lead, stat, photo, form in the authored
   sequence, and only the visual order changes.
   -------------------------------------------------------------------------- */
.ms-hero__visual { display: flex; flex-direction: column; }
.ms-hero__visual .ms-quote-start { order: 1; }
.ms-hero__visual .ms-hero__media { order: 2; }

/* On a phone the photograph moves below the card, so the estimator no longer
   sits ON the photo — it sat as a white raised card inside the white raised
   hero panel, a card in a card. It is now a section of the hero panel: no
   background or shadow of its own, a hairline above it (the one job borders
   have in this system), square-cornered because it has no edge to round. */
.ms-hero__visual .ms-quote-start {
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  border-top: 1px solid var(--ms-color-border);
  padding: var(--ms-space-5) 0 0;
}

/* 44px of headline is three lines of "Your home, squeaky clean." before the
   sentence explaining it. Two lines reads as a headline; three reads as a
   paragraph set in display type. */
.ms-hero__heading { font-size: clamp(2.125rem, 8vw, 2.5rem); }

.ms-hero__copy { display: flex; flex-direction: column; }
.ms-hero__lead { font-size: var(--ms-text-base); }

/* --------------------------------------------------------------------------
   2. The tap target that survived pass one.

   "See add-ons and travel fees" measured 181x19 — an inline link, so WCAG
   2.5.8's inline exemption technically covers it, but it sits alone at the end
   of a pill where it reads as a button rather than as a word in a sentence.
   When a link looks like a control it should be sized like one, whatever the
   exemption permits.
   -------------------------------------------------------------------------- */
.ms-services__note a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--ms-space-2);
}

/* --------------------------------------------------------------------------
   3. services.html — 4754px, 7.2 screens, the longest page on the site.

   It is long because it is a list of 47 things, and that list is the entire
   reason anyone opens this page. Cutting it would be cutting the content, so
   this does the two things that are actually available: make the page
   navigable, and stop it wasting height it does not need.

   The jump row is the navigability half — four anchors that already existed,
   surfaced at the top. Horizontal scroll rather than wrapping, so the row is
   one line at any width and cannot push the first panel further down the page
   than it already is.
   -------------------------------------------------------------------------- */
.ms-jump {
  display: block;
  position: sticky;
  /* Sits directly under the fixed nav pill, so it stays reachable while the
     visitor is deep in the fourth service and wants the second. */
  top: calc(5.25rem + var(--ms-safe-top));
  z-index: 5;
  /* No padding of its own: the <ul> carries it, inside its clipping box, so
     the chips' shadows have room. Same total height as before. */
  padding-block: 0;
  background: var(--ms-color-surface-base);
}

/* The five chips (four services and "Add-ons & fees") total well over the
   350px of usable width, so the row genuinely has to scroll — and a chip clipped flat at the edge reads as a
   broken layout rather than as "there is more this way". The fade says
   scrollable without spending height on a scrollbar or an arrow.

   The mask sits on the WRAPPER, never on the scrolling <ul> itself: a mask on
   the scroller travels with its content and the fade would slide away the
   moment the row moved. */
.ms-jump > .ms-container {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
}

.ms-jump ul {
  display: flex;
  gap: var(--ms-space-2);
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-x: auto;
  /* Momentum scrolling without a visible bar eating 12px of a 390px row. */
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;

  /* Room for the chips' shadows. overflow-x: auto clips on BOTH axes, and
     with zero vertical padding every chip's raised shadow ended in a hard
     horizontal line a few pixels below it — visible at 3x, read as a smudge
     at 1x. The fade that says "this row scrolls" lives on the wrapper above
     (a mask on the scroller travels with its content); it used to be
     declared here as well, which doubled the fade at the right edge. */
  padding: var(--ms-space-2) var(--ms-space-3);
  /* Pulled out by the same amount the side padding pushes in, so the first
     chip still aligns with the page gutter and the last one is never flush
     against the clipping edge. */
  margin-inline: calc(-1 * var(--ms-space-3));
}

.ms-jump ul::-webkit-scrollbar { display: none; }

/* space-3 rather than space-4: four chips at 16px of side padding measured
   388px of content against 342px of screen. Trimming 4px a side gives back
   32px, so on a normal phone the row is now a nudge from fitting rather than
   a third of a chip past the edge. The chips stay well over 44px wide, so no
   tap target is traded for it. */
.ms-jump a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--ms-space-3);
  white-space: nowrap;
  border-radius: var(--ms-radius-pill);
  background: var(--ms-color-surface);
  /* No shadow, like the nav chips these are the phone counterpart of. The
     raised-small shadow reaches 38px below a chip; inside a scroller that
     clips on both axes it was cut in a hard line however much padding the
     list carried, and a sticky row of six drop shadows was heavy anyway.
     A white pill on the page ground is the control's whole shape. */
  font-size: var(--ms-text-xs);
  font-weight: 700;
  color: var(--ms-color-blue-dark);
  text-decoration: none;
}

/* Density half. 47 rows at 33px each is 1550px of the page; at 28px it is
   1320px. Nothing is removed — the lines simply stop carrying desktop
   leading, which was tuned for a two-column list on a wide panel. */
.ms-check-list { gap: var(--ms-space-1); }

.ms-check-list li {
  gap: var(--ms-space-2);
  line-height: var(--ms-leading-snug);
}

.ms-service-panel { padding: var(--ms-space-5) var(--ms-space-4); }
.ms-service-panel__lead { font-size: var(--ms-text-sm); }
.ms-service-list { gap: var(--ms-space-4); }

/* The decorative banner is aria-hidden and carries no information. At 16:9 it
   still reads as a photograph of a real bathroom while giving back ~80px. */
.ms-photo-banner { aspect-ratio: 16 / 9; }

/* --------------------------------------------------------------------------
   Quote page — the price bar.

   Measured on the phone build: the estimate panel sits at roughly 3150px on
   an 812px viewport, four screens below the first question. The page lead
   promises "the price updates live as you go", and on the primary device
   every tap updated a number nobody could see. This bar mirrors the one line
   that matters — how many answers are still needed, or the total — and jumps
   to the panel. quote-form.js hides it whenever the panel itself is on
   screen, so it never covers the thing it points at and never sits over the
   footer.

   [hidden] must win over the class: the UA's [hidden] rule is outranked by
   .ms-quote-bar's display:flex, so the attribute alone would not hide it.
   -------------------------------------------------------------------------- */
.ms-quote-bar {
  position: fixed;
  left: var(--ms-space-3);
  right: var(--ms-space-3);
  bottom: calc(var(--ms-space-3) + var(--ms-safe-bottom));
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ms-space-3);
  min-height: 56px;
  padding: var(--ms-space-3) var(--ms-space-5);
  border-radius: var(--ms-radius-pill);
  background: var(--ms-color-surface);
  box-shadow: var(--ms-shadow-raised-md);
  color: var(--ms-color-ink);
  text-decoration: none;
}

.ms-quote-bar[hidden] { display: none; }

.ms-quote-bar__label {
  font-size: var(--ms-text-sm);
  font-weight: 700;
  color: var(--ms-color-ink-soft);
}

.ms-quote-bar__value {
  font-family: var(--ms-font-display);
  font-size: var(--ms-text-md);
  font-weight: 800;
  color: var(--ms-color-blue-dark);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The same "there is more this way" glyph the service tiles use. */
.ms-quote-bar__value::after { content: '\00a0\203A'; }
