/* Generated by tools/build.js — do not hand-edit. Edit the source files:
   css/fonts.css, tokens/colors.css, tokens/typography.css, tokens/spacing.css, tokens/effects.css, tokens/base.css, css/chrome.css, css/main.css */

/* ---- css/fonts.css ---- */
/* Generated by tools/self-host-fonts.mjs — do not hand-edit; re-run it instead.
   The brand fonts (Bricolage Grotesque, Hanken Grotesk, Space Mono), served
   from /fonts/ rather than Google Fonts since 2026-09-22. Bundled into every
   content page's CSS by tools/build.js and linked directly by the two tool
   pages (quote, live-data). */
/* vietnamese */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(/fonts/bricolage-grotesque-v9-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* vietnamese */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* vietnamese */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* vietnamese */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* vietnamese */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* vietnamese */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-v12-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/space-mono-v17-vietnamese-400.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/space-mono-v17-latin-ext-400.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/space-mono-v17-latin-400.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/space-mono-v17-vietnamese-700.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/space-mono-v17-latin-ext-700.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/space-mono-v17-latin-700.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- tokens/colors.css ---- */
/* Solar Pool Services — Color tokens
 * Palette derived directly from the hand-drawn brand logo (a vermilion sun
 * rising behind a swimmer in teal water, outlined in indigo, yellow cap) and
 * from real SoCal installation photography (blue pools, warm stucco, black mats).
 */
:root {
  /* ── Sun (primary brand) — the vermilion of the logo sun ───────────── */
  --sun-900: #7A2410;
  --sun-800: #A6321A;
  --sun-700: #C43C1E;
  --sun-600: #DC4620; /* PRIMARY brand color */
  --sun-500: #E85A2A;
  --sun-400: #F27B45;
  --sun-300: #F8A277;
  --sun-200: #FBC9AE;
  --sun-100: #FCE4D6;
  --sun-50:  #FDF1EA;

  /* ── Pool (secondary) — the teal water of the logo & real pools ──────── */
  --pool-900: #0F4753;
  --pool-800: #165A69;
  --pool-700: #1E6E7E;
  --pool-600: #268394;
  --pool-500: #2E93A3;
  --pool-400: #57ADB8;
  --pool-300: #8ACBCB; /* aqua highlight */
  --pool-200: #BCE2E0;
  --pool-100: #DEF1EF;
  --pool-50:  #EFF8F7;

  /* ── Indigo (accent) — the wave outline in the logo ──────────────────── */
  --indigo-700: #38316F;
  --indigo-600: #453B8C;
  --indigo-500: #574BA6;
  --indigo-300: #9A91CB;
  --indigo-100: #E4E1F1;

  /* ── Gold (highlight) — the swimmer's cap; sparing use ───────────────── */
  --gold-500: #E8AE13;
  --gold-400: #F4C025;
  --gold-200: #FBE49A;
  --gold-100: #FDF3D2;

  /* ── Warm neutrals — SoCal stucco & sand ─────────────────────────────── */
  --ink-900: #1E1A14; /* warm near-black — primary text */
  --ink-800: #2E281F;
  --ink-700: #423B30;
  --ink-600: #5C5344;
  --ink-500: #746A59;
  --ink-400: #9A8F7C;
  --ink-300: #C0B6A3;
  --sand-200: #E6DCCB;
  --sand-100: #F1EADC;
  --sand-50:  #FAF6EE;
  --white:    #FFFFFF;

  /* ── Semantic status ─────────────────────────────────────────────────── */
  --success-600: #2E7D52;
  --success-100: #DCEEE3;
  --warning-600: #C08A0E;
  --warning-100: #FBEFCF;
  --danger-600:  #C0402A;
  --danger-100:  #F7E0D9;

  /* ═══ Semantic aliases — reference these in components ════════════════ */
  --brand:            var(--sun-600);
  --brand-hover:      var(--sun-700);
  --brand-active:     var(--sun-800);
  --brand-soft:       var(--sun-100);
  --brand-on:         var(--white);

  --accent:           var(--pool-600);
  --accent-hover:     var(--pool-700);
  --accent-soft:      var(--pool-100);
  --accent-on:        var(--white);

  --text-strong:      var(--ink-900);
  --text-body:        var(--ink-700);
  --text-muted:       var(--ink-500);
  --text-faint:       var(--ink-400);
  --text-on-brand:    var(--white);
  --text-link:        var(--pool-700);
  --text-link-hover:  var(--sun-600);

  --surface-page:     var(--sand-50);
  --surface-card:     var(--white);
  --surface-sunk:     var(--sand-100);
  --surface-invert:   var(--ink-900);
  --surface-brand:    var(--sun-600);
  --surface-accent:   var(--pool-700);

  --border-subtle:    var(--sand-200);
  --border-default:   #DED3C0;
  --border-strong:    var(--ink-300);
  --border-focus:     var(--pool-500);
}

/* ---- tokens/typography.css ---- */
/* Solar Pool Services — Typography tokens */
:root {
  /* Families */
  /* No size-matched "fallback" faces between the brand font and system-ui:
     they were built and measured 2026-09-22 and made text paint later with
     no less movement. The why is in tools/self-host-fonts.mjs. */
  --font-display: 'Bricolage Grotesque', 'Hanken Grotesk', system-ui, sans-serif;
  --font-text:    'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono:    'Space Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Weights */
  --fw-regular: 400; /* @kind font */
  --fw-medium:  500; /* @kind font */
  --fw-semibold:600; /* @kind font */
  --fw-bold:    700; /* @kind font */
  --fw-black:   800; /* @kind font */

  /* Type scale — display uses tight tracking + line-height; text stays airy */
  --fs-display-xl: 4.5rem;   /* 72 — hero headline           */
  --fs-display-l:  3.375rem; /* 54 — page title              */
  --fs-display-m:  2.5rem;   /* 40 — section header          */
  --fs-display-s:  1.875rem; /* 30 — subsection              */
  --fs-title:      1.5rem;   /* 24 — card / block title      */
  --fs-lead:       1.25rem;  /* 20 — lead paragraph          */
  --fs-body:       1.0625rem;/* 17 — body                    */
  --fs-body-sm:    0.9375rem;/* 15 — secondary body          */
  --fs-caption:    0.8125rem;/* 13 — captions, labels        */
  --fs-overline:   0.75rem;  /* 12 — overline / eyebrow      */

  /* Line heights */
  --lh-tight:   1.04; /* @kind font */
  --lh-snug:    1.18; /* @kind font */
  --lh-heading: 1.28; /* @kind font */
  --lh-body:    1.6;  /* @kind font */
  --lh-relaxed: 1.72; /* @kind font */

  /* Letter spacing */
  --ls-display: -0.02em; /* @kind font */
  --ls-tight:   -0.01em; /* @kind font */
  --ls-normal:  0;       /* @kind font */
  /* eyebrows are UPPERCASE + tracked */
  --ls-overline: 0.14em; /* @kind font */
}

/* Mobile type scale — display sizes only. Desktop (≥641px) keeps the exact
   values above; without this, page titles render 54px on a 375px phone. */
@media (max-width: 640px) {
  :root {
    --fs-display-xl: 3rem;      /* 48 */
    --fs-display-l:  2.25rem;   /* 36 */
    --fs-display-m:  1.75rem;   /* 28 */
    --fs-display-s:  1.5rem;    /* 24 */
  }
}

/* ---- tokens/spacing.css ---- */
/* Solar Pool Services — Spacing & layout tokens
 * 4px base grid. Warm, generous spacing — the brand breathes.
 */
:root {
  --space-0:  0;
  --space-1:  0.25rem; /* 4  */
  --space-2:  0.5rem;  /* 8  */
  --space-3:  0.75rem; /* 12 */
  --space-4:  1rem;    /* 16 */
  --space-5:  1.5rem;  /* 24 */
  --space-6:  2rem;    /* 32 */
  --space-7:  2.5rem;  /* 40 */
  --space-8:  3rem;    /* 48 */
  --space-9:  4rem;    /* 64 */
  --space-10: 5rem;    /* 80 */
  --space-11: 7rem;    /* 112 */

  /* Layout */
  --container-max: 1200px;
  --container-narrow: 760px;
  --section-pad-y: clamp(3rem, 7vw, 7rem);
  --gutter: clamp(1.25rem, 4vw, 3rem); /* @kind spacing */
}

/* ---- tokens/effects.css ---- */
/* Solar Pool Services — Radii, borders, shadows, motion
 * Cards are gently rounded with soft, warm-tinted shadows (never hard/black).
 * Buttons round more. One "sun-ray" accent shadow for hero/brand moments.
 */
:root {
  /* Corner radii — friendly but not bubbly */
  --radius-xs:  4px;
  --radius-sm:  8px;
  --radius-md:  12px;  /* default card / input */
  --radius-lg:  18px;  /* feature cards */
  --radius-xl:  28px;  /* hero panels */
  --radius-pill: 999px;/* pills, tags, CTA buttons */

  /* Borders */
  --border-width: 1px; /* @kind other */
  --border-width-strong: 2px; /* @kind other */

  /* Shadows — warm brown-tinted, soft. Elevation, not drama. */
  --shadow-xs: 0 1px 2px rgba(46, 40, 31, 0.06);
  --shadow-sm: 0 1px 3px rgba(46, 40, 31, 0.08), 0 1px 2px rgba(46, 40, 31, 0.05);
  --shadow-md: 0 4px 14px rgba(46, 40, 31, 0.09), 0 2px 6px rgba(46, 40, 31, 0.05);
  --shadow-lg: 0 14px 34px rgba(46, 40, 31, 0.12), 0 6px 14px rgba(46, 40, 31, 0.07);
  --shadow-xl: 0 28px 60px rgba(46, 40, 31, 0.16), 0 12px 24px rgba(46, 40, 31, 0.08);
  /* Warm glow for brand/CTA hover — a hint of sunlight */
  --shadow-sun: 0 10px 28px rgba(220, 70, 32, 0.30);
  --shadow-focus: 0 0 0 3px rgba(46, 147, 163, 0.35); /* pool-tinted focus ring */

  /* Motion — gentle, no bounce. Trust, not flash. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);      /* @kind other */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);   /* @kind other */
  --dur-fast: 120ms; /* @kind other */
  --dur-base: 200ms; /* @kind other */
  --dur-slow: 340ms; /* @kind other */
}

/* ---- tokens/base.css ---- */
/* Solar Pool Services — Base element styles & helpers
 * Resets, base typography, link colors, and a few utility classes.
 */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-body);
  background: var(--surface-page);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Headings default to the display face */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--text-strong);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-tight);
  margin: 0 0 0.5em;
  text-wrap: balance;
}
h1 { font-size: var(--fs-display-l); line-height: var(--lh-tight); letter-spacing: var(--ls-display); }
h2 { font-size: var(--fs-display-m); }
h3 { font-size: var(--fs-display-s); }
h4 { font-size: var(--fs-title); }

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

/* Links — pool-teal by default, sun on hover */
a {
  color: var(--text-link);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
a:hover { color: var(--text-link-hover); }

strong, b { font-weight: var(--fw-bold); color: var(--text-strong); }

code, kbd, samp { font-family: var(--font-mono); font-size: 0.92em; }

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

::selection { background: var(--sun-200); color: var(--ink-900); }

/* A solid ring, not a glow (2026-09-23). It was `box-shadow: 0 0 0 3px` at 35% teal, which on
   the cream page measured barely off the background and vanished entirely on the orange and dark
   bands. An outline also follows the element's own corner radius, is not clipped by overflow:hidden
   parents, and survives Windows high-contrast mode, where box-shadow is dropped. --focus-ring flips
   to white inside the dark and orange sections (main.css). */
:focus-visible {
  outline: 3px solid var(--focus-ring, var(--pool-600));
  outline-offset: 2px;
}

/* ── Helper classes ─────────────────────────────────────────────────── */
.sps-eyebrow {
  font-family: var(--font-text);
  font-size: var(--fs-overline);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-overline);
  text-transform: uppercase;
  color: var(--brand);
}
.sps-lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-relaxed);
  color: var(--text-body);
}
.sps-mono-spec {
  font-family: var(--font-mono);
  font-size: var(--fs-body-sm);
  letter-spacing: -0.02em;
}
.sps-container { max-width: var(--container-max); margin-inline: auto; padding-inline: var(--gutter); }

/* ---- css/chrome.css ---- */
/* Site chrome (header + footer) — loaded on EVERY page including the two
 * self-contained tool pages (/quote/, /our-data/), which have their own page
 * CSS. Class names here must not collide with tool-page classes — the quote
 * button uses .sps-btn-quote (not .btn) for that reason. */

/* ---------- Header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  /* Notched phones on pages that opt into viewport-fit=cover (e.g. /our-data/):
     let the blurred bar fill behind the Dynamic Island and push its contents
     below the camera. A no-op everywhere else — the inset resolves to 0. */
  padding-top: env(safe-area-inset-top, 0px);
  background: rgba(255,255,255,0.92);
  -webkit-backdrop-filter: saturate(1.4) blur(8px); backdrop-filter: saturate(1.4) blur(8px);
  border-bottom: 1px solid var(--border-subtle);
  font-family: var(--font-text);
  line-height: 1.5;
}
.header-inner {
  max-width: var(--container-max); margin: 0 auto;
  /* Plain value first: a browser without max()/env() drops the declaration that
     follows entirely, and an unpadded header is worse than an un-inset one. Every
     safe-area rule in this file is paired with its fallback for that reason. */
  padding: 0.7rem var(--gutter);
  padding: 0.7rem max(var(--gutter), env(safe-area-inset-right, 0px)) 0.7rem max(var(--gutter), env(safe-area-inset-left, 0px));
  /* space-4 floor, not space-6: this gap is only the MINIMUM between the three
     clusters (nav's margin-left:auto spreads them on wide screens), and the
     six-link row needs the room — at space-6 the CTA overhung the 1200px
     container's padding box by ~29px. */
  display: flex; align-items: center; gap: var(--space-4);
}
.brand { display: flex; align-items: center; gap: 0.65rem; text-decoration: none; flex-shrink: 0; }
.brand img { height: 44px; width: auto; display: block; }
.brand-name {
  font-family: var(--font-display); font-weight: 800; font-size: 1.05rem;
  line-height: 1.15; color: var(--text-strong); letter-spacing: -0.01em;
}
/* No gap between links — each link's own 0.55rem side padding keeps ~18px
   between labels, and the six-link row is width-constrained (see above). */
.nav-desktop { display: flex; margin-left: auto; align-items: center; }
.nav-item { position: relative; }
.nav-link {
  /* 0.55rem side padding (was 0.65) — six top-level links + phone + CTA sit at
     ~1180px, and the extra 0.1rem per side pushed the row past the 1200px
     container. Keep real slack here: platforms render these fonts ±a few px. */
  display: block; padding: 0.55rem; border-radius: var(--radius-sm);
  font-family: inherit; font-weight: 600; font-size: 0.95rem; color: var(--text-body);
  text-decoration: none; white-space: nowrap;
}
/* Dropdown triggers are <button>s (open on click/touch, not navigation) but
   read as nav links — reset the UA button chrome so they match the <a> links. */
button.nav-link { border: 0; background: none; cursor: pointer; line-height: 1.5; }
.nav-link:hover { background: var(--sand-100); color: var(--brand); }
.nav-link[data-current], .nav-item:has([data-current]) > .nav-link { color: var(--brand); }
.nav-link:focus-visible { outline: 3px solid var(--pool-600); outline-offset: 2px; }
.nav-caret { display: inline-block; font-size: 0.85em; transition: transform var(--dur-fast) var(--ease-out); }
.nav-item:hover .nav-caret, .nav-item.is-open .nav-caret { transform: rotate(180deg); }

.nav-menu {
  position: absolute; top: 100%; left: 0; min-width: 210px; z-index: 20;
  background: #fff; border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
  border: 1px solid var(--border-subtle); padding: 0.4rem;
  display: flex; flex-direction: column;
  /* Hidden by default; fade + 4px slide-down on open. Kept in the DOM (not
     display:none) so the transition can run; visibility flips it off from AT
     and pointer events when closed. */
  opacity: 0; visibility: hidden; transform: translateY(4px);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              visibility 0s linear var(--dur-fast);
}
.nav-item:hover .nav-menu, .nav-item.is-open .nav-menu {
  opacity: 1; visibility: visible; transform: none; transition-delay: 0s;
}
.nav-menu a {
  padding: 0.55rem 0.7rem; border-radius: var(--radius-sm); color: var(--text-body);
  font-weight: 600; font-size: 0.9rem; text-decoration: none; white-space: nowrap;
}
.nav-menu a:hover { background: var(--sand-100); color: var(--brand); }
.nav-menu a[data-current] { color: var(--brand); }
/* Inset, so the ring cannot be clipped by the dropdown panel. */
.nav-menu a:focus-visible { outline: 3px solid var(--pool-600); outline-offset: -3px; }
.header-cta { display: flex; align-items: center; gap: 0.9rem; margin-left: auto; flex-shrink: 0; }
.nav-desktop + .header-cta { margin-left: 0; }
.header-phone {
  font-weight: 700; color: var(--text-strong); white-space: nowrap;
  font-size: 0.95rem; text-decoration: none;
}
.header-phone:hover { color: var(--brand); }
/* Desktop is text-only (no icon) per the nav spec; the icon only appears when
   the number collapses on narrow phones — see the <=640 block below. */
.header-phone-icon { display: none; line-height: 0; }
.sps-btn-quote {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  font-family: var(--font-text); font-weight: 700; font-size: 0.9375rem; line-height: 1;
  padding: 0.5rem 1rem; border-radius: var(--radius-pill);
  background: var(--brand); color: var(--brand-on); border: 2px solid transparent;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.sps-btn-quote:hover { background: var(--brand-hover); box-shadow: var(--shadow-sun); color: var(--brand-on); }
.sps-btn-quote:active { transform: translateY(1px) scale(0.99); }
.burger {
  display: none; background: none; border: 0; padding: 0.625rem; cursor: pointer;
  color: var(--text-strong); line-height: 0; border-radius: var(--radius-sm);
}
.burger .burger-close { display: none; }
.burger[aria-expanded="true"] .burger-open { display: none; }
.burger[aria-expanded="true"] .burger-close { display: inline; }
.mobile-menu {
  display: none; flex-direction: column; background: #fff;
  border-top: 1px solid var(--border-subtle); padding: 0.5rem var(--gutter) 1.2rem;
  box-shadow: var(--shadow-lg);
}
.mobile-menu:not([hidden]) { display: flex; }
.mm-link, .mm-sub {
  padding: 0.8rem 0; text-decoration: none; color: var(--text-strong);
  font-weight: 700; border-top: 1px solid var(--sand-100);
}
.mm-sub { padding-left: 1rem; font-weight: 600; color: var(--text-body); }
.mm-group {
  margin-top: 0.8rem; font-size: var(--fs-overline); font-weight: 700;
  letter-spacing: var(--ls-overline); text-transform: uppercase; color: var(--text-muted);
}
.mm-phone { margin-top: 1rem; padding: 0.8rem 0; font-weight: 800; color: var(--brand); text-decoration: none; }
.mm-quote {
  display: flex; align-items: center; justify-content: center; min-height: 48px; margin: 0.5rem 0 0.3rem;
  border-radius: var(--radius-pill, 999px); background: var(--brand); color: var(--brand-on, #fff);
  font-weight: 800; text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
  .nav-menu { transition: none; }
  .nav-caret { transition: none; }
}

/* ---------- Footer ---------- */
.site-footer { background: var(--ink-900); color: #fff; font-family: var(--font-text); line-height: 1.5; }
.footer-inner {
  max-width: var(--container-max); margin: 0 auto;
  padding: var(--space-9) var(--gutter) var(--space-6);
  padding: var(--space-9) max(var(--gutter), env(safe-area-inset-right, 0px)) calc(var(--space-6) + env(safe-area-inset-bottom, 0px)) max(var(--gutter), env(safe-area-inset-left, 0px));
}
.footer-grid { display: grid; grid-template-columns: 1.5fr 1fr 1fr 1fr 1.2fr; gap: var(--space-7) var(--space-6); }
.footer-col { display: flex; flex-direction: column; gap: 0.6rem; }
.footer-brand { display: flex; align-items: center; gap: 0.6rem; }
.footer-brand img { height: 46px; width: auto; display: block; }
.footer-brand span { font-family: var(--font-display); font-weight: 800; font-size: 1.05rem; line-height: 1.05; }
.footer-blurb { color: rgba(255,255,255,0.72); margin: 0.4rem 0 0; max-width: 30ch; font-size: 0.95rem; }
.footer-head {
  font-size: var(--fs-overline); font-weight: 700; letter-spacing: var(--ls-overline);
  text-transform: uppercase; color: var(--gold-400); margin-bottom: 0.3rem;
}
.site-footer a { color: rgba(255,255,255,0.82); font-size: 0.95rem; text-decoration: none; }
.site-footer a:hover { color: #fff; }
.footer-phone { font-weight: 700; }
.footer-hours { color: rgba(255,255,255,0.72); font-size: 0.9rem; }
.footer-bottom {
  margin-top: var(--space-7); padding-top: var(--space-5);
  border-top: 1px solid rgba(255,255,255,0.14);
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: 0.8rem; font-size: 0.85rem; color: rgba(255,255,255,0.6);
}

/* Legal row inside .footer-bottom (privacy / terms / accessibility / choices).
   The 100% lives on the ROW, not the nav, so the build credit can ride its
   right end and cost the page no extra height. */
.footer-legal-row {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 0.4rem 1.4rem;
}
.footer-legal { display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; }
.footer-legal a { color: rgba(255,255,255,0.6); text-decoration: none; }
.footer-legal a:hover { color: #FFFFFF; }

/* Build credit, bottom right. Two class names deep because the bare .site-footer
   a rule above is stronger than one — a lone .footer-credit loses the size. */
.footer-bottom .footer-credit {
  margin-left: auto; font-size: 0.8rem; color: rgba(255,255,255,0.45); text-decoration: none;
}
.footer-bottom .footer-credit:hover { color: rgba(255,255,255,0.75); }

/* Inline links in the footer-bottom text (e.g. the CSLB license verify link) */
.footer-bottom > span a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.footer-bottom > span a:hover { color: #FFFFFF; }

/* Skip-to-content link (WCAG 2.4.1) — visually hidden until keyboard focus */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 10000;
  background: var(--surface-card, #FFFFFF); color: var(--text-strong, #1E1A14);
  border: 2px solid var(--border-focus, #2E93A3);
  border-radius: var(--radius-pill, 999px);
  padding: 0.6rem 1.2rem; font-weight: 700; text-decoration: none;
}
.skip-link:focus { left: 12px; top: 12px; }
main:focus { outline: none; }

/* Consent microcopy under every lead-form submit button */
.form-consent {
  margin: 0.5rem 0 0; font-size: 0.8rem; line-height: 1.5;
  color: var(--text-muted, #746A59); text-align: center;
}
.form-consent a { color: var(--text-link, #1E6E7E); }

/* Turnstile slot — js/site.js renders the widget into it (empty = invisible) */
.form-turnstile { display: flex; justify-content: center; }
.form-turnstile:empty { display: none; }

/* ---------- Chrome responsive ---------- */
/* 1280px: the six-link nav (Services · Service Areas · How It Works · Live
   System Data · Resources · Contact) plus brand + phone + Get a Quote measures
   ~1180px, so it needs the full 1200px container a 1280 viewport provides;
   below that, collapse the whole link row into the hamburger panel. (The old
   1024px breakpoint was tuned for a 4-link nav and was already clipping once
   Live System Data made it five.) */
@media (max-width: 1280px) {
  .nav-desktop { display: none; }
  .burger { display: inline-flex; }
  /* Push the phone + CTA + hamburger cluster to the right edge. This needs the
     same selector specificity as the desktop `.nav-desktop + .header-cta`
     (0,2,0) rule that zeroes the margin — a bare `.header-cta` (0,1,0) loses to
     it and the whole cluster collapses to the left. */
  .nav-desktop + .header-cta { margin-left: auto; }
  .sps-btn-quote { min-height: 44px; }
  /* The open menu must never outgrow the screen — scroll inside it instead.
     (The header is sticky, so page scroll can't reveal the menu's bottom links.) */
  /* Three rungs, each one a superset of the last: no env() and no dvh → the first;
     env() but no dvh → the second (which still owes the header its inset-top, or
     the open menu overflows the screen by exactly that inset); both → the third. */
  .mobile-menu:not([hidden]) {
    max-height: calc(100vh - 66px);
    max-height: calc(100vh - 66px - env(safe-area-inset-top, 0px));
    max-height: calc(100dvh - 66px - env(safe-area-inset-top, 0px));
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(1.2rem + env(safe-area-inset-bottom));
  }
}
@media (max-width: 900px) {
  .footer-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 640px) {
  .brand-name { display: none; }
  /* Not enough room for the full number next to the logo + CTA + hamburger, so
     tap-to-call collapses to an icon button — mobile users are the callers, so
     one-tap dialing stays in the sticky header. */
  .header-phone-num { display: none; }
  .header-phone-icon { display: inline-flex; }
  .header-phone {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; color: var(--brand);
  }
  .header-inner { gap: var(--space-4); }
}
@media (max-width: 400px) {
  .sps-btn-quote { padding: 0.5rem 0.75rem; font-size: 0.875rem; }
  .header-cta { gap: 0.4rem; }
}
/* "Get a free quote" (2026-09-23; was "Get a Quote") is 26px longer, which put a 320px phone's header
   7px past the screen edge. */
@media (max-width: 340px) {
  .sps-btn-quote { padding: 0.5rem 0.55rem; font-size: 0.8125rem; }
}
@media (max-width: 560px) {
  /* Link columns stay 2-up rather than stacking — five stacked columns made the
     footer ~1.7 screens tall. The brand/blurb column spans the full width. */
  /* minmax(0,…) so the long support email can't blow one column out; it wraps. */
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6) var(--space-5); }
  .footer-col:first-child { grid-column: 1 / -1; }
  .footer-col a { overflow-wrap: anywhere; }
  .footer-col { gap: 0.2rem; }
  .footer-col a { padding: 0.45rem 0; }
}

/* ---- css/main.css ---- */
/* Solar Pool Services — site styles.
 * Component values ported 1:1 from the design system
 * (design_handoff_solar_pool_website/components/ + ui_kits/website/).
 * Tokens come from /tokens/*.css. */

/* ---------- Layout primitives ---------- */
.section { padding: var(--section-pad-y) 0; background: var(--surface-page); }
.section-sand { background: var(--sand-100); }
.section-ink { background: var(--ink-900); }
.section-pool-900 { background: var(--pool-900); }
.section-pool-800 { background: var(--pool-800); }
.section-sun { background: var(--sun-600); }
.container { max-width: var(--container-max); margin: 0 auto; padding: 0 var(--gutter); }
.container-narrow { max-width: var(--container-narrow); margin: 0 auto; padding: 0 var(--gutter); }
.center { text-align: center; }

.grid { display: grid; gap: var(--space-6); }
.grid-2 { grid-template-columns: 1fr 1fr; align-items: center; }
.grid-3 { grid-template-columns: 1fr 1fr 1fr; }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
.grid-2-uneven { grid-template-columns: 1.1fr 0.9fr; align-items: center; }
.grid-2-uneven-rev { grid-template-columns: 0.9fr 1.1fr; align-items: center; }
.grid-2x3 { grid-template-columns: 1fr 1fr; gap: var(--space-4); }

img { max-width: 100%; height: auto; }
.img-rounded { border-radius: var(--radius-lg); box-shadow: var(--shadow-md); display: block; }
.img-rounded-xl { border-radius: var(--radius-xl); box-shadow: var(--shadow-lg); display: block; }
/* tools/build.js wraps content photos in <picture> to offer WebP (2026-09-22).
   `contents` keeps the wrapper out of layout, so the <img> is still the grid or
   flex item every rule here was written for — and the <source>s must then be
   taken out of layout too, or each one becomes an empty grid cell of its own
   and pushes the photo onto the next row (it did, on every two-column hero).
   Source selection doesn't depend on display, so this changes nothing else. */
picture { display: contents; }
picture > source { display: none; }

/* Gallery grid tiles — uniform 4:3 crop with a caption beneath */
.gallery-item { margin: 0; }
.gallery-item img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); display: block;
}
.gallery-item figcaption {
  margin-top: var(--space-3); color: var(--text-muted); font-size: 0.95rem;
}

/* Before/after pair — matched 16:9 frames, each with a corner tag. Used on
 * /gallery/, /, and /installation/. */
.ba-frame { position: relative; }
.ba-frame img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); display: block;
}
.ba-tag {
  position: absolute; top: var(--space-3); left: var(--space-3);
  font-family: var(--font-mono); font-size: var(--fs-caption); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--white);
  padding: 0.3rem 0.75rem; border-radius: var(--radius-pill); box-shadow: var(--shadow-sm);
}
.ba-tag-before { background: var(--pool-700); }
.ba-tag-after  { background: var(--sun-600); }
/* .grid-2 centers its items, so the figure with the shorter caption gets
   nudged down and the two photos stop lining up. Pin both to the top. */
.ba-item { margin: 0; align-self: start; }
.ba-item figcaption { margin-top: var(--space-3); color: var(--text-muted); font-size: 0.95rem; }

/* (Header + footer chrome styles live in chrome.css, shared with the
 * self-contained tool pages at /quote/ and /our-data/.) */

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  font-family: var(--font-text); font-weight: 700; line-height: 1;
  border-radius: var(--radius-pill); white-space: nowrap; text-decoration: none;
  cursor: pointer; border: 2px solid transparent;
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out),
    background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
  padding: 12px 24px; font-size: 1.0625rem;
}
.btn-sm { padding: 8px 16px; font-size: 0.9375rem; }
.btn-lg { padding: 16px 32px; font-size: 1.1875rem; }
.btn:active { transform: translateY(1px) scale(0.99); }
.btn-primary { background: var(--brand); color: var(--brand-on); }
.btn-primary:hover { background: var(--brand-hover); box-shadow: var(--shadow-sun); color: var(--brand-on); }
.btn-secondary { background: var(--pool-700); color: #fff; }
.btn-secondary:hover { box-shadow: var(--shadow-md); color: #fff; }
.btn-outline { background: transparent; color: var(--text-strong); border-color: var(--border-strong); }
.btn-outline:hover { border-color: var(--brand); color: var(--brand); }
.btn-outline-white { background: transparent; color: #fff; border-color: rgba(255,255,255,0.75); }
.btn-outline-white:hover { border-color: #fff; color: #fff; background: rgba(255,255,255,0.1); }
.btn-full { width: 100%; }
.btn-row { display: flex; gap: var(--space-3); flex-wrap: wrap; }

/* ---------- Eyebrow / Badge / Stat ---------- */
.eyebrow {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-size: var(--fs-overline); font-weight: 700; letter-spacing: var(--ls-overline);
  /* sun-800, not --brand: at overline size, sun-600 is ~3.9–4.3:1 on white/sand
     (WCAG AA needs 4.5:1 for small text); sun-800 clears 5.6:1 on every surface
     up to sand-100. CTAs and large type keep --brand. */
  text-transform: uppercase; color: var(--sun-800); margin: 0 0 var(--space-3);
}
.eyebrow::before { content: ""; width: 28px; height: 2px; border-radius: 2px; background: currentColor; }
.eyebrow-gold { color: var(--gold-400); }
.eyebrow-center { justify-content: center; }

.badge {
  display: inline-flex; align-items: center; gap: 0.35rem; font-weight: 700;
  letter-spacing: 0.01em; border-radius: var(--radius-pill); line-height: 1.2;
  white-space: nowrap; padding: 5.6px 12.8px; font-size: var(--fs-caption);
}
.badge-brand { background: var(--sun-100); color: var(--sun-800); }
.badge-pool { background: var(--pool-100); color: var(--pool-800); }
.badge-indigo { background: var(--indigo-100); color: var(--indigo-700); }
.badge-gold { background: var(--gold-100); color: var(--warning-600); }
.badge-success { background: var(--success-100); color: var(--success-600); }
.badge-neutral { background: var(--sand-100); color: var(--ink-700); }
.badge-neutral-solid { background: var(--ink-800); color: #fff; }
.badge-row { display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* Badges that link out (the CSLB license lookup) keep the badge's own color
   rather than picking up the body link palette. */
a.badge { text-decoration: none; transition: filter 160ms ease-out; }
a.badge:hover { filter: brightness(0.96); }

.stat { display: flex; flex-direction: column; gap: 0.15rem; }
.stat-value {
  font-family: var(--font-display); font-weight: 800; font-size: var(--fs-display-m);
  line-height: 1; letter-spacing: var(--ls-display); color: var(--brand);
}
.stat-pool .stat-value { color: var(--pool-700); }
.stat-indigo .stat-value { color: var(--indigo-600); }
.stat-label { font-weight: 700; font-size: var(--fs-body); color: var(--text-strong); }
.stat-sublabel { font-size: var(--fs-body-sm); color: var(--text-muted); }

/* ---------- Cards ---------- */
.card {
  background: var(--surface-card); border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle); box-shadow: var(--shadow-md);
  padding: var(--space-6); position: relative; overflow: hidden;
}
.card-accent-top { border-top: 4px solid var(--sun-600); }

.service-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  background: var(--surface-card); border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle); box-shadow: var(--shadow-md);
  padding: var(--space-6); text-decoration: none; color: inherit;
  transition: box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.service-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-3px); }
.icon-chip {
  width: 52px; height: 52px; border-radius: var(--radius-md);
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  background: var(--sun-100); color: var(--sun-700);
}
.icon-chip-pool { background: var(--pool-100); color: var(--pool-700); }
.icon-chip-indigo { background: var(--indigo-100); color: var(--indigo-600); }
.icon-chip-sm { width: 40px; height: 40px; }
.icon-chip-sm svg { width: 20px; height: 20px; }
.service-card h2, .service-card h3 { font-size: var(--fs-title); margin: 0; }
.service-card p { margin: 0; flex-grow: 1; color: var(--text-body); }
.service-card .card-cta {
  display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 700;
  color: var(--brand); margin-top: var(--space-2);
}
.service-card .card-cta svg { transition: transform var(--dur-base) var(--ease-out); }
.service-card:hover .card-cta svg { transform: translateX(3px); }

.benefit-card {
  background: var(--surface-card); border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle); box-shadow: var(--shadow-sm);
  padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2);
}
.benefit-card h3 { font-size: var(--fs-body); font-family: var(--font-text); font-weight: 700; margin: 0; }
.benefit-card p { margin: 0; font-size: var(--fs-body-sm); color: var(--text-muted); }

/* ---------- Steps ---------- */
.step { display: flex; gap: var(--space-4); }
.step-marker-col { display: flex; flex-direction: column; align-items: center; flex: none; }
.step-marker {
  width: 48px; height: 48px; border-radius: var(--radius-pill); background: var(--brand);
  color: #fff; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 800; font-size: 1.375rem;
}
.step-rail { width: 2px; flex: 1; min-height: 16px; margin-top: var(--space-2); background: var(--border-default); }
.step-body { padding-bottom: var(--space-5); }
.step:last-child .step-body { padding-bottom: 0; }
.step-body h3 { font-size: var(--fs-title); margin: 0 0 0.35rem; }
.step-body p { margin: 0; color: var(--text-body); }

/* ---------- Testimonials ---------- */
.testimonial {
  display: flex; flex-direction: column; gap: var(--space-4); margin: 0;
  background: var(--surface-card); border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle); box-shadow: var(--shadow-md); padding: var(--space-6);
}
.testimonial .stars { display: flex; gap: 2px; color: var(--gold-400); }
.testimonial .stars svg { width: 20px; height: 20px; fill: currentColor; stroke: currentColor; }
.testimonial blockquote {
  margin: 0; font-size: var(--fs-lead); line-height: var(--lh-relaxed);
  color: var(--text-strong); font-weight: 500;
}
.testimonial figcaption { margin-top: auto; }
.testimonial .author { font-weight: 700; color: var(--text-strong); }
.testimonial .location { display: block; font-size: var(--fs-body-sm); color: var(--text-muted); }
/* Clickable testimonial cards (link out to the Google review). */
a.testimonial-link { display: flex; text-decoration: none; }
a.testimonial-link > .testimonial {
  flex: 1 1 auto;
  transition: box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
a.testimonial-link:hover > .testimonial { box-shadow: var(--shadow-lg); transform: translateY(-3px); }

/* ---------- Reviews page ----------
   This was a horizontal rail with prev/next buttons. Sideways scrolling on the
   one page whose entire job is reading reviews meant most of them were never
   seen — you had to work a scrollbar to get past card three. Now every review
   is on the page at once: one featured pull-quote, then the rest in a masonry
   column layout that packs the wildly different quote lengths with no dead
   space. No JS at all — CSS columns do the packing. */

/* Trust numbers under the intro. Left-aligned stats, centered as a row. */
.review-stats {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--space-6) var(--space-9); margin-top: var(--space-8);
}
.review-stats .stat { text-align: center; align-items: center; }
.review-stats .stat-label { font-weight: 600; color: var(--text-body); }

/* Featured pull-quote — sits straight on the pool-900 section with no card, so
   it reads as a headline instead of "the first review". */
.review-feature {
  display: block; max-width: 860px; margin: 0 auto var(--space-9);
  text-align: center; text-decoration: none;
}
.review-feature .review-mark {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: 4rem; line-height: 0.75; color: var(--gold-400);
  margin-bottom: var(--space-3);
}
.review-feature blockquote {
  margin: 0; color: #fff;
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  line-height: var(--lh-snug); letter-spacing: var(--ls-tight);
}
.review-feature .stars {
  display: flex; justify-content: center; gap: 3px;
  color: var(--gold-400); margin: var(--space-5) 0 var(--space-3);
}
.review-feature .stars svg { width: 22px; height: 22px; fill: currentColor; stroke: currentColor; }
.review-feature .review-byline {
  display: block; font-size: var(--fs-body-sm); font-weight: 600;
  color: rgba(255,255,255,0.8);
}
.review-feature .review-byline .author { color: #fff; font-weight: 700; }
.review-feature .review-source { color: var(--gold-400); }
.review-feature:hover .review-source { color: var(--gold-200); text-decoration: underline; }
.review-feature:focus-visible { outline: 2px solid #fff; outline-offset: 8px; border-radius: var(--radius-md); }

/* Masonry: CSS columns, not grid — grid would either stretch every card to the
   tallest in its row or leave ragged gaps. break-inside keeps a card whole. */
.review-grid { columns: 3; column-gap: var(--space-5); }
.review-grid > * {
  display: block; margin: 0 0 var(--space-5);
  break-inside: avoid; -webkit-column-break-inside: avoid;
}
.review-grid .testimonial { position: relative; padding: var(--space-5); }
/* Decorative open-quote in the corner the stars leave empty. */
.review-grid .testimonial::after {
  content: "\201C"; position: absolute; top: 2px; right: var(--space-4);
  font-family: var(--font-display); font-weight: 800; font-size: 3.5rem;
  line-height: 1; color: var(--sand-200); pointer-events: none;
}
.review-grid blockquote { font-size: var(--fs-body); line-height: var(--lh-body); }
.review-grid figcaption {
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: auto; padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.review-avatar {
  flex: none; width: 40px; height: 40px; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 800; font-size: 0.875rem;
  background: var(--pool-100); color: var(--pool-800);
}
/* Rotate the avatar tints so a column doesn't read as one flat block of teal. */
.review-grid > *:nth-child(4n+2) .review-avatar { background: var(--sun-100); color: var(--sun-800); }
.review-grid > *:nth-child(4n+3) .review-avatar { background: var(--indigo-100); color: var(--indigo-700); }
.review-grid > *:nth-child(4n+4) .review-avatar { background: var(--gold-100); color: var(--warning-600); }
.review-who { display: flex; flex-direction: column; min-width: 0; }
.review-grid .author, .review-grid .location { display: block; }
.review-grid .review-source {
  margin-left: auto; flex: none; display: inline-flex; color: var(--text-link);
  transition: transform var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
.review-grid .testimonial-link:hover .review-source { transform: translateX(3px); color: var(--brand); }
/* pool-900 behind the cards swallows the teal focus ring. */
.review-grid .testimonial-link:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: var(--radius-lg); }

.review-outro { text-align: center; margin-top: var(--space-8); }
.review-outro p { margin: 0 0 var(--space-5); color: rgba(255,255,255,0.75); font-size: var(--fs-body-sm); }
.review-outro-cta { margin: var(--space-6) 0 0 !important; font-size: var(--fs-body) !important; color: rgba(255,255,255,0.9) !important; }
.review-outro-cta a { color: #fff; text-decoration: underline; }
.review-outro-cta a:hover { color: var(--gold-400); }

/* ---------- Accordion ---------- */
.accordion { border-top: 1px solid var(--border-subtle); }
.accordion-item { border-bottom: 1px solid var(--border-subtle); }
.accordion-q {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); background: none; border: 0; cursor: pointer; text-align: left;
  padding: var(--space-5) 0; font-family: var(--font-display); font-weight: 700;
  font-size: var(--fs-title); color: var(--text-strong);
  transition: color var(--dur-fast) var(--ease-out);
}
.accordion-item.open .accordion-q { color: var(--brand); }
.accordion-q .acc-icon {
  flex: none; color: var(--brand); line-height: 0;
  transition: transform var(--dur-base) var(--ease-out);
}
.accordion-item.open .acc-icon { transform: rotate(45deg); }
.accordion-a {
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height var(--dur-slow) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}
.accordion-item.open .accordion-a { max-height: 100vh; opacity: 1; }
.accordion-a p { margin: 0; padding: 0 0 var(--space-5); max-width: 60ch; color: var(--text-body); }

/* ---------- Forms ---------- */
.field { display: flex; flex-direction: column; gap: 0.4rem; }
.field > label { font-weight: 600; font-size: var(--fs-body-sm); color: var(--text-strong); }
.field .req { color: var(--brand); }
.field .hint { font-size: var(--fs-caption); color: var(--text-muted); }
.field .error-msg { font-size: var(--fs-caption); color: var(--danger-600); }
/* Soft qualifier inside a group label, e.g. "Service details (check all that apply)" */
.field .hint-inline { font-weight: 400; color: var(--text-muted); }
/* Checkbox group (service details). Wraps to as many rows as it needs. */
.lf-checks { display: flex; flex-wrap: wrap; gap: 0.5rem var(--space-4); }
.lf-checks label {
  display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer;
  font-size: var(--fs-body-sm); color: var(--text-body);
}
.lf-checks input[type="checkbox"] { accent-color: var(--brand); width: 16px; height: 16px; cursor: pointer; }
.lf-checks input[type="checkbox"]:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
.input, .select, .textarea {
  width: 100%; font-family: var(--font-text); font-size: var(--fs-body);
  color: var(--text-strong); background: #fff; border: 1.5px solid var(--border-default);
  border-radius: var(--radius-md); padding: 0.7rem 0.9rem; outline: none; box-sizing: border-box;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.input:focus, .select:focus, .textarea:focus { border-color: var(--border-focus); box-shadow: var(--shadow-focus); }
.input.invalid, .select.invalid, .textarea.invalid { border-color: var(--danger-600); }
.select-wrap { position: relative; }
.select { appearance: none; padding-right: 2.4rem; cursor: pointer; }
.select-wrap::after {
  content: ""; position: absolute; right: 0.9rem; top: 50%; width: 10px; height: 10px;
  border-right: 2.5px solid var(--text-muted); border-bottom: 2.5px solid var(--text-muted);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.textarea { resize: vertical; min-height: 120px; line-height: var(--lh-body); }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.form-grid .full { grid-column: 1 / -1; }
.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ---------- Misc patterns ---------- */
.chip {
  display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.5rem 0.9rem;
  background: var(--sand-100); border-radius: var(--radius-pill);
  font-weight: 600; font-size: var(--fs-body-sm); color: var(--text-strong);
}
.chip svg { color: var(--sun-600); }
.chip-ghost { background: transparent; border: 1.5px dashed var(--border-strong); color: var(--text-muted); }
.chip-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* Chips that navigate (the /services/ jump row): same look, quiet hover. */
a.chip {
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
a.chip:hover { background: var(--sand-200); color: var(--text-strong); }

.check-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.check-list li { display: flex; gap: 0.6rem; align-items: flex-start; color: var(--text-body); }
.check-list svg { flex: none; color: var(--pool-600); margin-top: 2px; }
.check-list-success svg { color: var(--success-600); }

.pricing-table {
  width: 100%; border-collapse: collapse; background: var(--surface-card);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); overflow: hidden;
}
/* overflow-x:auto (not hidden): on narrow phones the table pans inside its own
   wrapper instead of the price column getting clipped off unreachable. */
.pricing-wrap { border-radius: var(--radius-lg); overflow-x: auto; overflow-y: hidden; box-shadow: var(--shadow-md); }
.pricing-table th {
  background: var(--ink-900); color: #fff; text-align: left; font-weight: 700;
  font-size: var(--fs-body-sm); padding: var(--space-4) var(--space-5);
}
.pricing-table td { padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border-subtle); }
.pricing-table tbody tr:nth-child(even) { background: var(--sand-50); }
.pricing-table .price { text-align: right; font-family: var(--font-mono); color: var(--pool-700); font-weight: 700; white-space: nowrap; }
.pricing-table th:last-child { text-align: right; }
/* Top-down pool glyphs in the size column, drawn to relative scale (√ of surface area) */
.pricing-table .size-label { display: inline-flex; align-items: center; gap: var(--space-3); white-space: nowrap; }
.pricing-table .pool-glyph { flex: none; width: 56px; height: 28px; }
.pricing-table .pool-glyph rect { fill: var(--pool-300); stroke: var(--pool-600); stroke-width: 2; }

/* ---------- Hero patterns ---------- */
.hero-dark { position: relative; background: var(--ink-900); overflow: hidden; }
.hero-dark .hero-bg {
  position: absolute; inset: 0; background-size: cover; background-position: center;
  opacity: 0.55;
}
.hero-bg-home {
  background-image: url('/assets/install-crew.jpg');
  /* WebP where the browser takes it (2026-09-22); the line above is the fallback.
     tools/build.js preloads the same two files — change them together. */
  background-image: image-set(url('/assets/install-crew.webp') type('image/webp'), url('/assets/install-crew.jpg') type('image/jpeg'));
}
.hero-dark .hero-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(rgba(30,26,20,0.35), rgba(30,26,20,0.55) 55%, rgba(30,26,20,0.85));
}
.hero-dark .container { position: relative; }
.hero-content { max-width: 720px; padding: clamp(3.5rem, 8vw, 7rem) 0; }
.hero-pill {
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: rgba(255,255,255,0.14); color: #fff; border-radius: var(--radius-pill);
  padding: 0.45rem 1rem; font-weight: 600; font-size: var(--fs-body-sm);
  margin-bottom: var(--space-5);
}
.hero-pill svg { color: var(--gold-400); }
a.hero-pill { text-decoration: none; transition: background var(--dur-base) var(--ease-out); }
a.hero-pill:hover { background: rgba(255,255,255,0.24); }
.hero-dark h1 { color: #fff; font-size: clamp(2.15rem, 6vw, 4.5rem); line-height: var(--lh-tight); margin: 0 0 var(--space-4); }
.hero-dark .lead { color: rgba(255,255,255,0.88); }
.hero-sub { margin-top: var(--space-5); color: rgba(255,255,255,0.75); font-size: var(--fs-body-sm); }
.hero-sub a { color: var(--gold-400); font-weight: 700; }
.hero-sub a:hover { color: var(--gold-200); }

.lead { font-size: var(--fs-lead); line-height: var(--lh-relaxed); color: var(--text-body); }
.on-dark, .on-dark h1, .on-dark h2, .on-dark h3 { color: #fff; }
.on-dark .lead, .on-dark p { color: rgba(255,255,255,0.9); }
/* The focus ring (tokens/base.css) turns white wherever the ground is dark or orange: teal on the
   sun-600 bands measured about 1.6:1, i.e. invisible. White clears 3:1 on all of them. */
.hero-dark, .on-dark, .section-sun, .section-ink, .section-pool-900, .section-pool-800, .how-stage, .site-footer { --focus-ring: #fff; }

.stats-band { display: flex; justify-content: space-between; gap: var(--space-5); flex-wrap: wrap; }
.stats-band .stat-value, .stats-band .stat-label { color: #fff; }
.stats-band .stat-label { font-weight: 600; opacity: 0.9; }

.section-head { max-width: 720px; margin: 0 auto var(--space-7); text-align: center; }
.section-head h2 { margin: 0 0 var(--space-3); }
.section-head .lead { margin: 0; }

.info-row { display: flex; gap: var(--space-4); align-items: center; }
.info-row > div { min-width: 0; }
.info-row .info-value { overflow-wrap: anywhere; }
.info-row .icon-chip { width: 42px; height: 42px; }
.info-row .info-label {
  font-size: var(--fs-overline); font-weight: 700; letter-spacing: var(--ls-overline);
  text-transform: uppercase; color: var(--text-muted);
}
.info-row .info-value { font-weight: 700; color: var(--text-strong); }
.info-row .info-value a { color: var(--text-strong); text-decoration: none; }
.info-row .info-value a:hover { color: var(--brand); }

.success-state { text-align: center; padding: var(--space-6) 0; }
.success-check {
  width: 64px; height: 64px; border-radius: 50%; margin: 0 auto var(--space-4);
  background: var(--success-100); color: var(--success-600);
  display: flex; align-items: center; justify-content: center;
}

/* ---------- Home page (2026-09-22) ---------- */
/* The repair shortcut under the hero buttons — same gold as the licence link. */
.hero-urgent { margin: var(--space-4) 0 0; font-weight: 700; }
.hero-urgent a { color: var(--gold-400); text-decoration: none; }
.hero-urgent a:hover { color: var(--gold-200); text-decoration: underline; }
.hero-urgent + .hero-sub { margin-top: var(--space-3); }
/* The H1 is the search phrase, set small; the big line under it is the pitch,
   styled exactly as the H1 used to be. Google weighs the heading, the customer
   reads the big line, and the title tag leads with the same words. (0,2,1) so it
   beats .hero-dark h1, which every other dark hero still uses as-is. */
.hero-dark h1.hero-kicker {
  font-size: var(--fs-lead); line-height: var(--lh-snug); letter-spacing: normal;
  color: var(--gold-400); margin: 0 0 var(--space-3);
}
.hero-display {
  font-family: var(--font-display); font-weight: var(--fw-bold); color: #fff;
  font-size: clamp(2.15rem, 6vw, 4.5rem); line-height: var(--lh-tight);
  letter-spacing: var(--ls-display); margin: 0 0 var(--space-4); text-wrap: balance;
}

/* Swim season: two bars on a Jan–Dec track. The bars carry no text; the label
   beside each one says the same months, so the chart reads in greyscale and to
   a screen reader (role="img" + aria-label on .season). */
.season { max-width: 880px; margin: 0 auto; }
.season-row { display: grid; grid-template-columns: 11rem 1fr; gap: var(--space-4); align-items: center; }
.season-row + .season-row { margin-top: var(--space-3); }
.season-label { display: flex; flex-direction: column; font-size: var(--fs-body-sm); color: var(--text-muted); line-height: 1.3; }
.season-label strong { color: var(--text-strong); font-size: var(--fs-body); }
.season-ticks { display: grid; grid-template-columns: repeat(12, 1fr); text-align: center; font-size: var(--fs-caption); color: var(--text-muted); }
.season-track {
  position: relative; height: 2.25rem; background: var(--surface-card);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-pill);
}
.season-bar {
  position: absolute; top: 3px; bottom: 3px; border-radius: var(--radius-pill);
  left: calc(var(--from) / 12 * 100%); width: calc((var(--to) - var(--from)) / 12 * 100%);
}
.season-bar-cold { background: var(--pool-300); }
.season-bar-warm { background: var(--sun-600); }
.season-note { max-width: 720px; margin: var(--space-5) auto 0; text-align: center; font-size: var(--fs-body-sm); color: var(--text-muted); }
.season-stats { margin-top: var(--space-7); text-align: center; }
.season-stats .stat { align-items: center; }

/* Service-area cards: the heading is the link to the area page; city names
   inside the text may link to their own pages, so the card itself cannot. */
.area-card h3 { font-size: var(--fs-title); margin: 0 0 var(--space-2); }
.area-card h3 a { color: var(--text-strong); text-decoration: none; display: inline-flex; align-items: center; gap: 0.4rem; }
.area-card h3 a svg { color: var(--brand); flex: none; transition: transform var(--dur-base) var(--ease-out); }
.area-card h3 a:hover { color: var(--brand); }
.area-card h3 a:hover svg { transform: translateX(3px); }
.area-card p { margin: 0; color: var(--text-body); }

/* ---------- Home page (2026-09-23): the combined page ---------- */
/* Text links that end in an arrow stay INLINE, with the icon on the last line of the text. As
   inline-flex, a wrapped link became a full-width box and its arrow floated off to the far right. */
.hero-urgent a svg, .how-more a svg, .how-skip a svg, .area-list h3 a svg {
  display: inline-block; vertical-align: -0.2em; margin-left: 0.3em;
}
/* Trust band: the Google rating is the one item that links (to the profile). */
.stats-band .stat-link { text-decoration: none; color: #fff; }
.stats-band .stat-link .stat-value { display: inline-flex; align-items: center; gap: 0.3rem; }
.stats-band .stat-link svg { width: 0.72em; height: 0.72em; fill: var(--gold-400); stroke: var(--gold-400); }
.stats-band .stat-link:hover .stat-label { text-decoration: underline; text-underline-offset: 3px; }

/* How it works. ONE markup, two layouts (docs/js/home.js picks):
     default     — four cards with a still each, in the page's own light style. What reduced
                   motion, Save-Data, no WebGL, no JavaScript and the Claude pane get.
     .how--stage — the scene (.how-view) pins FULL-SCREEN and the step cards scroll over it;
                   docs/js/home3d/stage.js moves the camera with the scroll, and home.js turns the
                   site header transparent while it is over the scene (body.hdr-dark).
   Nothing in either is hidden until something fires: the cards are always visible text. */
.how { padding-bottom: var(--space-7); }
.how .section-head { margin-bottom: var(--space-6); }
.how-skip { display: none; margin: var(--space-4) 0 0; font-weight: 700; font-size: var(--fs-body-sm); }
/* Keyboard only (the site's .skip-link pattern): a way out of a pinned section for someone tabbing, but
   not a teal "skip this" as the last thing a phone reader sees before the part that teaches. */
.how--stage .how-skip { position: relative; height: 0; margin: 0; }
.how--stage .how-skip a { position: absolute; white-space: nowrap; left: 50%; top: var(--space-3); transform: translate(-50%, -200%); opacity: 0; pointer-events: none; }
.how--stage .how-skip a:focus-visible { transform: translate(-50%, 0); opacity: 1; pointer-events: auto; z-index: 5; }

.how-view { display: none; }
/* The whole loop, lit — the opening shot's own poster — above the four close-ups, so a visitor who gets
   the still layout sees the one picture that explains it all at once. The stage never shows it. */
.how-whole { max-width: var(--container-max); margin: 0 auto var(--space-6); padding: 0 var(--gutter); }
.how-whole img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius-lg); background: #faf6ee; box-shadow: var(--shadow-md); }
.how--stage .how-whole { display: none; }
/* Phones: the portrait poster, cropped 4:5, so the loop is twice the size it is in a 16:9 strip. */
@media (max-width: 640px) { .how-whole img { aspect-ratio: 4 / 5; } }
.how-beats {
  list-style: none; margin: 0 auto; padding: 0 var(--gutter); max-width: var(--container-max);
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-6);
}
.how-beat {
  background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); overflow: hidden; display: flex; flex-direction: column;
}
.how-still img { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; background: #faf6ee; }
/* v18: the last beat is a camera stop with no card (the sensors view). The still layout has no camera, so it
   has nothing to show there. */
.how:not(.how--stage) .how-beat--view { display: none; }

.how-card { padding: var(--space-5) var(--space-6) var(--space-6); }
/* "01 The pump" — the review page's step label, in the page's colours when it sits in a light card. */
.how-eyebrow {
  display: flex; align-items: center; gap: 0.75rem; margin: 0 0 var(--space-2);
  font-family: var(--font-mono); font-size: var(--fs-overline); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--sun-800);
}
.how-eyebrow::before { content: ""; width: 26px; height: 1px; background: currentColor; }
.how-eyebrow .num { color: var(--text-muted); }
.how-card h3 { margin: 0 0 var(--space-2); font-size: var(--fs-title); }
.how-card p:last-child { margin: 0; }
.how-more { margin: var(--space-6) 0 0; text-align: center; font-weight: 700; }

/* ── Stage layout ──────────────────────────────────────────────────────────────────────────────
   The view is the whole screen while the section is pinned: top 0, 100vh (the LARGE viewport, so a
   phone's toolbar sliding away uncovers more scene rather than a strip of page), under a header that
   has gone transparent. Keith, 2026-09-23: the white bar over the top of it and the frame around it
   were what made the scene read as muted — measured, the render itself is identical to the review
   page's, pixel for pixel; it was the presentation.
   v16 (2026-09-27): the scene is an architectural model in a dark slate studio, not a dusk photograph, so the
   section's darks went from warm (#12111b, the dusk sky) to the model's backdrop: #0a0c10 is scene.js LOOK.bg's
   zenith, drawn untone-mapped, so the canvas fading in over the stage does not shift its tint.
   v18 (2026-09-28): Keith picked v16w's look, the white model on the page's own cream, so the section is light
   again: #faf6ee (--sand-50) is LOOK.bg now, and the cards, chips, dots and the header over the scene went light
   with it (v16w's stage-light.css, carried into these rules rather than laid over them). */
.how--stage .how-skip { display: block; }
.how--stage .how-stage { position: relative; background: #faf6ee; margin-top: var(--space-5); --focus-ring: var(--ink-900, #1e1a14); }
.how--stage .how-view {
  display: block; position: sticky; top: 0; height: 100vh; overflow: hidden; background: #faf6ee;
}
.how-poster img, .how-canvas { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.how-canvas { opacity: 0; transition: opacity 0.8s var(--ease-out); }
.how-view.is-live .how-canvas { opacity: 1; }
/* (v18: no film grain. It was the dark studio's; v16w dropped it on the white model.) */
/* Chips: the review page's glass pills with a glowing dot. */
.how-labels { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.how-lbl {
  position: absolute; left: 0; top: 0; opacity: 0; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 10px; padding: 9px 14px 9px 11px;
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-800, #2e2922); background: rgba(255, 255, 255, 0.86); border: 1px solid rgba(30, 26, 20, 0.12);
  border-radius: var(--radius-pill);
  -webkit-backdrop-filter: blur(14px) saturate(1.1); backdrop-filter: blur(14px) saturate(1.1);
  box-shadow: 0 8px 24px rgba(66, 59, 48, 0.14);
  will-change: transform, opacity;
}
/* A dot's colour is the temperature of the water it points at: blue going up, orange coming back —
   including "Back into your pool", which is warm water arriving (it was teal in v14). v16: the cool dot is the
   cool line's own blue (scene.js LOOK.cool); teal now means the pool water. */
.how-lbl i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-700, #4a4338); flex: none; box-shadow: 0 0 0 3px rgba(74, 67, 56, 0.18); }
.how-lbl--cool i { background: #1f6fe3; box-shadow: 0 0 0 3px rgba(31, 111, 227, 0.18); }
.how-lbl--warm i { background: #f05a1a; box-shadow: 0 0 0 3px rgba(240, 90, 26, 0.18); }
/* data-dot chips are placed by their dot, not their middle (stage.js): the dot sits ON the part. --flip
   mirrors the pill so the dot is on its right, for a part near the right edge of the view. */
.how-lbl--flip { flex-direction: row-reverse; padding: 9px 11px 9px 14px; }
/* The side dots, from the review page. Desktop only, as there: a hover affordance, and on a phone the
   thumb's side of the screen belongs to the scroll. */
.how-dots {
  position: absolute; right: 26px; top: 50%; transform: translateY(-50%); z-index: 2;
  display: grid; gap: 14px; opacity: 0; transition: opacity 0.35s var(--ease-out);
}
/* Only while the view is pinned (stage.js sets how--pinned): as the section let go, the rail and the
   current step's name rode up into the header beside "Get a free quote". */
.how--live.how--pinned .how-dots { opacity: 1; }
.how-dots a { position: relative; display: block; width: 10px; height: 10px; border-radius: 50%; background: rgba(30, 26, 20, 0.18); transition: background 0.3s, transform 0.3s var(--ease-out); }
.how-dots a span {
  position: absolute; right: 22px; top: 50%; transform: translateY(-50%); white-space: nowrap;
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-600, #5e5649); opacity: 0; transition: opacity 0.25s; pointer-events: none;
}
.how-dots a:hover span, .how-dots a:focus-visible span, .how-dots a.is-active span { opacity: 1; }
.how-dots a.is-active { background: var(--sun-500); transform: scale(1.35); box-shadow: 0 0 0 4px rgba(232, 90, 42, 0.25); }
@media (max-width: 860px) { .how-dots { display: none; } }
/* The cards scroll over the pinned view: pulled up by the view's own height, with a lead-in so the
   opening shot shows before the first card arrives, and a tail so the last one lands before the view
   lets go (arithmetic in stage.js, schedule()). */
.how--stage .how-beats {
  /* Lead-in 92svh: the opening shot (the whole loop, lit) HOLDS for the first 30% of a screen of scroll
     before the camera heads for the pump (stage.js, schedule()), and the first card still has its 62svh
     to arrive. Tail 34svh: the last step's camera lands with its card centred, half a screen before the
     end of the beats, and the view has to stay pinned through that plus a moment's hold. */
  display: block; max-width: none; padding: 92svh 0 34svh; position: relative; margin-top: -100vh;
}
.how--stage .how-beat {
  min-height: 62svh; background: none; border: 0; box-shadow: none; overflow: visible; border-radius: 0;
  display: flex; flex-direction: row; align-items: center;
  max-width: calc(var(--container-max) + 120px); margin: 0 auto; padding: 0 var(--gutter);
}
.how--stage .how-still { display: none; }
/* The review page's glass cards: cream glass over the white model (v18; slate glass on v16's dark studio, warm
   glass before that), big display type, the orange step label. */
.how--stage .how-card {
  width: min(540px, 100%); padding: clamp(26px, 3.2vw, 42px); border-radius: var(--radius-xl);
  background: linear-gradient(160deg, rgba(255, 253, 249, 0.86), rgba(250, 246, 238, 0.66));
  border: 1px solid rgba(30, 26, 20, 0.08);
  -webkit-backdrop-filter: blur(18px) saturate(1.1); backdrop-filter: blur(18px) saturate(1.1);
  box-shadow: 0 24px 60px rgba(66, 59, 48, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.how--stage .how-eyebrow { margin-bottom: 18px; font-size: 0.72rem; color: var(--sun-600, #dc4620); }
.how--stage .how-eyebrow .num { color: var(--ink-500, #7a7163); opacity: 1; }
.how--stage .how-card h3 {
  margin: 0 0 20px; color: var(--ink-900, #1e1a14); font-family: var(--font-display); font-weight: 800;
  font-size: clamp(2rem, 4.2vw, 3.7rem); line-height: 1.04; letter-spacing: -0.025em; text-wrap: balance;
}
/* :not(.how-eyebrow): the step label is a <p> too, and this rule (0,2,1) outranked .how-eyebrow's (0,2,0),
   which turned the small orange "01 The pump" into a 17px grey line that read like a step counter. */
.how--stage .how-card p:not(.how-eyebrow) { color: var(--ink-700, #4a4338); font-size: 1.08rem; line-height: 1.6; max-width: 46ch; }
.how--stage .how-more { margin-top: var(--space-6); }
/* The review page's choreography: the step you are on is lit, the others step back. Only once the
   scene is running (.how--live), which is only after the visitor has scrolled — never a resting state
   a crawler could see. */
.how--live .how-beat .how-card { opacity: 0; transform: translateY(28px); transition: opacity 0.45s cubic-bezier(0.65, 0, 0.35, 1), transform 0.45s cubic-bezier(0.65, 0, 0.35, 1); }
.how--live .how-beat.is-active .how-card { opacity: var(--hf, 1); transform: none; transition: opacity 0.7s var(--ease-out), transform 0.9s var(--ease-out); }
/* Leaving under the header (--hf, stage.js): the fade follows the scroll, so no opacity transition behind it. */
.how--live .how-beat.is-active .how-card.is-leaving { transition: transform 0.9s var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .how--live .how-beat .how-card { transform: none; } }

/* ── The header over dark ground (home only) ───────────────────────────────────────────────────
   body.hdr-dark is set by docs/js/home.js while the header sits over the pinned 3D scene:
   the review page's see-through top bar, so the picture runs to the top of the screen. Never while the
   phone menu is open — its panel is white and wants the ordinary header above it. */
body.hdr-dark:not(:has(#mobile-menu:not([hidden]))) .site-header {
  /* v18: over the white model the header keeps its dark text, on a cream fade instead of the dark one. */
  background: linear-gradient(180deg, rgba(250, 246, 238, 0.92) 0%, rgba(250, 246, 238, 0) 100%);
  /* border-box + no-repeat: the gradient is sized to the padding box by default and REPEATS into the
     1px transparent border, which drew its top edge as a line under the header. */
  background-origin: border-box; background-repeat: no-repeat;
  -webkit-backdrop-filter: none; backdrop-filter: none; border-bottom-color: transparent;
}

.live-proof {
  display: flex; align-items: center; justify-content: center; gap: var(--space-3); flex-wrap: wrap;
  margin: var(--space-7) auto 0; max-width: 720px; text-align: center; font-weight: 600; color: var(--text-strong);
}
.live-proof svg { color: var(--brand); flex: none; }

.rating-link { color: var(--gold-400); font-weight: 700; text-decoration: none; }
.rating-link:hover { color: var(--gold-200); text-decoration: underline; text-underline-offset: 3px; }

.extras {
  list-style: none; margin: var(--space-6) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-5);
}
.extras li { font-size: var(--fs-body-sm); color: var(--text-body); padding-left: var(--space-4); border-left: 3px solid var(--sun-200); }
.extras strong { display: block; color: var(--text-strong); font-size: var(--fs-body); }

.certs { margin-top: var(--space-7); padding-top: var(--space-6); border-top: 1px solid var(--border-subtle); }
.certs-head {
  margin: 0 0 var(--space-5); text-align: center; font-family: var(--font-mono); font-size: var(--fs-overline);
  font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted);
}
.certs-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-5); }
.certs-list li { display: flex; align-items: center; gap: var(--space-3); font-size: var(--fs-body-sm); color: var(--text-body); }
.certs-list img, .certs-mark { width: 52px; height: 52px; object-fit: contain; flex: none; }
.certs-mark {
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
  background: var(--ink-800); color: #fff; font-family: var(--font-mono); font-weight: 700; font-size: 0.75rem;
}
.certs-list strong { display: block; color: var(--text-strong); }

.area-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
.area-list li {
  background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
}
.area-list h3 { margin: 0 0 0.2rem; font-size: 1.125rem; }
.area-list h3 a { color: var(--text-strong); text-decoration: none; }
.area-list h3 a svg { color: var(--brand); transition: transform var(--dur-base) var(--ease-out); }
.area-list h3 a:hover { color: var(--brand); }
.area-list h3 a:hover svg { transform: translateX(3px); }
.area-list p { margin: 0; font-size: var(--fs-body-sm); color: var(--text-body); }

.next-steps {
  list-style: none; counter-reset: step; margin: var(--space-6) auto 0; padding: 0; text-align: left;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4);
}
.next-steps li {
  counter-increment: step; position: relative; padding: var(--space-4) var(--space-4) var(--space-4) 3.25rem;
  background: rgba(255, 255, 255, 0.1); border-radius: var(--radius-md);
}
.next-steps li::before {
  content: counter(step); position: absolute; left: var(--space-4); top: var(--space-4);
  width: 1.6rem; height: 1.6rem; border-radius: 50%; background: #fff; color: var(--sun-700);
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 0.9rem;
}
.next-steps strong { display: block; color: #fff; line-height: var(--lh-snug); }
.next-steps span { display: block; margin-top: 0.2rem; font-size: var(--fs-body-sm); color: rgba(255, 255, 255, 0.9); }
.quote-proof { margin: var(--space-5) 0 0; font-size: var(--fs-body-sm); color: rgba(255, 255, 255, 0.88); }

/* Phone call bar — only ≤860px, and only once no on-page button row is on screen (home.js). */
.callbar { display: none; }
@media (max-width: 860px) {
  .callbar {
    display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    padding: 10px var(--space-3) calc(10px + env(safe-area-inset-bottom, 0px));
    background: rgba(250, 246, 238, 0.97); border-top: 1px solid var(--border-default);
    box-shadow: 0 -6px 20px rgba(46, 40, 31, 0.12);
    transform: translateY(110%); visibility: hidden;
    /* visibility follows the slide out, so a parked bar is not a pair of invisible links in the tab order */
    transition: transform 280ms var(--ease-out), visibility 0s linear 280ms;
  }
  .callbar.is-up { transform: none; visibility: visible; transition: transform 280ms var(--ease-out), visibility 0s; }
  /* The open phone menu carries both of the bar's links itself. */
  body:has(#mobile-menu:not([hidden])) .callbar { transform: translateY(110%); visibility: hidden; }
  .callbar a {
    /* gap, not a space in the markup: a flex container drops whitespace at the edges of its items */
    display: flex; align-items: center; justify-content: center; gap: 0.3em; min-height: 48px; padding: 0 var(--space-3);
    border-radius: var(--radius-pill); font-weight: 700; font-size: 0.9375rem; text-decoration: none; white-space: nowrap;
  }
  .callbar-call { color: var(--text-strong); border: 2px solid var(--ink-700); }
  .callbar-quote { background: var(--brand); color: var(--brand-on); }
  /* The bar is the phone's quote button on this page, so the header's goes (the burger menu keeps
     the link) — one screen, one of each button. Room for the bar under the footer's last line. */
  .has-callbar .sps-btn-quote { display: none; }
  .has-callbar .site-footer { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
  /* ...and the step cards settle above the bar, not behind it. */
  .has-callbar .how--stage .how-beat { padding-bottom: calc(var(--space-4) + 70px + env(safe-area-inset-bottom, 0px)); }
  /* Over the pinned scene the bar goes dark glass, like the header above it (body.cb-dark, home.js): a
     cream slab across the foot of every frame was the phone's version of the white header Keith asked
     to have off the scene. Same buttons, same place; the quote pill stays orange. */
  .callbar { transition: transform 280ms var(--ease-out), visibility 0s linear 280ms, background-color 0.3s, border-color 0.3s; }
  .callbar.is-up { transition: transform 280ms var(--ease-out), visibility 0s, background-color 0.3s, border-color 0.3s; }
  body.cb-dark .callbar {
    background: rgba(250, 246, 238, 0.78); border-top-color: rgba(30, 26, 20, 0.1); box-shadow: none;   /* (v18: cream glass over the white model) */
    -webkit-backdrop-filter: blur(16px) saturate(1.2); backdrop-filter: blur(16px) saturate(1.2);
  }
}
@media (max-width: 360px) { .callbar-num { display: none; } }

@media (max-width: 900px) {
  .extras { grid-template-columns: 1fr; gap: var(--space-4); }
  .certs-list { grid-template-columns: repeat(2, 1fr); }
  .area-list { grid-template-columns: repeat(2, 1fr); }
  .next-steps { grid-template-columns: 1fr; max-width: 420px; }
}
@media (max-width: 640px) {
  .how-beats { grid-template-columns: 1fr; gap: var(--space-5); }
  .how-card { padding: var(--space-4) var(--space-5) var(--space-5); }
  /* A plain divided list on a phone: five cards' borders, shadows and gaps were ~150px of chrome. */
  .area-list { grid-template-columns: 1fr; gap: 0; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); }
  .area-list li { padding: var(--space-3) var(--space-4); border: 0; border-radius: 0; background: none; }
  .area-list li + li { border-top: 1px solid var(--border-subtle); }
  .page-home #services .service-card { padding: var(--space-5); gap: var(--space-2); }
  .page-home #services .icon-chip { width: 40px; height: 40px; }
  .certs-list { gap: var(--space-4) var(--space-3); }
  .certs-list li { flex-direction: column; align-items: flex-start; gap: var(--space-2); font-size: var(--fs-caption); }
  .certs-list img, .certs-mark { width: 40px; height: 40px; }
  /* The outcome figures sit three across even on a phone; the one-line explanations under them go,
     because the section's lead and note already say each of those things in a sentence. */
  .grid.season-stats { grid-template-columns: repeat(3, 1fr); gap: var(--space-3); margin-top: var(--space-6); }
  .season-stats .stat-value { font-size: 1.6rem; }
  .season-stats .stat-label { font-size: var(--fs-caption); line-height: 1.3; }
  .season-stats .stat-sublabel { display: none; }
  /* Reviews become a row you swipe, the next card peeking in from the edge so it reads as more. Every
     card is still in the page and reachable by keyboard; nothing is hidden, only side by side. */
  #reviews .grid-3 {
    display: flex; overflow-x: auto; scroll-snap-type: x mandatory; gap: var(--space-4);
    margin: 0 calc(-1 * var(--gutter)); padding: 4px var(--gutter) var(--space-3); scrollbar-width: none;
  }
  #reviews .grid-3::-webkit-scrollbar { display: none; }
  #reviews .grid-3 > .testimonial-link { flex: 0 0 86%; scroll-snap-align: center; }
  .extras li { padding-left: var(--space-3); }
}
/* A portrait screen (phones, most tablets upright) puts the card across the foot of the view,
   so the subject has the space above it — the framing v14's KEY_PORTRAIT was tuned for. */
@media (orientation: portrait), (max-width: 640px) {
  /* 50svh a step on a portrait screen: a flick per step. The lead-in is longer than the desktop's
     because a portrait step's camera lands when its card reaches the FOOT of the screen (stage.js), so
     the first card has further to come before the opening shot hands over; +30svh of it is the opening
     shot's hold, as on the desktop. */
  .how--stage .how-beats { padding: 120svh 0 10svh; }
  .how--stage .how-beat { align-items: flex-end; min-height: 50svh; padding-bottom: var(--space-4); }
  /* The review page's phone card: full width, 22px corners, 16px body. */
  .how--stage .how-card { width: 100%; padding: 22px; border-radius: 22px; }
  .how--stage .how-eyebrow { margin-bottom: 12px; }
  .how--stage .how-card h3 { font-size: 1.75rem; margin-bottom: 10px; }
  .how--stage .how-card p:not(.how-eyebrow) { font-size: 1rem; line-height: 1.55; }
}
/* A phone on its side (390px tall): the card is centred in the room between the header and the call bar,
   and compact, as v14's were. Before this the phones' call-bar clearance (86px under each step, meant for
   a portrait card at the foot) pushed every centred card up under the header at its own keyframe. */
@media (orientation: landscape) and (max-height: 500px) {
  .how--stage .how-beat, .has-callbar .how--stage .how-beat { align-items: center; padding-top: 72px; padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px)); }
  .how--stage .how-card { width: min(440px, 100%); padding: 18px 22px; border-radius: 20px; }
  .how--stage .how-eyebrow { margin-bottom: 8px; }
  .how--stage .how-card h3 { font-size: 1.5rem; margin-bottom: 8px; }
  .how--stage .how-card p:not(.how-eyebrow) { font-size: 0.95rem; line-height: 1.45; }
}

/* ---------- Responsive (breakpoints per design: 980/900/640/560) ---------- */
@media (max-width: 980px) {
  .review-grid { columns: 2; }
}
@media (max-width: 900px) {
  .grid-2, .grid-3, .grid-4, .grid-2-uneven, .grid-2-uneven-rev { grid-template-columns: 1fr; }
  .grid-2x3 { grid-template-columns: 1fr 1fr; }
  .form-grid { grid-template-columns: 1fr; }
  .stats-band { justify-content: flex-start; gap: var(--space-6); }
}
@media (max-width: 640px) {
  .br-lg { display: none; }
  .hero-bg-home {
    background-image: url('/assets/install-crew-800.jpg');
    background-image: image-set(url('/assets/install-crew-800.webp') type('image/webp'), url('/assets/install-crew-800.jpg') type('image/jpeg'));
  }
  .review-grid { columns: 1; }
  .review-stats { gap: var(--space-5) var(--space-7); }
}
@media (max-width: 560px) {
  .season-row { grid-template-columns: 1fr; gap: var(--space-2); }
  .season-months .season-label { display: none; }
  .season-label { flex-direction: row; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; }
  .season-ticks { font-size: 0.68rem; }
  .grid-2x3 { grid-template-columns: 1fr; }
  .btn-row .btn { width: 100%; }
  .pricing-table th, .pricing-table td { padding: var(--space-3); font-size: var(--fs-body-sm); }
  .pricing-table .price { white-space: normal; }
  .pricing-table .pool-glyph { width: 44px; height: 22px; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ---------- More Info (article hub + article pages) ---------- */
.article-meta {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  font-size: var(--fs-body-sm); font-weight: 600; color: var(--text-muted);
}
.article-meta span { display: inline-flex; align-items: center; gap: 0.4rem; }
.article-meta svg { color: var(--sun-600); flex: none; }
.article-card .article-meta { font-size: var(--fs-caption); }

.back-link {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-weight: 700; font-size: var(--fs-body-sm); color: var(--pool-700);
  /* padding + negative margin: ~44px tap target, pixel-identical position */
  padding: 0.6rem 0.25rem;
  margin: -0.6rem -0.25rem calc(var(--space-5) - 0.6rem);
}
.back-link svg { flex: none; transform: rotate(180deg); transition: transform var(--dur-base) var(--ease-out); }
.back-link:hover { color: var(--brand); }
.back-link:hover svg { transform: rotate(180deg) translateX(3px); }

.article-body h2 { margin: var(--space-7) 0 var(--space-3); }
.article-body h3 { margin: var(--space-5) 0 var(--space-2); }
.article-body p { color: var(--text-body); }
.article-body ul, .article-body ol {
  margin: var(--space-3) 0 var(--space-4); padding-left: 1.3rem;
  display: flex; flex-direction: column; gap: var(--space-2); color: var(--text-body);
}
.article-body a { font-weight: 600; }
.article-body a:hover { color: var(--brand); }

/* ---------- Buying-guide article blocks ---------- */
.article-figure { margin: var(--space-6) 0 0; }
.article-figure img {
  width: 100%; aspect-ratio: 21 / 9; object-fit: cover; display: block;
  border-radius: var(--radius-xl); box-shadow: var(--shadow-lg);
}
.article-figure figcaption {
  margin-top: var(--space-3); text-align: center;
  font-size: var(--fs-caption); color: var(--text-muted);
}

/* "Look for: … / Avoid: …" verdict line closing a component card */
.verdict { margin: 0; font-size: var(--fs-body-sm); }
.verdict .look { font-weight: 700; color: var(--pool-700); }
.verdict .avoid { font-weight: 700; color: var(--sun-700); margin-left: var(--space-2); }

/* Mono label above a card title ("Best for · most new installations").
   sun-800/pool-800 rather than --brand for the same reason .eyebrow uses
   sun-800: at caption size sun-600 misses 4.5:1 on white and sand.
   These live on <p>, so they are element-qualified to clear `.article-body p`
   (0,1,1) — a bare class selector loses that fight and inherits body color. */
p.card-label {
  font-family: var(--font-mono); font-weight: 700; font-size: var(--fs-caption);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--sun-800); margin: 0 0 var(--space-2);
}
p.card-label-pool { color: var(--pool-800); }
p.card-sub {
  font-family: var(--font-mono); font-size: var(--fs-caption);
  color: var(--text-muted); margin: 0.25rem 0 var(--space-2);
}
.card-title-row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.card-title-row h3 { margin: 0; }

/* Pool-tinted callout with an action parked on the right */
.callout {
  background: var(--pool-50); border: 1px solid var(--pool-200);
  border-radius: var(--radius-lg); padding: var(--space-5) var(--space-6);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
}
.callout h3 { margin: 0 0 0.35rem; font-size: var(--fs-title); }
.callout p { margin: 0; font-size: var(--fs-body-sm); max-width: 46ch; }

/* Display-weight closing statement, and the brand-ruled pull quote */
p.pull {
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-title);
  line-height: var(--lh-heading); color: var(--text-strong); text-wrap: pretty;
}
.pullquote {
  border-left: 3px solid var(--brand);
  padding: 0.2rem 0 0.2rem var(--space-5); margin: var(--space-6) 0;
}
.pullquote p {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-title);
  line-height: var(--lh-relaxed); color: var(--text-strong); margin: 0;
}

/* Footnote marker + note. The strikethrough and the "*" are both purely visual,
   so the struck heading carries an .sr-only phrase — a screen reader announces
   neither line-through nor a bare asterisk. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.fn-mark { color: var(--sun-800); font-weight: 700; }
.benefit-card h3 s { text-decoration-thickness: 2px; text-decoration-color: var(--sun-700); }
p.footnote {
  margin: var(--space-4) 0 0; font-size: var(--fs-body-sm); color: var(--text-muted);
}

/* Two-up ✗/✓ diagram pair (one-piece C-clamp vs two-piece header bracket).
   Tiles carry their own flex-basis, so they drop to one column on narrow
   screens without a media query. The ✗/✓ reads in brand sun/pool rather than
   the danger/success tokens, to match the .verdict line closing each card. */
.clamp-figs { display: flex; flex-wrap: wrap; gap: var(--space-4); margin: var(--space-4) 0; }
.clamp-fig { flex: 1 1 260px; margin: 0; }
.clamp-frame {
  position: relative; background: var(--sand-50);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  overflow: hidden;
}
/* Child combinator: the badge icon is also inside .clamp-frame, and must keep
   its own 16px intrinsic size rather than stretch to the frame width. */
.clamp-frame > svg { display: block; width: 100%; height: auto; }
.clamp-badge {
  position: absolute; top: var(--space-3); left: var(--space-3); z-index: 2;
  width: 34px; height: 34px; border-radius: var(--radius-pill);
  display: flex; align-items: center; justify-content: center;
  color: var(--white); box-shadow: var(--shadow-sm);
}
.clamp-badge-no { background: var(--sun-700); }
.clamp-badge-yes { background: var(--pool-700); }
.clamp-cap {
  display: flex; gap: 0.5rem; align-items: flex-start;
  margin-top: var(--space-3);
  font-size: var(--fs-body-sm); color: var(--text-body);
}
.clamp-cap svg { flex: none; margin-top: 2px; }
.clamp-cap strong { color: var(--text-strong); }
.clamp-cap-no svg { color: var(--sun-700); }
.clamp-cap-yes svg { color: var(--pool-700); }

@media (max-width: 640px) {
  .callout { justify-content: flex-start; }
}

/* ---------- Repair guides ----------
   Responsive video slot for the optional per-guide embed. aspect-ratio rather
   than the old padding-top hack, so the iframe needs no wrapper of its own. */
.guide-video {
  aspect-ratio: 16 / 9; border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--shadow-md); background: var(--ink-800);
}
.guide-video iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ---------- Print ----------
   The printable "doc" half of a repair guide is this rule, and nothing else:
   the page IS the handout, so a guide's written steps and its printout are the
   same text and can never drift apart. Drops the site chrome and anything
   screen-only (.no-print), flattens the section rhythm, and forces the FAQ
   accordions open — a collapsed <div> prints as an empty box, which on paper
   reads as a missing answer rather than a closed one. Applies site-wide because
   every page here is worth printing cleanly, not just the guides. */
@media print {
  .site-header, .site-footer, .mobile-menu, .skip-link,
  #sps-staging-flag, .no-print { display: none !important; }

  html, body { background: #fff !important; }
  .section { padding: 0 0 var(--space-6) !important; background: #fff !important; }
  .section.on-dark, .section.on-dark h1, .section.on-dark h2,
  .section.on-dark h3, .section.on-dark p { color: #000 !important; }
  .container, .container-narrow { max-width: none; padding: 0; }
  .section-head { max-width: none; text-align: left; }
  .eyebrow-center { justify-content: flex-start; }

  .card, .benefit-card, .service-card, .callout {
    box-shadow: none !important; border: 1px solid #bbb !important;
    background: #fff !important;
  }
  .card, .benefit-card, .service-card, .callout, .step, figure, img {
    break-inside: avoid;
  }
  h1, h2, h3 { break-after: avoid; }

  /* Collapsed by a max-height/opacity transition on screen — both have to be
     unwound or the answers print as blank space. */
  .accordion-a { max-height: none !important; opacity: 1 !important; }

  /* The brand marks still read in greyscale; solid fills waste toner. */
  .step-marker { background: #444 !important; color: #fff !important; }
  .icon-chip { background: #eee !important; color: #333 !important; }
}
