@font-face {
    font-family: "Spectral";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/spectral-latin-400-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Spectral";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/fonts/spectral-latin-500-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Spectral";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/fonts/spectral-latin-600-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Spectral";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/assets/fonts/spectral-latin-700-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Spectral";
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/spectral-latin-400-italic.woff2") format("woff2");
}

@font-face {
    font-family: "Public Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/public-sans-latin-400-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Public Sans";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/fonts/public-sans-latin-500-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Public Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/fonts/public-sans-latin-600-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Public Sans";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/assets/fonts/public-sans-latin-700-normal.woff2") format("woff2");
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-mono-latin-600-normal.woff2") format("woff2");
}

/* 配色方案 1b 专用。刻意不 preload:只有主动切到 1b 的用户才需要下载。 */
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-sans-latin-700-normal.woff2") format("woff2");
}

:root {
    /* Warm Editorial tokens (SPEC / Redesign-Spec §2) */
    --we-bg: #f1ede4;
    --we-bg-gradient: linear-gradient(180deg, #efe9dd, #f1ede4);
    --we-card: #ffffff;
    --we-card-sub: #fbf9f4;
    --we-band: #f2eee5;
    --we-selected: #f6ece1;
    --we-quote-bg: #fbf2ea;
    --we-quote-border: #ecccb4;
    /* Redesign-Spec §8: the outline on the white secondary buttons that sit *inside* the
       quote card. It reads darker than --we-border because it has to hold up against the
       warm #fbf2ea fill. It was hardcoded in .quote-cta-card .button.secondary; the lead
       panel now shares the same card, so it graduates to the token layer. */
    --we-quote-outline: #e0d3c2;
    --we-border: #e6e0d4;
    --we-border-row: #f3eee3;
    --we-border-input: #ddd5c6;
    --we-accent: #c2582a;
    --we-accent-deep: #a24a22;
    --we-accent-dim: #8a5a30;
    --we-accent-soft: #b08050;
    --we-dark: #2a2620;
    --we-text: #2a2620;
    --we-text-2: #5a5348;
    --we-text-3: #7a7365;
    --we-text-4: #9a9284;
    --we-green-bg: #eaf0e4;
    --we-green-fg: #5d7a4a;
    --we-green-card: #f0f4ea;
    --we-green-border: #d9e3cc;
    --we-green-deep: #4f6a3a;
    /* 首页 readiness badge 的三个状态各占一个语义色族:quote=强调色(可直接去比价)、
       review=琥珀警示(先本地核实)、fast=绿色(即时可买)。此前 quote 的底色/字色写死在
       .readiness-badge 里,切到 1b 冷色主题时不跟随;review 又只覆盖了 background,
       文字色继承那份写死的暖棕,在冷蓝底上撞色。三组的对比度均已按 WCAG AA 4.5:1 校验
       (11px/600 够不上大字豁免),不要在不重算对比度的情况下改这些值。 */
    --we-badge-quote-bg: #f6e3d6;
    --we-badge-quote-fg: #974c22;
    --we-badge-review-bg: #fbeed4;
    --we-badge-review-fg: #7a5312;
    --we-dot-warn: #d99b3a;
    --we-dot-excl: #c2582a;
    --we-dot-assume: #7a9b6a;
    --we-dot-neutral: #9a9284;
    --we-radius-card: 14px;
    --we-radius-sm: 9px;
    --we-radius-pill: 20px;
    --we-shadow-card: 0 4px 16px rgba(120, 90, 50, 0.05);
    --we-shadow-float: 0 4px 20px rgba(120, 90, 50, 0.06);
    --we-shadow-btn: 0 4px 14px rgba(194, 88, 42, 0.28);
    --we-font-serif: "Spectral", Georgia, serif;
    --we-font-sans: "Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
    --we-font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
    /* legacy aliases -> new values (retire per-phase) */
    --canvas: var(--we-bg);
    --ink: var(--we-text);
    --muted: var(--we-text-3);
    --panel: var(--we-card);
    --line: var(--we-border);
    --accent: var(--we-accent);
    --accent-strong: var(--we-accent-deep);
    --sage: var(--we-green-bg);
    --shadow: var(--we-shadow-card);
}

/* ---------------------------------------------------------------------------
   配色方案 1b「Technical Blueprint」——冷蓝灰。
   SPEC: docs/superpowers/specs/2026-08-10-palette-language-toggle-design.md §4.2

   本块只重定义自定义属性,不含任何具体 CSS 属性。9 个 legacy 别名
   (--canvas/--ink/--muted/--panel/--line/--accent/--accent-strong/--sage/--shadow)
   指向 --we-*,会自动跟随,无需在此重复。
   圆角、--we-font-mono、--we-card 两套同值,刻意不覆盖。

   --we-accent 用的是加深版 #1f6f85 而不是设计稿的亮青 #5db3c9:后者在白底上
   对比度只有 2.40,而本令牌有 19 处当文字色使用。亮青归位到 --we-accent-soft。
   --we-dot-excl 脱离 accent 单独取珊瑚红:1b 的青色 accent 读不出"警示"语义。
   --------------------------------------------------------------------------- */
[data-palette="1b"] {
    --we-bg: #eef1f4;
    --we-bg-gradient: linear-gradient(180deg, #e7ecf1, #eef1f4);
    --we-card-sub: #f6f8fa;
    --we-band: #eef1f4;
    --we-selected: #e3f0f4;
    --we-quote-bg: #eaf3f7;
    --we-quote-border: #b8dde7;
    --we-quote-outline: #c3d3dc;
    --we-border: #d3dae1;
    --we-border-row: #e8edf1;
    --we-border-input: #cdd5dd;
    --we-accent: #1f6f85;
    --we-accent-deep: #155a6e;
    --we-accent-dim: #2f7d92;
    --we-accent-soft: #5db3c9;
    --we-dark: #1e2833;
    --we-text: #1e2833;
    --we-text-2: #4a5a68;
    --we-text-3: #67788a;
    --we-text-4: #818f9d;
    --we-green-bg: #e2eee8;
    --we-green-fg: #2f6650;
    --we-green-card: #eaf3ef;
    --we-green-border: #cbe0d6;
    --we-green-deep: #245244;
    /* badge quote 跟随本主题的冷青强调色;review 刻意保留琥珀,理由与 dot-excl 取
     * 珊瑚红一致——1b 的青色读不出"警示"语义,三个状态必须能一眼区分。 */
    --we-badge-quote-bg: #dfeef2;
    --we-badge-quote-fg: #155a6e;
    --we-badge-review-bg: #f6e9cd;
    --we-badge-review-fg: #6b4a10;
    --we-dot-warn: #b5791d;
    --we-dot-excl: #c4462f;
    --we-dot-assume: #4d8676;
    --we-dot-neutral: #67788a;
    --we-shadow-card: 0 4px 16px rgba(40, 70, 90, 0.06);
    --we-shadow-float: 0 4px 20px rgba(40, 70, 90, 0.08);
    --we-shadow-btn: 0 4px 14px rgba(31, 111, 133, 0.28);
    --we-font-serif: "IBM Plex Sans", system-ui, sans-serif;
    --we-font-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
}

* {
    box-sizing: border-box;
}

/* 2026-07-30 预留滚动条槽位。站内页面高矮不一(例如 /calculators 会滚动、
   /account/projects 不会),经典滚动条出现/消失会让布局视口宽度变化 ~15px,
   居中的 .site-shell 随之横移约 7.5px——表现为页面间切换时整条导航跳一下。
   macOS 的叠加式滚动条本来就没有这个问题,这条只影响 Windows/Linux。 */
html {
    scrollbar-gutter: stable;
}

/* 页脚沉底:内容不足一屏时,页脚此前会浮在视口中间、下方露出背景色。
   .app-feedback 是 position: fixed,不参与流,因此 body 的直接流内子元素
   只有 header / main / footer 三个,纵向 flex 不改变它们的排列顺序。 */
body {
    margin: 0;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    font-family: var(--we-font-sans);
    color: var(--ink);
    background: var(--we-bg);
}

body > main {
    flex: 1 0 auto;
}

h1,
h2,
h3,
.brand {
    font-family: var(--we-font-serif);
}

a {
    color: var(--accent-strong);
    text-decoration: none;
}

img,
canvas {
    max-width: 100%;
}

.site-shell {
    width: min(1240px, calc(100% - min(80px, 8vw)));
    margin: 0 auto;
}

.site-header {
    position: sticky;
    top: 0;
    z-index: 10;
    backdrop-filter: blur(8px);
    background: rgba(251, 249, 244, 0.92);
    border-bottom: 1px solid var(--we-border);
}

.nav-row {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    min-height: 56px;
}

.brand {
    font-size: 18px;
    font-weight: 700;
    letter-spacing: normal;
    color: var(--we-accent);
}

.site-nav {
    display: flex;
    gap: 22px;
    flex-wrap: wrap;
    align-items: center;
    margin-left: 20px;
    margin-right: auto;
}

.site-nav a {
    font-size: 13.5px;
}

.site-nav a:not(.nav-cta) {
    color: #6b6558;
}

.site-nav a:not(.nav-cta):hover {
    color: var(--we-accent);
}

.site-nav a.is-current {
    color: var(--we-accent);
}

.site-header a:focus {
    outline: none;
}

.site-header a:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: 2px;
}

/* 文字型导航链接:焦点指示用下划线而非外框——部分引擎在 mousedown 窗口会瞬时命中
   :focus-visible,外框会闪成"按钮框";下划线在该瞬态下不刺眼,键盘可达性不丢 */
.site-nav a:focus,
.site-nav a:focus-visible {
    outline: none;
    text-decoration: underline;
    text-underline-offset: 4px;
}

.site-nav a:active {
    color: var(--we-accent);
}

.site-shell.home-shell {
    width: min(1160px, calc(100% - 80px));
}

@media (max-width: 640px) {
    .site-shell.home-shell {
        width: calc(100% - 32px);
    }
}

.nav-toggle {
    display: none;
    border: 1px solid var(--we-border-input);
    background: var(--we-card);
    border-radius: var(--we-radius-sm);
    padding: 10px 16px;
    color: var(--we-text-2);
}

.site-header .nav-cta {
    padding: 9px 18px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    min-height: 0;
    box-shadow: none;
}

.nav-toggle[hidden] {
    display: none !important;
}

.nav-cta,
.button.primary {
    background: var(--we-accent);
    color: white;
    padding: 12px 18px;
    border-radius: var(--we-radius-sm);
    box-shadow: var(--we-shadow-btn);
}

.button,
.nav-toggle,
.nav-cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 48px;
    transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease, border-color 160ms ease, opacity 160ms ease;
    will-change: transform;
}

/* Re-assert the desktop default that the shared button rule above overrides: the
   toggle only exists below 960px, and the @media block near the end of this file
   turns it back on there. Without this, the toggle paints on desktop until the
   deferred site.js chain runs toggleNavigation() and sets [hidden]. */
.nav-toggle {
    display: none;
}

.button.secondary {
    border: 1px solid var(--we-border-input);
    border-radius: var(--we-radius-sm);
    padding: 12px 18px;
    background: var(--we-card);
    color: var(--we-text-2);
}

/* B-07: .button sets display:inline-flex, which used to defeat the [hidden] attribute -
   the report-gated actions (Email / Copy report) rendered live but did nothing. */
.button[hidden] {
    display: none;
}

.hero {
    padding: 60px 0 40px;
    background: var(--we-bg-gradient);
    border-bottom: 1px solid var(--we-border);
}

.hero-grid,
.calculator-layout,
.results-layout,
.dashboard-grid {
    display: grid;
    gap: 24px;
}

.hero-grid {
    grid-template-columns: 1.5fr 1fr;
    gap: 40px;
    align-items: center;
}

.page-head h1 {
    font-size: clamp(30px, 3vw, 40px);
    line-height: 1;
    margin: 0 0 16px;
}

.hero h1 {
    margin: 0;
    font: 700 clamp(34px, 4.5vw, 54px)/1.02 var(--we-font-serif);
    letter-spacing: -0.01em;
    text-wrap: balance;
}

.hero-copy,
.page-head p,
.card p,
.panel p,
.prose-page p {
    color: var(--muted);
    line-height: 1.7;
}

.page-updated {
    margin: -4px 0 20px;
    font-size: 13px;
    color: var(--we-text-2, var(--muted));
}

.page-contact-email {
    margin: 4px 0 8px;
    font-size: 18px;
    font-weight: 600;
}

.page-section {
    margin-top: 28px;
}

.page-section h2 {
    margin: 0 0 10px;
    font-size: 19px;
    line-height: 1.35;
}

.page-section-list {
    margin: 0 0 12px;
    padding-left: 20px;
    color: var(--muted);
    line-height: 1.7;
}

.page-section-list li {
    margin-bottom: 6px;
}

.page-section-links {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin: 10px 0 0;
    font-size: 14px;
    font-weight: 600;
}

.hero-copy {
    margin: 18px 0 0;
    max-width: 52ch;
    font-size: 15px;
    line-height: 1.6;
    color: var(--we-text-2);
}

.hero-card,
.panel,
.card,
.metric-card {
    background: var(--we-card);
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-card);
    box-shadow: var(--we-shadow-card);
}

.hero-card,
.panel,
.card {
    padding: 24px;
}

.eyebrow {
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-size: 0.76rem;
    color: var(--accent-strong);
}

/* Home-scoped eyebrow (tool pages keep the shared .eyebrow above) */
.hero .eyebrow,
.section-heading .eyebrow {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    color: var(--we-accent);
}

.hero .eyebrow {
    margin: 0 0 16px;
}

.section-heading .eyebrow {
    margin: 0 0 8px;
    font-size: 16px;
    font-weight: 400;
}

#materials .section-heading .eyebrow {
    color: var(--we-dot-assume);
}

.hero-actions,
.page-links,
.panel-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 24px;
}

.hero-actions .button {
    padding: 13px 24px;
    font-size: 14px;
    font-weight: 600;
}

.hero-card {
    padding: 26px;
    border-radius: 16px;
    box-shadow: var(--we-shadow-float);
}

.hero-card h2 {
    margin: 0 0 12px;
    font: 700 18px var(--we-font-serif);
}

.hero-card > p {
    margin: 0 0 16px;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--we-text-3);
}

.home-trust-list {
    display: grid;
    gap: 10px;
    margin: 0;
    padding-left: 0;
    list-style: none;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--we-text-2);
}

.home-trust-list li {
    display: flex;
    gap: 11px;
}

.home-trust-list li::before {
    content: "\25C6";
    flex: none;
    margin-top: 4px;
    font-size: 10px;
    line-height: 1;
    color: var(--we-accent);
}

.readiness-badge {
    display: inline-flex;
    width: fit-content;
    margin-bottom: 12px;
    padding: 4px 11px;
    border-radius: var(--we-radius-pill);
    background: var(--we-badge-quote-bg);
    color: var(--we-badge-quote-fg);
    font-size: 11px;
    font-weight: 600;
}

/* 每个变体都同时声明 background 和 color。此前 --review_required 只覆盖 background、
   靠继承拿走上面写死的字色,换主题时就变成冷底暖字;三个状态一律成对声明,避免复发。 */
.readiness-badge--planning_quote {
    background: var(--we-badge-quote-bg);
    color: var(--we-badge-quote-fg);
}

.readiness-badge--review_required {
    background: var(--we-badge-review-bg);
    color: var(--we-badge-review-fg);
}

.readiness-badge--fast_estimate {
    background: var(--we-green-bg);
    color: var(--we-green-deep);
}

.home-tool-card-type {
    display: inline-flex;
    width: fit-content;
    padding: 3px 8px;
    border-radius: var(--we-radius-pill);
    background: var(--we-band);
    color: var(--we-text-2);
    font-size: 11px;
    font-weight: 600;
}

/* 整卡可点。390px 下工具卡约 343×236px,而底部链接只有约 297×15px,触屏目标过小。
   覆盖层用 ::before 而不是 ::after —— .card a:not(.button)::after 已经被链接末尾的
   箭头字符占用,复用 ::after 会把箭头顶掉。
   只作用于 [data-home-tool-card],不波及下方 guide 文章卡片(它们同样是 .card)。
   注意选择器必须是属性形式:卡片的 class 只有 "card",不存在 .home-tool-card 这个类。
   已知代价:绝对定位的覆盖层画在卡片内容之上,会挡住正文拖选和 badge 的 title 悬停。
   前者是 stretched-link 的通用代价;后者由搜索框下方的 .home-readiness-legend 承担
   释义,因此这条规则不能脱离该图例单独存在。 */
.card[data-home-tool-card] {
    position: relative;
}

.card[data-home-tool-card] > a::before {
    content: "";
    position: absolute;
    inset: 0;
}

/* 有了覆盖层,链接自身的焦点框只圈住那一行文字、在整卡里很难看清,焦点态改由卡片承担。
   用 :has(a:focus-visible) 而不是 :focus-within,后者鼠标点击也会亮框。 */
.card[data-home-tool-card]:has(a:focus-visible) {
    outline: 2px solid var(--we-accent);
    outline-offset: 3px;
}

.section-grid {
    padding: 18px 0 40px;
}

.home-tool-search {
    display: grid;
    grid-template-columns: minmax(0, 520px) auto;
    align-items: end;
    gap: 6px 14px;
    padding-top: 18px;
}

.home-tool-search label {
    grid-column: 1 / -1;
    font-size: 13px;
    font-weight: 600;
    color: var(--we-text-2);
}

.home-tool-search input {
    width: 100%;
    min-height: 44px;
    border: 1px solid var(--we-border-input);
    border-radius: var(--we-radius-sm);
    padding: 10px 13px;
    background: var(--we-card);
    color: var(--we-text);
    font: inherit;
}

.home-tool-search input:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: 2px;
}

.home-tool-search-status {
    margin: 0 0 11px;
    font-size: 12px;
    color: var(--we-text-3);
}

.home-tool-search-empty {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-card-sub);
}

.home-tool-search-empty p {
    margin: 0;
    color: var(--we-text-2);
}

.home-tool-search-empty button {
    border: 0;
    padding: 0;
    background: transparent;
    color: var(--we-accent);
    font: inherit;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.home-tool-search-empty[hidden],
[data-home-tool-group][hidden],
[data-home-tool-card][hidden] {
    display: none;
}

/* Readiness 标签图例。收起态只有一行 summary,几乎不增加首页纵向长度,
   展开后是可复制、可被搜索引擎读取的正文。原生 <details>,不需要 JS。 */
.home-readiness-legend {
    margin-top: 2px;
    font-size: 13px;
}

.home-readiness-legend > summary {
    width: fit-content;
    color: var(--we-accent);
    font-weight: 600;
    cursor: pointer;
}

.home-readiness-legend > summary:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: 3px;
    border-radius: var(--we-radius-sm);
}

.home-readiness-legend ul {
    display: grid;
    gap: 10px;
    margin: 12px 0 0;
    padding: 14px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-card-sub);
    list-style: none;
}

.home-readiness-legend li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
}

/* 图例里的徽章与卡片上是同一个组件,但这里不需要卡片布局用的那 12px 下边距。 */
.home-readiness-legend .readiness-badge {
    flex: none;
    margin-bottom: 0;
}

.home-readiness-legend-help {
    flex: 1 1 260px;
    line-height: 1.5;
    color: var(--we-text-2);
}

.home-tool-groups.is-filtering {
    width: min(1160px, calc(100% - 80px));
    margin: 0 auto;
    padding: 18px 0 40px;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
}

.home-tool-groups.is-filtering [data-home-tool-group],
.home-tool-groups.is-filtering .card-grid {
    display: contents;
}

.home-tool-groups.is-filtering [data-home-tool-group][hidden] {
    display: none;
}

.home-tool-groups.is-filtering .section-heading {
    display: none;
}

@media (max-width: 640px) {
    .home-tool-search {
        grid-template-columns: minmax(0, 1fr);
    }

    .home-tool-search-status {
        margin: 0;
    }

    .home-tool-search-empty {
        flex-wrap: wrap;
    }

    .home-tool-groups.is-filtering {
        width: calc(100% - 32px);
        grid-template-columns: minmax(0, 1fr);
    }
}

.section-heading h2 {
    margin: 0 0 24px;
    font: 700 30px var(--we-font-serif);
}

.card-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
}

.compact {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 22px;
}

.card > .readiness-badge,
.card > a.button {
    align-self: flex-start;
    margin-bottom: 0;
}

.card h3 {
    margin: 0;
    font: 600 18px var(--we-font-serif);
}

.card p {
    flex: 1;
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--we-text-3);
}

.card a:not(.button) {
    font-size: 13px;
    font-weight: 600;
    color: var(--we-accent);
}

.card a:not(.button)::after {
    content: " \2192";
}

/* The description is the paragraph that absorbs the leftover height. Without this the added
   lines each claim an equal share of it and every card turns into airy, unreadable bands. */
.card > .card-scenario,
.card > .card-cost {
    flex: 0 0 auto;
}

.card > .card-cost {
    font-weight: 600;
    color: var(--we-text-2);
}


.card-grid.compact .card {
    background: var(--we-card-sub);
}

.section-more {
    margin: 20px 0 0;
}

.section-more a {
    font-size: 13px;
    font-weight: 600;
    color: var(--we-accent);
}

.section-more a::after {
    content: " \2192";
}

.page-head {
    padding: 48px 0 18px;
}

/* .page-head-split {
    display: grid;
    gap: 18px;
} */
.page-head-split {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 28px;
    padding-bottom: 22px;
    border-bottom: 1px solid var(--we-border);
    margin-bottom: 28px;
}

.page-head-copy {
    min-width: 0;
    flex: 1 1 380px;
}

.page-head-copy p:last-child {
    max-width: 60ch;
    margin-bottom: 0;
}

.page-head-total {
    flex: none;
    padding: 16px 24px;
    background: var(--we-card);
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-card);
    box-shadow: var(--we-shadow-card);
    text-align: right;
}

/* Design §13.2: regulated builders (garage / ADU) mark the headline total planning-level
   until the feasibility gate clears. Rendered only while the gate is unresolved. */
.page-head-total-confidence {
    display: inline-block;
    margin: 0 0 6px auto;
    padding: 3px 9px;
    border-radius: var(--we-radius-pill);
    background: var(--we-selected);
    color: #a85a2a;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.page-head-total-confidence[hidden] {
    display: none;
}

.page-head-total-label {
    margin: 0;
    color: var(--we-text-4);
    font-size: 0.69rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.page-head-total-amount {
    display: block;
    margin-top: 2px;
    font: 700 clamp(26px, 3vw, 34px)/1.05 var(--we-font-serif);
    color: var(--we-text);
}

.page-head-total-unpriced {
    margin: 5px 0 0;
    color: var(--we-accent-deep);
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1.4;
}

.page-head-total-unpriced[hidden] {
    display: none;
}

/* Estimate headline moved out of the removed status strip and is now the total card's
   only meta line (design §5.1). Region basis / completion were dropped from the title
   strip on 2026-07-26: both stay visible on the BOM panel meta line, the BOM total band
   and the "About this estimate" row. */
.page-head-total-summary {
    /* Long builder headlines (garage / bathroom) would otherwise stretch the card across
       the strip and push the H1 into extra wraps. */
    max-width: 38ch;
    margin: 6px 0 0 auto;
    color: var(--we-text-3);
    font-family: var(--we-font-sans);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.45;
    text-align: right;
}

.page-head-total-summary[hidden] {
    display: none;
}

.planning-risk-banner {
    margin-top: 18px;
    padding: 16px 20px;
    background: var(--we-quote-bg);
    border: 1px solid var(--we-quote-border);
    border-radius: var(--we-radius-card);
}

.planning-risk-banner[hidden],
.planning-risk-banner > [hidden] {
    display: none;
}

.planning-risk-banner[data-planning-tone="danger"],
.planning-risk-banner[data-planning-tone="error"] {
    border-color: var(--we-accent);
    background: var(--we-selected);
}

.planning-risk-banner-eyebrow {
    margin: 0;
    color: var(--we-accent-deep);
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
}

.planning-risk-banner-title {
    margin: 6px 0 0;
    font: 700 1rem/1.3 var(--we-font-serif);
    color: var(--we-text);
}

.planning-risk-banner-copy {
    margin: 6px 0 0;
    max-width: 84ch;
    color: var(--we-text-2);
    font-size: 0.86rem;
    line-height: 1.6;
}

/* Phase 4 (design 14.1): the calculator page adopts the builder three-zone skeleton -
   one shared 320px input rail plus a minmax(0,1fr) result column. */
.calculator-layout {
    grid-template-columns: 320px minmax(0, 1fr);
    gap: 28px;
    align-items: start;
}

.calculator-layout[data-tool="project"] {
    grid-template-columns: 320px minmax(0, 1fr);
    gap: 28px;
}

/* 2026-07-26 用户裁决:左列不 sticky、不做内部滚动——真实表单远长于设计 mock,
   sticky+内滚产生双滚动条;整页单一滚动更可控。 */

.calculator-layout > *,
.results-layout > *,
.project-side-column,
.panel,
.preview-surface,
.preview-stage,
.three-stage {
    min-width: 0;
}

.calculator-layout[data-slug="epoxy-floor-calculator"] #calculator-preview {
    scroll-margin-top: 96px;
}

.calculator-layout[data-slug="epoxy-floor-calculator"] .preview-stage,
.calculator-layout[data-slug="epoxy-floor-calculator"] .three-stage {
    scroll-margin-top: 96px;
}

/* The epoxy 2D layout stacks slab + dimension line + summary + layer strip + process rail
   + note. Since the Phase 4 two-card reflow halved the card width, the layer names wrap
   instead of truncating and a decorative-flake system wraps the process rail onto a second
   row, so the stack needs a taller height budget than the other calculators. */
.calculator-layout[data-slug="epoxy-floor-calculator"] #preview-canvas {
    scroll-margin-top: 96px;
    min-height: 420px;
}

.calculator-layout[data-slug="siding-calculator"] #preview-canvas {
    height: clamp(420px, 58vw, 460px);
    min-height: 420px;
}

@media (max-width: 640px) {
    /* Phone keeps the same height budget as the desktop card: the epoxy footer stack is
       the same size at both widths, and at the old 340px the slab dimension annotation
       landed on the summary line. */
    .calculator-layout[data-slug="epoxy-floor-calculator"] #preview-canvas,
    .calculator-layout[data-slug="epoxy-floor-calculator"] .three-stage {
        min-height: 420px;
    }

    .calculator-layout[data-slug="siding-calculator"] #preview-canvas {
        height: 360px;
        min-height: 360px;
    }
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .preview-stage {
    display: grid;
    gap: 10px;
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
    align-items: stretch;
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail-chip {
    min-width: 0;
    min-height: 44px;
    padding: 8px 10px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    border-radius: 8px;
    background: rgba(255, 251, 246, 0.92);
    color: #172033;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail-chip-wide {
    grid-column: span 4;
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail-label {
    display: block;
    margin-bottom: 2px;
    color: rgba(23, 32, 51, 0.62);
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0;
    text-transform: uppercase;
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail-value {
    display: block;
    min-width: 0;
    overflow-wrap: anywhere;
    color: #172033;
    font-size: 0.8rem;
    font-weight: 800;
    line-height: 1.25;
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] .preview-stage.is-zoomed [data-outdoor-kitchen-preview-rail] {
    display: none;
}

.calculator-layout[data-slug="outdoor-kitchen-builder"] #preview-canvas {
    height: clamp(390px, 42vw, 500px);
    min-height: 390px;
}

.project-side-column {
    display: grid;
    gap: 24px;
    align-content: start;
}

.project-summary-anchor {
    display: grid;
    gap: 18px;
}

.project-summary-anchor > .calculator-scope-summary {
    margin: 0;
}

.calculator-layout[data-slug="bathroom-remodel"] .bathroom-summary {
    display: grid;
    gap: 10px;
    margin: 0;
    padding: 12px 14px;
    border: 1px solid var(--we-border-row);
    border-radius: var(--we-radius-sm);
    background: var(--we-card-sub);
    box-shadow: none;
}

.calculator-layout[data-slug="bathroom-remodel"] .bathroom-summary .calculator-scope-copy {
    margin-bottom: 0;
}

.calculator-layout[data-slug="bathroom-remodel"] .bathroom-summary .calculator-scope-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.project-side-column > .preview-pricing-details,
.project-side-column > .preview-pricing,
.project-side-column > .trust-contract,
.project-side-column > .material-theme-summary,
.project-side-column > .metric-grid,
.project-side-column > .result-notes {
    margin-top: 0;
    margin-bottom: 0;
}

.project-side-column > .metric-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Summary card row: 4 key metrics + the Material theme card share one auto-fit track.
   .metric-grid stays the JS render target (common.js renderMetrics innerHTML), so it
   dissolves into the row with display:contents rather than nesting a second grid. */
.project-summary-metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
    gap: 12px;
}

.project-summary-metrics > .metric-grid {
    display: contents;
}

.project-summary-metrics > .metric-grid[hidden] {
    display: none;
}

.project-summary-metrics .metric-card {
    padding: 14px 16px;
    background: var(--we-card-sub);
    box-shadow: none;
}

.project-summary-metrics .metric-card span {
    margin-bottom: 3px;
    color: var(--we-text-4);
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.project-summary-metrics .metric-card strong {
    font-family: var(--we-font-mono);
    font-size: 0.95rem;
    line-height: 1.35;
}

/* Material tier card sits in the summary row as a green sibling of the metric cards.
   The theme description stays on the card as a small third line (D4 information
   preservation): [data-material-theme-description] is written live by common.js. */
.project-summary-metrics > .material-theme-summary {
    margin: 0;
    padding: 14px 16px;
    border-radius: var(--we-radius-card);
    border-color: var(--we-green-border);
    background: var(--we-green-card);
    box-shadow: none;
}

.project-summary-metrics > .material-theme-summary[hidden] {
    display: none;
}

.project-summary-metrics > .material-theme-summary .material-theme-eyebrow {
    margin: 0;
    color: var(--we-green-fg);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.project-summary-metrics > .material-theme-summary .material-theme-title {
    margin: 5px 0 0;
    font-family: var(--we-font-sans);
    font-size: 0.86rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--we-green-deep);
}

.project-summary-metrics > .material-theme-summary .material-theme-copy {
    margin: 5px 0 0;
    color: var(--we-text-3);
    font-size: 0.72rem;
    line-height: 1.55;
}

.project-summary-metrics > .material-theme-summary [hidden] {
    display: none;
}

.project-side-column > .result-notes {
    grid-template-columns: 1fr;
}

.project-side-column .trust-contract-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.project-results-main {
    display: grid;
    gap: 24px;
    align-content: start;
}

@media (min-width: 961px) {
    /* D3: material list + material guide are full-bleed; the lead panel stacks under
       them until batch 4 relocates the quote CTA. */
    .project-results-main {
        grid-column: 1 / -1;
        grid-template-columns: minmax(0, 1fr);
        align-items: start;
    }
}

/* Batch 6 / D8: the bathroom (312–360px) and garage-ADU (320–420px) input-column width
   overrides that predate the Warm Editorial three-zone skeleton are gone — every builder
   now uses the one 320px sticky rail from `.calculator-layout[data-tool="project"]`.
   Their ≤960px single-column overrides stay where they are. */

.calculator-layout[data-slug="bathroom-remodel"] .preview-surface-header {
    align-items: start;
}

.calculator-layout[data-slug="bathroom-remodel"] .preview-stage-controls {
    top: 10px;
    left: 12px;
    right: 12px;
    align-items: stretch;
    max-width: none;
}

/* Bathroom is the only page with six layer chips, and the equal-width `flex: 1 1 0` +
   ellipsis treatment that used to squeeze them onto one line clipped half the labels
   ("Shower w…", "Plomeria / v…") and still pushed the row past the card edge, hiding the
   last chip outright on the narrower 3D card. The chips now size to their own text and
   the row falls back to the shared wrap behaviour — no `flex-wrap` here, so the ≤720px
   scroll-strip rule in `.preview-surface-layer-slot` keeps its mobile contract. Both the
   2D and 3D rows render through this same selector, so they stay identical. */
.calculator-layout[data-slug="bathroom-remodel"] .preview-stage-control-row {
    justify-content: flex-start;
    gap: 4px;
}

.calculator-layout[data-slug="bathroom-remodel"] .preview-stage-control-row .preview-stage-chip {
    min-height: 30px;
    padding: 0 6px;
    font-size: 0.68rem;
    letter-spacing: 0;
    white-space: nowrap;
}

.calculator-layout[data-slug="bathroom-remodel"] #preview-canvas {
    min-height: 360px;
}

.calculator-layout[data-slug="bathroom-remodel"] .three-stage {
    min-height: clamp(440px, 42vw, 540px);
}

.calculator-layout[data-slug="paint-calculator"] .three-stage {
    min-height: 320px;
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-controls {
    gap: 6px;
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-chip {
    min-height: 32px;
    padding: 0 11px;
    font-size: 0.77rem;
    box-shadow: 0 8px 18px rgba(23, 32, 51, 0.1);
}

.three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-controls {
    right: 14px;
    flex-wrap: nowrap;
    gap: 4px;
    padding: 7px 10px;
    border-radius: 14px;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-controls::-webkit-scrollbar {
    display: none;
}

.three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-caption {
    font-size: 0.58rem;
    letter-spacing: 0.06em;
}

.three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-chip {
    flex: 0 0 auto;
    min-width: max-content;
    min-height: 28px;
    padding: 0 9px;
    font-size: 0.68rem;
    white-space: nowrap;
}

.three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-summary {
    bottom: 78px;
    max-width: min(420px, calc(100% - 32px));
    padding: 8px 10px;
    background: rgba(255, 251, 246, 0.76);
    font-size: 0.78rem;
    line-height: 1.4;
}

.three-stage[data-scene-slug="deck-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="shed-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="fence-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="pergola-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="patio-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-controls {
    right: 14px;
}

.three-stage-layer-summary {
    position: absolute;
    left: 0;
    right: 0;
    /* 宽度由视口决定而非内容决定，所以切换图层时宽度天然不变。
       用 margin:0 auto（等价于 margin-inline:auto + margin-block:0）而不是 transform 居中：
       绝对定位在 left/right/width 同时存在时会"过度约束"，浏览器此时会用 auto 外边距去居中，
       而不是丢弃 right —— 这样即使某个 slug 规则另外设了 left/right（如下方移动端覆盖规则），
       元素仍然居中，不会像 transform 方案那样被再左移半个宽度飞出视口。合并成一条 margin
       声明，避免和其他地方的 margin 分写产生层叠顺序问题。 */
    width: min(56vw, 720px);
    margin: 0 auto;
    bottom: 68px;
    z-index: 3;
    padding: 10px 13px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    border-radius: 8px;
    background: rgba(255, 251, 246, 0.86);
    color: #243047;
    font-size: 0.82rem;
    line-height: 1.45;
    box-shadow: 0 12px 28px rgba(23, 32, 51, 0.12);
}

.three-stage-layer-summary[hidden] {
    display: none;
}

.three-stage[data-scene-slug="deck-builder"] .three-stage-layer-controls {
    max-width: calc(100% - 28px);
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.three-stage[data-scene-slug="deck-builder"] .three-stage-layer-controls::-webkit-scrollbar {
    display: none;
}

.three-stage[data-scene-slug="deck-builder"] .three-stage-layer-chip {
    flex: 0 0 auto;
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-controls {
    gap: 6px;
    padding: 8px 10px;
    border-radius: 14px;
    background: rgba(255, 251, 246, 0.72);
    box-shadow: 0 10px 22px rgba(23, 32, 51, 0.1);
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-caption {
    font-size: 0.6rem;
    letter-spacing: 0.07em;
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-chip {
    min-height: 30px;
    padding: 0 10px;
    font-size: 0.73rem;
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-legend {
    background: rgba(255, 251, 246, 0.74);
    box-shadow: 0 10px 20px rgba(23, 32, 51, 0.1);
}

.three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-legend-toggle {
    min-height: 28px;
    padding: 0 10px;
    font-size: 0.71rem;
}

.calculator-layout[data-slug="bathroom-remodel"] .metric-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.calculator-layout[data-slug="bathroom-remodel"] .result-notes {
    grid-template-columns: 1fr;
}

/* D3: the material list runs full width under the three-zone layout. */
.results-layout[data-slug="bathroom-remodel"],
.results-layout {
    grid-template-columns: minmax(0, 1fr);
}

.results-layout,
.dashboard-grid {
    padding: 24px 0 60px;
}

.dashboard-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.input-panel label,
.lead-panel label,
.quote-form label {
    display: grid;
    gap: 6px;
    /* gap: 8px;
    margin-bottom: 16px; */
}

#calculator-input-form .field-grid
{
    background: rgb(255, 255, 255);
    border: 1px solid rgb(230, 224, 212);
    border-radius: 13px;
    padding: 15px;
}


.field-group {
    margin-bottom: 20px;
}

.field-group[hidden],
.field[hidden] {
    display: none !important;
}

.field-group:last-of-type {
    margin-bottom: 0;
}

/* 2026-07-26 用户指令:左列包装盒(嵌套壳)删除——presets 区按设计稿为
   "小标题 + 独立预设卡" 的平铺结构,不再有外层米色盒。 */
.calculator-scope-summary,
.calculator-presets {
    margin: 0 0 18px;
}

.calculator-scope-copy,
.calculator-presets-copy {
    display: grid;
    gap: 6px;
    margin-bottom: 12px;
}

.calculator-scope-eyebrow {
    margin: 0;
    color: rgba(23, 32, 51, 0.58);
    font-size: 1rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.calculator-scope-note {
    margin: 0;
    color: rgba(23, 32, 51, 0.74);
    font-size: 0.82rem;
    line-height: 1.5;
}

.calculator-scope-chips,
.calculator-preset-grid {
    /* display: grid; */
    gap: 10px;
}

.calculator-scope-chips {
    grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
}

.calculator-scope-chip {
    display: inline-flex;
    min-height: 0;
    padding: 8px 14px;
    border-radius: var(--we-radius-sm);
    background: var(--we-green-bg);
    color: var(--we-green-deep);
    font-size: 12.5px;
    font-weight: 600;
}

/* Calculator validation summary (template 3.3).
   This block lost all of its CSS in 17bab16 and rendered as bare document flow ever since -
   headline, copy and bullets floating between the preview and the metric grid with no card,
   border or background. It stays inside #calculator-preview and is never folded into a support
   card, because it carries two things the support cards must not: blocking input errors and
   non-blocking planner advisories.

   The two tones have to be tellable apart at a glance, since one of them stops the estimate
   from updating and the other does not:
     warning -> advisory, amber (--we-dot-warn)
     error   -> blocking, accent red (--we-dot-excl)
   `display: grid` below would otherwise beat the `hidden` attribute the JS toggles, hence the
   [hidden] companion rule. */
.validation-summary {
    display: grid;
    gap: 8px;
    margin: 0 0 18px;
    padding: 15px 17px 16px;
    border: 1px solid var(--we-border);
    border-left: 3px solid var(--we-dot-warn);
    border-radius: var(--we-radius-card);
    background: var(--we-card);
    box-shadow: var(--we-shadow-card);
}

.validation-summary[hidden] {
    display: none;
}

.validation-summary-eyebrow {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    color: var(--we-text-4);
    font-size: 0.7rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.validation-summary-eyebrow::before {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--we-dot-warn);
}

.validation-summary-title {
    margin: 0;
    color: var(--we-text);
    font-family: var(--we-font-serif);
    font-size: 1.05rem;
    font-weight: 600;
    line-height: 1.35;
}

.validation-summary-copy {
    margin: 0;
    color: var(--we-text-2);
    font-size: 0.9rem;
    line-height: 1.55;
}

.validation-summary-list {
    display: grid;
    gap: 6px;
    margin: 2px 0 0;
    padding: 0;
    list-style: none;
}

.validation-summary-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding-left: 16px;
    position: relative;
    color: var(--we-text-2);
    font-size: 0.88rem;
    line-height: 1.5;
}

.validation-summary-list li::before {
    content: "";
    position: absolute;
    top: 0.62em;
    left: 2px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--we-dot-warn);
}

.validation-summary .validation-action-button {
    min-height: 44px;
    padding: 8px 14px;
    font-size: 0.82rem;
}

.validation-summary[data-validation-tone="error"] {
    border-left-color: var(--we-dot-excl);
    background: var(--we-quote-bg);
}

.validation-summary[data-validation-tone="error"] .validation-summary-eyebrow {
    color: var(--we-accent-deep);
}

.validation-summary[data-validation-tone="error"] .validation-summary-eyebrow::before,
.validation-summary[data-validation-tone="error"] .validation-summary-list li::before {
    background: var(--we-dot-excl);
}

.project-related-link {
    text-decoration: none;
    transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}

.project-related-link:hover,
.project-related-link:focus-visible {
    transform: translateY(-1px);
    box-shadow: 0 14px 24px rgba(23, 32, 51, 0.1);
    background: rgba(231, 238, 223, 0.96);
}

.calculator-preset-grid {
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.calculator-preset-button {
    display: grid;
    gap: 6px;
    min-height: 86px;
    padding: 14px 16px;
    border: 1px solid rgba(166, 127, 67, 0.18);
    border-radius: 20px;
    background: rgba(255, 255, 255, 0.88);
    color: #172033;
    text-align: left;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

.calculator-preset-button strong {
    font-size: 0.95rem;
}

.calculator-preset-button small {
    color: rgba(23, 32, 51, 0.68);
    line-height: 1.45;
}

.calculator-preset-button.is-active {
    border-color: rgba(113, 88, 52, 0.42);
    background: linear-gradient(135deg, rgba(239, 231, 215, 0.96), rgba(228, 216, 194, 0.92));
    box-shadow: 0 14px 28px rgba(23, 32, 51, 0.08);
}

.project-preset-button {
    min-height: 104px;
    padding: 16px 18px;
}

.project-preset-button strong {
    font-size: 1rem;
    line-height: 1.35;
}

.field-group-header {
    margin-bottom: 12px;
}

.field-group-header h3 {
    margin: 0;
    font-size: 1rem;
}

/* 2026-08-02 A3: one-line reassurance microcopy under a group heading (Advanced assumptions). */
.field-group-note {
    margin: 4px 0 0;
    color: rgba(23, 32, 51, 0.68);
    font-size: 0.84rem;
    line-height: 1.45;
}

.field-grid {
    /* display: grid;
    background: rgb(255, 255, 255);
    border: 1px solid rgb(230, 224, 212);
    border-radius: 13px;
    padding: 15px; */
    display: grid;
    gap: 14px;
}

.assumption-note {
    margin: 0 0 18px;
    padding: 12px 14px;
    border-radius: 18px;
    background: rgba(223, 232, 215, 0.65);
    color: var(--ink);
}

.project-compliance-note {
    background: rgba(244, 229, 199, 0.82);
}

.project-planning-notes {
    margin-bottom: 20px;
}

.project-risk-banner {
    margin: 0 0 12px;
    padding: 12px 14px;
    border-radius: 18px;
    border: 1px solid rgba(192, 90, 43, 0.18);
    background: rgba(255, 243, 221, 0.92);
    color: var(--ink);
}

.planning-notes {
    border: 1px solid rgba(216, 207, 192, 0.84);
    border-radius: 22px;
    background: rgba(255, 251, 246, 0.92);
    overflow: hidden;
}

.planning-notes summary {
    display: grid;
    gap: 4px;
    padding: 16px 18px;
    cursor: pointer;
    list-style: none;
}

.planning-notes summary::-webkit-details-marker {
    display: none;
}

.planning-notes summary span {
    font-weight: 700;
}

.planning-notes summary small {
    color: var(--muted);
}

.planning-note-list {
    margin: 0;
    padding: 0 18px 18px 38px;
    display: grid;
    gap: 10px;
    color: var(--ink);
}

.project-stage-stack {
    display: grid;
    gap: 14px;
    margin-bottom: 20px;
}

.project-input-action-copy p {
    margin: 0;
}

.trust-contract {
    display: grid;
    gap: 12px;
    margin: 14px 0 16px;
    padding: 14px;
    border: 1px solid rgba(167, 139, 100, 0.36);
    border-radius: 8px;
    background: rgba(255, 251, 244, 0.92);
}

.trust-contract-header {
    display: grid;
    gap: 4px;
}

.trust-contract-eyebrow {
    margin: 0;
    color: #5f4a2c;
    font-size: 0.76rem;
    font-weight: 800;
    letter-spacing: 0;
    text-transform: uppercase;
}

.trust-contract-summary {
    margin: 0;
    color: #384152;
    font-size: 0.92rem;
    line-height: 1.45;
}

.trust-contract-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(176px, 1fr));
    gap: 8px;
}

.trust-contract-item {
    display: grid;
    gap: 5px;
    align-content: start;
    min-height: 96px;
    padding: 10px 12px;
    border: 1px solid rgba(210, 199, 184, 0.82);
    border-left: 4px solid #8b7355;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.88);
}

.trust-contract-item[data-trust-contract-tone="success"] {
    border-left-color: #2f855a;
}

.trust-contract-item[data-trust-contract-tone="warning"] {
    border-left-color: #b7791f;
}

.trust-contract-label {
    color: #5c6573;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0;
    text-transform: uppercase;
}

.trust-contract-value {
    color: #1f2937;
    font-size: 0.94rem;
    line-height: 1.32;
}

.trust-contract-note {
    color: #596579;
    font-size: 0.82rem;
    line-height: 1.36;
}

@media (max-width: 640px) {
    .trust-contract {
        padding: 12px;
    }

    .trust-contract-grid {
        grid-template-columns: 1fr;
    }
}

.project-stage {
    border: 1px solid var(--we-border);
    border-radius: 13px;
    background: var(--we-card);
    overflow: hidden;
}

.project-stage summary {
    display: grid;
    gap: 4px;
    padding: 16px 18px;
    cursor: pointer;
    list-style: none;
}

.project-stage summary::-webkit-details-marker {
    display: none;
}

.project-stage-summary {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 12px;
    align-items: start;
}

.project-stage-kicker,
.project-stage-count {
    display: inline-flex;
    align-items: center;
    min-height: 30px;
    padding: 0 12px;
    border-radius: var(--we-radius-pill);
    background: rgba(255, 244, 225, 0.96);
    color: #8c3f1d;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.project-stage-count {
    justify-self: end;
    background: rgba(223, 232, 215, 0.92);
    color: #315d3f;
}

.project-stage-title-group {
    display: grid;
    gap: 4px;
    min-width: 0;
}

.project-stage-title-group small {
    color: var(--muted);
    line-height: 1.45;
}

.project-stage summary span {
    font-weight: 700;
}

.project-stage summary small {
    color: var(--muted);
    line-height: 1.45;
}

.project-stage:not([open]) summary {
    background: rgba(255, 252, 247, 0.92);
}

.project-stage[open] summary {
    border-bottom: 1px solid rgba(216, 207, 192, 0.72);
    background: rgba(246, 240, 230, 0.92);
}

.project-stage-body {
    display: grid;
    gap: 18px;
    padding: 16px 18px 18px;
}

.project-stage-body .field-group {
    margin-bottom: 0;
}

.project-stage-body .field-group + .field-group {
    padding-top: 14px;
    border-top: 1px solid rgba(216, 207, 192, 0.56);
}

.project-summary-card-statuses {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.project-summary-chip-grid {
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
}

.project-summary-chip {
    display: grid;
    gap: 6px;
    padding: 12px 14px;
    border-radius: 18px;
    background: rgba(244, 238, 228, 0.92);
    border: 1px solid rgba(216, 207, 192, 0.76);
}

.project-summary-chip[hidden] {
    display: none;
}

.project-summary-chip span {
    color: rgba(23, 32, 51, 0.58);
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.project-summary-chip strong {
    font-size: 0.88rem;
    line-height: 1.45;
}

.project-boundary-summary {
    display: grid;
    gap: 14px;
}

.project-boundary-summary[data-boundary-tone="warning"] {
    border-color: rgba(192, 90, 43, 0.24);
    background: linear-gradient(180deg, rgba(255, 247, 237, 0.96), rgba(248, 235, 217, 0.92));
}

.project-boundary-title {
    margin: 0;
    font-size: 1rem;
    line-height: 1.35;
}

.project-boundary-grid {
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

.project-boundary-chip {
    display: grid;
    gap: 6px;
    padding: 12px 14px;
    border-radius: 18px;
    border: 1px solid rgba(216, 207, 192, 0.76);
    background: rgba(255, 255, 255, 0.82);
}

.project-boundary-chip span {
    color: rgba(23, 32, 51, 0.58);
    font-size: 0.72rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.project-boundary-chip strong {
    font-size: 0.88rem;
    line-height: 1.45;
}

/* ---------------------------------------------------------------------------
   Regulated-builder overlay (design §13, garage-conversion-adu-builder only)
   The readiness section is the shared shell for two blocks: the full-width
   feasibility gate banner (§13.3) and the collapsible feasibility review
   checklist (§13.4). The section itself carries no chrome - each block is its
   own card - so it can span the full page width above the three zones.
   --------------------------------------------------------------------------- */
.garage-adu-readiness-summary {
    display: grid;
    gap: 16px;
    padding: 0;
    border: 0;
    background: none;
}

/* §13.3 gate banner */
.garage-adu-feasibility-gate {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(min(240px, 100%), auto);
    align-items: start;
    gap: 24px;
    padding: 22px 24px;
    background: var(--we-quote-bg);
    border: 1px solid var(--we-quote-border);
    border-radius: var(--we-radius-card);
}

.garage-adu-feasibility-gate[hidden] {
    display: none;
}

.garage-feasibility-gate-copy {
    margin-bottom: 0;
}

.garage-adu-feasibility-gate .calculator-scope-eyebrow {
    color: #a85a2a;
    letter-spacing: 0.12em;
}

.garage-adu-feasibility-headline {
    display: block;
    margin: 0.1rem 0 0.35rem;
    color: #8a3f18;
    font-family: var(--we-font-serif);
    font-size: 20px;
    font-weight: 700;
    line-height: 1.25;
}

.garage-adu-feasibility-gate .calculator-scope-note {
    max-width: 56ch;
    color: var(--we-text-3);
    font-size: 13px;
}

.garage-feasibility-meter {
    display: grid;
    gap: 7px;
    min-width: min(210px, 100%);
    padding: 12px 16px;
    background: var(--we-card);
    border: 1px solid var(--we-quote-border);
    border-radius: 11px;
}

.garage-feasibility-meter-head {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    margin: 0;
    color: var(--we-text-3);
    font-size: 11.5px;
}

.garage-feasibility-meter-head strong {
    color: var(--we-text);
    font-weight: 600;
}

.garage-feasibility-meter-track {
    display: block;
    height: 8px;
    background: #f0e6da;
    border-radius: 6px;
    overflow: hidden;
}

.garage-feasibility-meter-fill {
    display: block;
    height: 100%;
    background: var(--we-dot-assume);
    border-radius: 6px;
    transition: width 0.2s ease;
}

.garage-feasibility-resume {
    /* 44px floor: this is a primary touch target on the ≤640 breakpoint contract. */
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    margin-top: 5px;
    padding: 9px 12px;
    background: var(--we-accent);
    border-radius: 8px;
    color: #fff;
    font-size: 12.5px;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
}

.garage-feasibility-resume::after {
    content: "\00a0\2192";
}

.garage-feasibility-resume:hover,
.garage-feasibility-resume:focus-visible {
    background: var(--we-accent-deep);
    color: #fff;
}

/* §13.4 review checklist */
.garage-feasibility-review {
    display: grid;
    gap: 0;
    background: var(--we-card);
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-card);
    box-shadow: var(--we-shadow-card);
    overflow: hidden;
    margin-bottom: 24px;
}

.garage-feasibility-review-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 18px;
    border-bottom: 1px solid #eee6d9;
}

.garage-feasibility-review-head .calculator-scope-copy {
    gap: 2px;
    margin-bottom: 0;
}

.garage-feasibility-review-head .calculator-scope-eyebrow {
    color: var(--we-text-4);
    font-size: 16px;
    letter-spacing: 0.05em;
}

.garage-feasibility-review-head .calculator-scope-note {
    color: var(--we-text-3);
    font-size: 12.5px;
}

/* Text-link affordance per §13.4 - .button.secondary chrome would read as a second CTA
   next to the gate banner's orange button, so it is neutralised at equal specificity. */
.button.garage-adu-readiness-toggle {
    flex: none;
    align-self: center;
    min-height: 44px;
    padding: 8px 14px;
    background: none;
    border: 0;
    box-shadow: none;
    color: var(--we-accent);
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
}

.button.garage-adu-readiness-toggle:hover,
.button.garage-adu-readiness-toggle:focus-visible {
    background: none;
    border: 0;
    color: var(--we-accent-deep);
    text-decoration: underline;
}

.garage-adu-readiness-summary[data-readiness-expanded="false"] .garage-adu-feasibility-signals,
.garage-adu-readiness-summary[data-readiness-expanded="false"] .garage-fit-summary {
    display: none;
}

.garage-adu-feasibility-signals {
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    padding: 16px 18px 0;
}

.garage-adu-feasibility-signal {
    display: grid;
    gap: 6px;
    justify-content: stretch;
    min-height: 0;
    padding: 12px 14px;
    border: 1px solid #eee6d9;
    border-radius: 11px;
    background: var(--we-card-sub);
    color: var(--we-text);
    text-align: left;
}

.garage-adu-feasibility-signal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.garage-adu-feasibility-signal-name {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
}

.garage-adu-feasibility-signal-name::before {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--we-dot-warn);
}

.garage-adu-feasibility-signal-state {
    flex: none;
    padding: 3px 9px;
    border-radius: var(--we-radius-pill);
    background: var(--we-selected);
    color: #a85a2a;
    font-size: 10.5px;
    font-weight: 600;
}

.garage-adu-feasibility-signal strong {
    margin-left: 16px;
    color: var(--we-text-3);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.5;
}

.garage-adu-feasibility-signal[data-feasibility-state="clear"] {
    background: #f4f7ef;
    border-color: #dde7d1;
}

.garage-adu-feasibility-signal[data-feasibility-state="clear"] .garage-adu-feasibility-signal-name::before {
    background: var(--we-dot-assume);
}

.garage-adu-feasibility-signal[data-feasibility-state="clear"] .garage-adu-feasibility-signal-state {
    background: var(--we-green-bg);
    color: var(--we-green-fg);
}

.garage-adu-feasibility-signal[data-feasibility-tone="danger"] .garage-adu-feasibility-signal-name::before {
    background: var(--we-dot-excl);
}

.garage-fit-summary {
    padding: 14px 18px 16px;
    margin-top: 14px;
    border-top: 1px solid var(--we-border-row);
}

.garage-fit-summary .calculator-scope-eyebrow {
    margin-bottom: 9px;
    color: var(--we-text-4);
    font-size: 11px;
    letter-spacing: 0.05em;
}

.garage-fit-summary .calculator-scope-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 9px;
    align-items: center;
}

/* IA pass: the chips close the feasibility band instead of announcing a separate
   "Current fit snapshot" section, so the row label sits inline with the chips. */
.garage-fit-summary-label {
    color: var(--we-text-4);
    font-size: 16px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

/* Bath / layout is a planning allowance, not a feasibility area - it is styled as a
   note that states its own limits, never as a chip that could read as "passed". */
.garage-fit-summary-note {
    display: grid;
    gap: 3px;
    margin: 11px 0 0;
    padding: 10px 12px;
    border-radius: var(--we-radius-sm);
    background: var(--we-surface-2, rgba(0, 0, 0, 0.03));
    color: var(--we-text-2);
    font-size: 12.5px;
    line-height: 1.5;
}

.garage-fit-summary-note[hidden] {
    display: none;
}

.garage-fit-summary-note[data-feasibility-tone="warning"] {
    background: var(--we-amber-bg, rgba(180, 120, 0, 0.08));
}

.garage-fit-summary-note-label {
    color: var(--we-text-4);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Shared eyebrow that ties the two pinned handoff hosts into one readable band. */
.garage-handoff-band-eyebrow {
    margin: 0 0 4px;
    color: var(--we-text-4);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;}

.garage-fit-summary .calculator-scope-chip {
    min-height: 0;
    padding: 8px 14px;
    border-radius: var(--we-radius-sm);
    background: var(--we-green-bg);
    color: var(--we-green-deep);
    font-size: 12.5px;
    font-weight: 600;
}

/* §13.6 contractor handoff pack - its own card between the accordion and the quote CTA */
.garage-handoff-card {
    display: grid;
    gap: 0;
    padding: 0;
    overflow: hidden;
}

.garage-handoff-card[hidden] {
    display: none;
}

.garage-handoff-card .project-support-card {
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

/* §13.6 reads the pack contents as a checklist of what the bidder receives. */
.garage-handoff-card .project-support-details {
    display: grid;
    gap: 8px;
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
}

.garage-handoff-card .project-support-details[hidden] {
    display: none;
}

.garage-handoff-card .project-support-details li {
    display: grid;
    grid-template-columns: 14px minmax(0, 1fr);
    gap: 9px;
    color: var(--we-text-2);
    font-size: 12.5px;
    line-height: 1.5;
}

.garage-handoff-card .project-support-details li::before {
    content: "\2713";
    color: var(--we-dot-assume);
    font-size: 12px;
    line-height: 1.55;
}

.garage-handoff-note {
    margin: 0;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
}

.garage-handoff-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 9px;
    padding: 12px 16px 14px;
    border-top: 1px solid var(--we-border-row);
}

.garage-handoff-actions .button {
    min-height: 44px;
}

.garage-handoff-download {
    background: var(--we-dark);
    border-color: var(--we-dark);
    color: #fff;
}

.garage-handoff-download:hover,
.garage-handoff-download:focus-visible {
    background: #17140f;
    border-color: #17140f;
    color: #fff;
}

/* Garage ADU planning reference now reuses the Plan details panel in the result column. */
.project-education-shell {
    margin: 0;
}

/* The shared boundary belongs to the expandable body, before the three inline topics. */
.project-support-education-disclaimer {
    margin: 0 0 12px;
    color: var(--we-text-4);
    font-size: 11.5px;
    font-style: italic;
    line-height: 1.5;
    max-width: none;
}

@media (max-width: 720px) {
    .garage-adu-feasibility-gate {
        grid-template-columns: minmax(0, 1fr);
    }
}

@media (max-width: 640px) {
    .garage-feasibility-review-head {
        align-items: flex-start;
        flex-wrap: wrap;
        gap: 4px;
    }

    /* Once the row wraps, the toggle must stay a compact right-aligned text link rather
       than a full-width bar (the shared .button sizing would otherwise stretch it). */
    .garage-feasibility-review-head .button.garage-adu-readiness-toggle {
        flex: 0 0 auto;
        width: auto;
        align-self: flex-end;
        margin-left: auto;
        padding-right: 0;
    }
}

.project-risk-snapshot {
    margin-bottom: 18px;
}

.project-risk-snapshot[data-risk-tone="warning"] {
    border-color: rgba(192, 90, 43, 0.24);
    background: rgba(255, 244, 225, 0.94);
}

.project-risk-snapshot[data-risk-tone="error"] {
    border-color: rgba(151, 41, 37, 0.24);
    background: rgba(255, 242, 240, 0.94);
}

.project-risk-list {
    margin: 0;
    padding: 0 0 0 18px;
    display: grid;
    gap: 8px;
    color: var(--ink);
}

.field {
    display: grid;
    gap: 8px;
    min-width: 0;
}

.field > span {
    min-width: 0;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

.field-help {
    color: rgba(23, 32, 51, 0.68);
    font-size: 0.84rem;
    line-height: 1.45;
}

.field-feedback {
    display: grid;
    gap: 6px;
}

.field-feedback-list {
    display: grid;
    gap: 6px;
}

.field-feedback-note {
    display: block;
    padding: 8px 10px;
    border-radius: 12px;
    background: rgba(223, 232, 215, 0.65);
    color: rgba(23, 32, 51, 0.84);
    font-size: 0.8rem;
    line-height: 1.45;
}

.field-feedback-note[data-field-feedback-tone="warning"],
.field-feedback-note[data-field-feedback-tone="error"] {
    background: rgba(244, 229, 199, 0.82);
    color: #7f4d18;
}

.field.has-error > span,
.field.has-error .checkbox-row strong {
    color: #8f3320;
}

.field.has-error input,
.field.has-error select,
.field.has-error textarea,
.field.has-error .checkbox-row {
    border-color: rgba(143, 51, 32, 0.58);
    background: rgba(255, 244, 241, 0.92);
}

.field-checkbox {
    margin-bottom: 0;
}

.checkbox-row {
    display: flex;
    align-items: start;
    gap: 12px;
    min-height: 56px;
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: white;
}

.checkbox-row input {
    min-height: 0;
    margin-top: 3px;
    padding: 0;
}

.checkbox-row span {
    display: grid;
    gap: 2px;
}

.checkbox-row small {
    color: var(--muted);
}

.field.is-locked .checkbox-row {
    border-color: rgba(166, 127, 67, 0.34);
    background: rgba(250, 244, 232, 0.96);
}

.field.is-locked .checkbox-row strong::after {
    content: " Required above 30 in";
    color: #8f5f22;
    font-size: 0.82rem;
    font-weight: 600;
}

.field.is-locked .checkbox-row input {
    cursor: not-allowed;
}

.bom-detail {
    display: block;
    margin-top: 4px;
    color: var(--muted);
    font-size: 0.84rem;
    line-height: 1.5;
}

.bom-meta-line {
    margin-top: 6px;
}

.bom-material-cell,
.bom-quantity-cell {
    vertical-align: top;
}

.bom-material-primary {
    display: block;
    font-size: 0.92rem;
    line-height: 1.45;
    color: #172033;
}

.bom-material-secondary,
.bom-quantity-stack,
.bom-procurement-stack,
.bom-links-stack {
    display: grid;
    gap: 6px;
}

.bom-material-secondary,
.bom-procurement-stack {
    margin-top: 6px;
    color: rgba(23, 32, 51, 0.68);
    font-size: 0.78rem;
    line-height: 1.45;
}

.bom-quantity-stack strong {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    padding: 6px 10px;
    border-radius: var(--we-radius-pill);
    background: rgba(247, 242, 234, 0.96);
    color: #172033;
    font-size: 0.82rem;
    line-height: 1.35;
}

.bom-quantity-stack small {
    color: rgba(23, 32, 51, 0.68);
    font-size: 0.78rem;
    line-height: 1.45;
}

.bom-links-stack a {
    width: fit-content;
}

/* ---- D3: full-width material list (7-column builder grid) ---- */
#project-material-list,
#calculator-material-list {
    padding: 0;
    overflow: hidden;
}

#project-material-list > .disclosure,
#calculator-material-list > .disclosure {
    margin: 0;
    padding: 14px 22px;
    border-bottom: 1px solid var(--we-border-row);
    font-size: 0.8rem;
}

.bom-panel-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px 24px;
    padding: 20px 22px 16px;
    /* border-bottom: 1px solid var(--we-border); */
}

/* The title and its product-word subtitle share one line; the quantity note takes a full
   row of its own. (The selection bar used to live here too; it now sits below the
   toolbar, so it is no longer a child of this block.) */
.bom-panel-head-copy {
    flex: 1 1 260px;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
}

.bom-panel-head-copy > .bom-panel-quantity-note {
    flex: 1 1 100%;
}

/* The title and the table-level quantity note form one band: the title drops its own rule
   so the note sits inside the same block and the divider stays a single line. Both BOM
   panels always render the note, so no h2 is left without one. */
.bom-panel-head-copy h2 {
    padding: 18px 10px 8px 22px;
    margin: 0;
    color: rgba(23, 32, 51, 0.58);
    font-size: 1.1rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-family: var(--we-font-sans);
}

/* R-16 format 1: the industry term stays the heading and the product term sits under it, so
   the search word and the brand word each keep a job. Sits inside the same band as the
   quantity note below, so it carries no rule of its own. */
.bom-brand-subtitle {
    padding: 0 22px 8px 0;
    margin: 0;
    color: rgba(23, 32, 51, 0.62);
    font-size: 0.92rem;
}

/* Shopping-progress ticks. The checkbox only appears once bindBomSelectionControls has claimed
   the tbody: without JS it could be ticked but never remembered, and a control that silently
   forgets is worse than no control. */
.bom-select-check {
    display: none;
}

/* Every declaration here is undoing the global `input, select` rule further down this file
   (width:100%, min-height:56px, padding, border, radius, white background). Left alone it
   styles a checkbox like a text field: a 20x56 rounded empty box with no tick, which is what
   first shipped here and what pushed the phone row from 69px to 81px.

   Chrome discards padding on `appearance: checkbox`, so the input is instead a 44x44
   transparent hit box with the former 20px control painted as a centred SVG. Negative 12px
   margins reclaim the extra area on every side: the row still lays out a 20x20 slot while
   the bare input itself owns the full touch target. SVG colours are literals because a data
   URI cannot read custom properties; they mirror --we-card and --we-accent. */
tbody[data-bom-select-bound="true"] .bom-select-check {
    -webkit-appearance: none;
    appearance: none;
    box-sizing: border-box;
    display: inline-block;
    flex: none;
    width: 44px;
    min-width: 0;
    max-width: none;
    height: 44px;
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='.75' y='.75' width='20.5' height='20.5' rx='5.5' fill='%23ffffff' stroke='%23c9c0ae' stroke-width='1.5'/%3E%3C/svg%3E") center / 20px 20px no-repeat;
    margin: -12px;
    cursor: pointer;
}

tbody[data-bom-select-bound="true"] .bom-select-check:checked {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='.75' y='.75' width='20.5' height='20.5' rx='5.5' fill='%23c2582a' stroke='%23c2582a' stroke-width='1.5'/%3E%3Cpath d='M6 11.4l3.4 3.4L16 8.2' fill='none' stroke='%23ffffff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* The outline hugs the 20px glyph instead of ringing the invisible 44px hit box. */
tbody[data-bom-select-bound="true"] .bom-select-check:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: -10px;
}

/* Sits under the estimator's headline in the total card: it says how big the plan is, so it
   reads as a subtitle rather than competing with the material sentence above it. */
/* The single trust line under the total. Quiet, but always present: it is the one place the
   materials-only caveat is stated, so it must not read as another dismissible banner. */
.page-head-total-trust {
    margin: 10px 0 0;
    padding-top: 10px;
    border-top: 1px solid var(--we-border-row);
    color: rgba(23, 32, 51, 0.66);
    font-size: 0.8rem;
    line-height: 1.5;
}

.page-head-total-scope {
    margin: 4px 0 0;
    color: rgba(23, 32, 51, 0.6);
    font-size: 0.82rem;
    font-weight: 600;
}

/* Saved-plan rows: the size and the total are the two facts that tell one save from another
   when the name and date are identical. */
/* The second way out of the readiness panel: quieter than the resume CTA, because answering
   is still the better path - it just must not be the only one offered. */
.garage-feasibility-skip {
    display: inline-block;
    margin-left: 14px;
    color: rgba(23, 32, 51, 0.66);
    font-size: 0.85rem;
    font-weight: 600;
}

.account-plan-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 12px;
    margin: 2px 0 0;
}

.account-plan-shape {
    color: rgba(23, 32, 51, 0.62);
    font-size: 0.88rem;
}

.account-plan-total {
    font-size: 0.95rem;
}

.bom-select-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    padding: 8px 22px;
    margin: 0;
    color: #2f6a3d;
    font-size: 0.85rem;
    font-weight: 700;
}

.bom-select-summary[hidden] {
    display: none;
}

.bom-select-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.bom-select-summary .button {
    min-height: 40px;
    padding: 0 14px;
    font-size: 0.82rem;
}

.bom-select-summary .button[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
}

/* Selected rows are highlighted, not dimmed: these are the lines about to be acted on, not
   lines that are finished with. They also keep their position - re-sorting the list under
   someone's thumb in a store aisle loses their place. */
.bom-row.is-selected > td {
    background: rgba(239, 247, 236, 0.9);
}

.bom-row.is-selected .bom-material-primary {
    color: #21502f;
}

.bom-panel-quantity-note {
    padding: 0 22px 16px;
    border-bottom: 1px solid var(--we-border-row);
    margin: 0;
    color: rgba(23, 32, 51, 0.62);
    font-size: 0.85rem;
    line-height: 1.5;
}

/* D4: builder filters sit on one row under the panel title. The server-rendered Line
   status wrapper stays in the DOM (applied-filter chips + the calculator-only auto-hide
   contract) but stops drawing a box, because JS moves its field into the controls row. */
.bom-panel-tools {
    flex: 1 1 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 10px 12px;
    min-width: 0;
}

.bom-panel-tools > .bom-maturity-toolbar {
    display: contents;
}

.bom-panel-tools > .bom-explorer-toolbar {
    flex: 1 1 100%;
}

.bom-panel-tools .bom-explorer-controls {
    display: flex;
    flex: 1 1 auto;
    flex-wrap: wrap;
    gap: 10px 12px;
}

.bom-panel-tools .bom-explorer-field {
    flex: 0 0 152px;
    min-width: 0;
}

.bom-panel-tools .bom-explorer-field[data-bom-field="search"] {
    flex: 1 1 180px;
    min-width: 180px;
}

.bom-panel-tools .bom-applied-filters {
    flex: 1 1 100%;
}

.bom-panel-tools .bom-applied-filters:empty {
    display: none;
}

#project-material-list table,
#calculator-material-list table {
    margin-top: 0;
    table-layout: fixed;
}

#project-material-list .bom-col-material,
#calculator-material-list .bom-col-material {
    width: 28.5%;
}

#project-material-list .bom-col-assembly,
#calculator-material-list .bom-col-assembly,
#project-material-list .bom-col-total,
#calculator-material-list .bom-col-total {
    width: 13.2%;
}

#project-material-list .bom-col-unit,
#calculator-material-list .bom-col-unit {
    width: 10.5%;
}

#project-material-list .bom-col-qty,
#calculator-material-list .bom-col-qty {
    width: 7.9%;
}

#project-material-list .bom-col-unit-price,
#calculator-material-list .bom-col-unit-price {
    width: 11.85%;
}

/* D4: Source now carries the retailer link, the status badge and (once the row is
   expanded) the provenance rows, so it takes the 3% the material column can spare. */
#project-material-list .bom-col-source,
#calculator-material-list .bom-col-source {
    width: 14.85%;
}

#project-material-list th,
#calculator-material-list th,
#project-material-list td,
#calculator-material-list td {
    padding: 14px 12px;
    overflow-wrap: anywhere;
}

#project-material-list th:first-child,
#calculator-material-list th:first-child,
#project-material-list td:first-child,
#calculator-material-list td:first-child {
    padding-left: 22px;
}

#project-material-list th:last-child,
#calculator-material-list th:last-child,
#project-material-list td:last-child,
#calculator-material-list td:last-child {
    padding-right: 22px;
}

.bom-th-numeric,
.bom-quantity-cell,
.bom-unit-price-cell,
.bom-total-cell {
    text-align: right;
}

.bom-unit-price-cell,
.bom-total-cell {
    font-family: var(--we-font-mono);
    font-size: 0.86rem;
    color: var(--we-text-2);
    white-space: nowrap;
}

.bom-total-cell {
    font-weight: 700;
    color: var(--ink);
}

.bom-price-empty {
    color: var(--we-text-4);
}

/* C2: the editable unit price. The pencil is the resting state (decision 13) - the input
   only appears once the row is being edited - so the cell keeps its numeric right rail
   and gains at most a 24px control. The cell is nowrap, so the stack has to allow the
   revealed input onto its own line. */
.bom-unit-price-stack {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
}

.bom-price-override-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--we-text-3);
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
}

.bom-price-override-toggle:hover,
.bom-price-override-toggle[aria-expanded="true"] {
    background: var(--we-band);
    color: var(--we-accent);
}

.bom-price-override-toggle:focus-visible,
.bom-price-override-input:focus-visible {
    outline: 3px solid rgba(37, 99, 235, 0.55);
    outline-offset: 2px;
}

.bom-price-override-input {
    width: 92px;
    min-height: 44px;
    padding: 6px 8px;
    border: 1px solid rgb(230, 224, 212);
    border-radius: 8px;
    background: rgb(251, 249, 244);
    font-family: var(--we-font-mono);
    font-size: 0.86rem;
    text-align: right;
    color: var(--ink);
}

/* Decision 9: this channel is styled like a field note but is wired to no risk counter,
   so it must not reuse .field-feedback's hooks. */
.price-override-feedback {
    display: grid;
    gap: 6px;
    margin-top: 6px;
    text-align: left;
    white-space: normal;
}

.price-override-feedback[hidden] {
    display: none;
}

.price-override-feedback-global {
    margin: 0 0 12px;
}

.price-override-note {
    display: block;
    padding: 8px 10px;
    border-radius: 12px;
    background: rgba(223, 232, 215, 0.65);
    color: rgba(23, 32, 51, 0.84);
    font-family: var(--we-font-sans);
    font-size: 0.8rem;
    line-height: 1.45;
}

/* Why a row has no pencil. Muted and chrome-free on purpose: it explains an absence in a
   44-row table, so it must read as a footnote, not as the feedback notes above. */
.price-override-locked {
    display: block;
    margin-top: 4px;
    color: rgba(23, 32, 51, 0.56);
    font-family: var(--we-font-sans);
    font-size: 0.72rem;
    line-height: 1.35;
    text-align: left;
    white-space: normal;
}

.price-override-note[data-price-override-tone="warning"],
.price-override-note[data-price-override-tone="error"] {
    background: rgba(244, 229, 199, 0.82);
    color: #7f4d18;
}

#project-material-list .bom-quantity-stack,
#calculator-material-list .bom-quantity-stack {
    justify-items: end;
}

#project-material-list .bom-quantity-stack strong,
#calculator-material-list .bom-quantity-stack strong {
    padding: 0;
    background: none;
    font-family: var(--we-font-mono);
    font-size: 0.86rem;
}

.bom-row {
    cursor: pointer;
}

/* D4: the chevron rides the material name's first line instead of being absolutely
   positioned, so multi-line names, review badges and the Explain button can't shift it. */
.bom-material-head {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.bom-row-chevron {
    display: inline-flex;
    flex: none;
    align-items: center;
    width: 1em;
    /* matches .bom-material-primary's first line box (0.92rem x 1.45) */
    height: 1.334rem;
    color: var(--we-accent-soft);
    font-size: 0.7rem;
    line-height: 1;
}

.bom-row-chevron::before {
    content: "\25B8";
}

.bom-row.is-expanded .bom-row-chevron::before {
    content: "\25BE";
}

.bom-row.is-expanded > td {
    border-bottom-color: transparent;
    background: rgba(242, 238, 229, 0.5);
}

.bom-assembly-cell {
    display: table-cell;
}

.bom-assembly-name {
    display: inline-block;
    vertical-align: middle;
    color: var(--we-text-2);
    font-size: 0.82rem;
}

.bom-layer-control {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    min-height: 24px;
    margin-left: 8px;
    padding: 0 7px;
    vertical-align: middle;
    border: 1px solid var(--we-border-input);
    border-radius: var(--we-radius-pill);
    background: var(--we-card-sub);
    color: var(--we-text-3);
    font-family: var(--we-font-mono);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
}

.bom-layer-control:hover {
    border-color: var(--we-accent);
    color: var(--we-accent);
}

.bom-layer-control[aria-pressed="true"] {
    background: var(--we-dark);
    border-color: var(--we-dark);
    color: #fff8ef;
}

.bom-layer-control:focus-visible {
    outline: 3px solid rgba(37, 99, 235, 0.55);
    outline-offset: 2px;
}

.bom-source-stack {
    display: grid;
    gap: 6px;
    justify-items: start;
}

/* D4: pricing provenance is not a second toggle inside the Source cell - it follows the
   row's own disclosure (gesture A), so a collapsed row shows only links + line status. */
.bom-row .bom-procurement-stack {
    display: none;
}

.bom-row.is-expanded .bom-procurement-stack {
    display: grid;
}

.bom-meta-key {
    color: rgba(23, 32, 51, 0.52);
}

.bom-explanation-row > td {
    padding: 0 22px 16px 60px;
    background: rgba(242, 238, 229, 0.5);
}

.bom-explanation-row[hidden] {
    display: none;
}

.bom-total-band {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 6px 18px;
    padding: 4px 16px 22px;
    /* border-top: 2px solid var(--we-border); */
    background: var(--we-band);
}

.bom-total-band .total-row {
    display: flex;
    flex: 1 1 100%;
    align-items: baseline;
    justify-content: space-between;
    gap: 18px;
    margin: 0;
    font-size: 0.98rem;
    font-weight: 700;
    color: var(--ink);
}

.bom-total-band .total-row strong {
    font-family: var(--we-font-mono);
    font-size: 1.05rem;
}

.bom-total-band .total-breakdown {
    display: flex;
    flex: 1 1 100%;
    flex-wrap: wrap;
    gap: 6px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--we-border);
}

.bom-total-band .total-breakdown[hidden] {
    display: none;
}

.bom-total-band .total-breakdown-row {
    font-weight: 500;
}

.bom-total-band .total-row-meta {
    margin: 0;
    font-size: 0.8rem;
}

.bom-labor-action {
    margin-top: 12px;
    padding: 14px 16px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-surface-warm);
}

.bom-labor-action .checkbox-row {
    align-items: flex-start;
    cursor: pointer;
}

.bom-labor-action .checkbox-row strong {
    font-size: 1rem;
}

.bom-maturity-toolbar,
.bom-explorer-toolbar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin: 18px 0 12px;
}

.bom-panel-tools > .bom-maturity-toolbar,
.bom-panel-tools > .bom-explorer-toolbar {
    margin: 0;
    align-items: stretch;
}

#project-material-list .bom-explorer-summary,
#calculator-material-list .bom-explorer-summary {
    margin: 14px 22px;
}

.bom-maturity-toolbar {
    align-items: center;
}

.bom-maturity-toolbar[hidden] {
    display: none !important;
}

.bom-explorer-controls {
    display: grid;
    grid-template-columns: repeat(3, minmax(150px, 1fr));
    gap: 12px;
    flex: 1 1 480px;
}

.bom-explorer-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    color: var(--muted);
    font-size: 0.86rem;
    font-weight: 600;
}

.bom-explorer-field input,
.bom-explorer-field select {
    min-height: 44px;
    padding: 7px 13px;
    background: rgb(251, 249, 244);
    border: 1px solid rgb(230, 224, 212);
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    gap: 8px;
    font-size: 12px;
    color: rgb(90, 83, 72);
}

.bom-explorer-field select:focus-visible,
.bom-explorer-field input:focus-visible,
.bom-explanation-toggle:focus-visible,
.three-stage-chip:focus-visible,
.preview-stage-chip:focus-visible {
    outline: 3px solid rgba(37, 99, 235, 0.55);
    outline-offset: 2px;
}

.bom-explorer-actions {
    display: flex;
    gap: 10px;
    margin-top: 20px;
}
.bom-explorer-actions >button {
    min-height: 44px;
}

.bom-explorer-action {
    min-width: 112px;
}

.bom-maturity-badge {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    min-height: 1.75rem;
    padding: 0.2rem 0.55rem;
    border-radius: var(--we-radius-pill);
    font-size: 0.78rem;
    font-weight: 700;
    background: #eef2ff;
    color: #1f2937;
}

.bom-maturity-badge[data-bom-maturity-tone="success"] {
    background: #dcfce7;
    color: #166534;
}

.bom-maturity-badge[data-bom-maturity-tone="warning"] {
    background: #fef3c7;
    color: #92400e;
}

.bom-maturity-badge[data-bom-maturity-tone="danger"] {
    background: #fee2e2;
    color: #991b1b;
}

.bom-explanation-toggle {
    min-height: 44px;
    margin-top: 0.5rem;
}

#project-material-list .bom-explanation-toggle,
#calculator-material-list .bom-explanation-toggle {
    min-height: 0;
    margin-top: 8px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--we-accent);
    font-size: 0.74rem;
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.bom-explanation {
    margin-top: 0.5rem;
    padding: 0.75rem;
    border: 1px solid rgba(15, 23, 42, 0.12);
    border-radius: 0.75rem;
    background: #f8fafc;
}

#project-material-list .bom-explanation,
#calculator-material-list .bom-explanation {
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
}

#project-material-list .bom-explanation strong,
#calculator-material-list .bom-explanation strong {
    display: block;
    color: var(--we-text-4);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

#project-material-list .bom-explanation-meta,
#calculator-material-list .bom-explanation-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 18px;
    margin-top: 6px;
}

#project-material-list .bom-explanation-meta .bom-meta-line,
#calculator-material-list .bom-explanation-meta .bom-meta-line {
    margin-top: 0;
}

#project-material-list .bom-explanation p,
#calculator-material-list .bom-explanation p,
#project-material-list .bom-explanation li,
#calculator-material-list .bom-explanation li {
    font-family: var(--we-font-mono);
    font-size: 0.76rem;
    line-height: 1.6;
    color: var(--we-text-2);
}

.bom-explanation p {
    margin: 0.35rem 0;
    color: var(--muted);
}

.bom-explanation ul {
    margin: 0.5rem 0 0;
    padding-left: 1.1rem;
}

.bom-explorer-summary {
    display: grid;
    gap: 10px;
    margin: 0 0 14px;
}

.bom-explorer-summary-chips,
.bom-explorer-subtotals {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.bom-explorer-summary-chip,
.bom-explorer-subtotal {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(120, 104, 84, 0.18);
    background: rgba(250, 246, 239, 0.92);
    color: var(--ink);
    font-size: 0.85rem;
    line-height: 1.4;
}

.bom-explorer-subtotal strong {
    font-weight: 700;
}

/* Collapsed by default: the group subtotals are read occasionally, and they are the only
   part of the explorer head whose height scales with the project. */
.bom-explorer-subtotals-toggle > summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 28px;
    padding: 2px 0;
    cursor: pointer;
    list-style: none;
    color: var(--we-text-2, var(--muted));
    font-size: 0.85rem;
    font-weight: 700;
}

.bom-explorer-subtotals-toggle > summary::-webkit-details-marker {
    display: none;
}

.bom-explorer-subtotals-toggle > summary::before {
    content: '';
    width: 0;
    height: 0;
    border-left: 5px solid currentColor;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform 120ms ease;
}

.bom-explorer-subtotals-toggle[open] > summary::before {
    transform: rotate(90deg);
}

.bom-explorer-subtotals-toggle > summary:focus-visible {
    outline: 2px solid var(--we-focus, currentColor);
    outline-offset: 2px;
}

.bom-explorer-subtotals-toggle > .bom-explorer-subtotals {
    padding-top: 4px;
}

/* 门店小计与装配小计共用 .bom-explorer-subtotal 的排版和默认折叠状态,
   只在这里区分两点:门店块与装配块之间留出间距,以及「买不到」那一格降为
   次要色,避免它在一排金额里看起来像一家最贵的店。 */
.bom-explorer-store-subtotals {
    margin-bottom: 8px;
}

.bom-explorer-subtotal.is-unlisted strong,
.bom-explorer-subtotal.is-unlisted span {
    color: var(--muted);
}

/* 提示块用与「需复核」同族的暖警示色,而不是错误红:这些行不是坏了,
   只是需要读者自己去找货源。 */
.bom-explorer-unlisted-callout {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 8px;
    padding: 10px 12px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-band);
    font-size: 0.85rem;
}

.bom-explorer-unlisted-callout > span {
    flex: 1 1 220px;
    min-width: 0;
}

.bom-explorer-unlisted-callout .button {
    min-height: 44px;
    flex: 0 0 auto;
}

/* The row only needs the 44px touch target where taps are coarse; on a mouse it would
   just spend the height this collapse was meant to save. */
@media (pointer: coarse) {
    .bom-explorer-subtotals-toggle > summary {
        min-height: 44px;
        padding: 4px 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .bom-explorer-subtotals-toggle > summary::before {
        transition: none;
    }
}

.bom-empty-row td {
    padding: 18px 16px;
    text-align: center;
    color: var(--muted);
}

input,
select,
button,
textarea {
    font: inherit;
}

input,
select {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: 56px;
    padding: 0 16px;
    border-radius: 18px;
    border: 1px solid var(--line);
    background: white;
}

select {
    overflow: hidden;
    text-overflow: ellipsis;
}

button {
    border: 0;
    cursor: pointer;
}

button:focus-visible,
a:focus-visible {
    outline: 3px solid rgba(192, 90, 43, 0.28);
    outline-offset: 2px;
}

.button:hover,
.nav-toggle:hover,
.nav-cta:hover {
    transform: translateY(-1px);
    box-shadow: var(--we-shadow-float);
}

.button.primary:hover,
.nav-cta:hover {
    background: var(--we-accent-deep);
    box-shadow: var(--we-shadow-btn);
}

.button:active,
.nav-toggle:active,
.nav-cta:active,
.button.is-pressed {
    transform: scale(0.98);
}

.button[disabled],
.button.is-loading {
    cursor: wait;
    opacity: 0.78;
}

.button[data-locked="true"]:not(.is-loading) {
    cursor: not-allowed;
    opacity: 0.58;
}

.button.is-loading::after {
    content: "";
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-right-color: transparent;
    animation: spin 0.8s linear infinite;
}

.button.is-success {
    background: #ebf7ea;
    border-color: #9fcb9c;
    color: #215d2f;
}

.button.is-error {
    background: #fff1ee;
    border-color: #edb8ad;
    color: #8f3320;
}

.action-feedback {
    min-height: 22px;
    margin: 12px 2px 0;
    font-size: 0.92rem;
    color: var(--muted);
}

.action-feedback.is-success {
    color: #215d2f;
}

.action-feedback.is-error {
    color: #8f3320;
}

@media (max-width: 960px) {
    .bom-explorer-controls {
        grid-template-columns: repeat(2, minmax(150px, 1fr));
    }
}

@media (max-width: 640px) {
    #project-material-list .bom-panel-head,
    #calculator-material-list .bom-panel-head {
        display: none;
    }

    .bom-explorer-collapse {
        margin: 0;
        border-bottom: 1px solid var(--we-border);
    }

    .bom-explorer-collapse > summary {
        min-height: 52px;
        padding: 15px 18px;
        color: var(--accent-strong);
        font-weight: 800;
        cursor: pointer;
    }

    .bom-explorer-collapse-content {
        padding: 0 18px 18px;
    }

    .bom-explorer-collapse .bom-explorer-toolbar,
    .bom-explorer-collapse .bom-explorer-summary {
        margin: 0;
    }

    .bom-explorer-collapse .bom-explorer-summary {
        margin-top: 16px;
    }

    .bom-maturity-toolbar,
    .bom-explorer-controls {
        grid-template-columns: 1fr;
        flex-direction: column;
    }

    .bom-panel-tools .bom-explorer-controls {
        flex-direction: column;
    }

    .bom-panel-tools .bom-explorer-field,
    .bom-panel-tools .bom-explorer-field[data-bom-field="search"] {
        flex: 0 0 auto;
        width: 100%;
        min-width: 0;
    }

    .bom-explorer-field input,
    .bom-explorer-field select {
        /* Same iOS focus-zoom rule as the input panel: <16px zooms and never zooms back. */
        font-size: 16px;
    }

    .bom-explorer-actions {
        width: 100%;
    }

    .bom-explorer-actions .button {
        flex: 1 1 100%;
    }

    .project-mobile-summary > .bom-select-summary {
        padding: 12px 0 0;
        border-top: 1px solid var(--we-border-row);
    }

    .project-mobile-summary > .bom-select-summary .bom-select-actions {
        width: 100%;
    }

    .project-mobile-summary > .bom-select-summary .button {
        flex: 1 1 calc(50% - 4px);
        padding-right: 8px;
        padding-left: 8px;
    }

    .bom-panel.table-scroll {
        overflow-x: visible;
    }

    .bom-panel table {
        display: block;
        width: 100%;
        min-width: 0;
    }

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

    .bom-panel tbody,
    .bom-panel tbody tr {
        display: block;
        width: 100%;
    }

    .bom-panel tbody tr {
        display: flex;
        flex-wrap: wrap;
        column-gap: 12px;
        row-gap: 4px;
        padding: 12px 0;
        border-bottom: 1px solid rgba(23, 32, 51, 0.12);
    }

    .bom-panel td {
        display: block;
        border: none;
        padding: 0;
        max-width: 100%;
        overflow-wrap: break-word;
    }

    .bom-panel td[data-label]::before {
        content: attr(data-label);
        display: block;
        font-size: 0.68rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: rgba(23, 32, 51, 0.45);
    }

    .bom-panel td.bom-material-cell {
        order: 1;
        flex: 1 1 100%;
    }

    .bom-panel td.bom-quantity-cell {
        order: 3;
    }

    .bom-panel td.link-cell {
        order: 4;
        flex: 1 1 100%;
    }

    /* Phase 4 removed the calculator's eight-column nth-child ordering: both BOM panels
       now share the seven-column `.bom-*-cell` ordering declared below. */

    /* D3 seven-column builder rows: every card cell keeps its column name and the
       2D layer control keeps a 44px tap target. */
    #project-material-list,
    #calculator-material-list {
        overflow: visible;
    }

    #project-material-list > .disclosure,
    #calculator-material-list > .disclosure,
    #project-material-list .bom-panel-head,
    #calculator-material-list .bom-panel-head,
    #project-material-list .bom-explorer-summary,
    #calculator-material-list .bom-explorer-summary {
        padding-left: 18px;
        padding-right: 18px;
        margin-left: 0;
        margin-right: 0;
    }

    #project-material-list tbody tr.bom-row,
    #calculator-material-list tbody tr.bom-row {
        padding: 12px 18px;
    }

    #project-material-list td,
    #calculator-material-list td {
        text-align: left;
    }

    #project-material-list td:first-child,
    #calculator-material-list td:first-child,
    #project-material-list td:last-child,
    #calculator-material-list td:last-child {
        padding-left: 0;
        padding-right: 0;
    }

    #project-material-list .bom-row .bom-material-cell,
    #calculator-material-list .bom-row .bom-material-cell {
        padding-left: 0;
    }

    #project-material-list .bom-quantity-cell .bom-quantity-stack,
    #calculator-material-list .bom-quantity-cell .bom-quantity-stack {
        justify-items: start;
    }

    #project-material-list td.bom-total-cell,
    #calculator-material-list td.bom-total-cell {
        order: 2;
        font-weight: 700;
    }

    #project-material-list td.bom-source-cell,
    #calculator-material-list td.bom-source-cell {
        order: 4;
        flex: 1 1 100%;
    }

    #project-material-list td.bom-assembly-cell,
    #calculator-material-list td.bom-assembly-cell,
    #project-material-list td.bom-unit-cell,
    #calculator-material-list td.bom-unit-cell,
    #project-material-list td.bom-unit-price-cell,
    #calculator-material-list td.bom-unit-price-cell {
        order: 5;
        font-size: 0.8rem;
        color: rgba(23, 32, 51, 0.6);
        max-width: 100%;
    }

    #project-material-list tr.bom-row.is-expanded,
    #calculator-material-list tr.bom-row.is-expanded {
        border-bottom: 0;
    }

    #project-material-list tr.bom-explanation-row,
    #calculator-material-list tr.bom-explanation-row {
        display: block;
        padding: 0 18px 14px;
    }

    #project-material-list tr.bom-explanation-row[hidden],
    #calculator-material-list tr.bom-explanation-row[hidden] {
        display: none;
    }

    #project-material-list .bom-explanation-row > td,
    #calculator-material-list .bom-explanation-row > td {
        padding: 0;
        background: none;
    }

    .bom-layer-control {
        min-width: 44px;
        min-height: 44px;
    }

    /* Phone rows are progressive disclosure, not a full card. Stacked, every row printed
       ten labelled fields and measured ~547px, so a 23-line deck BOM ran ~12,600px and
       could not be scanned or carried into a store. A collapsed row now uses two compact
       lines: the material name gets the first, then qty, unit, total and retailer share the
       second. Everything else rides the existing row expansion. Desktop is untouched. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded),
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) {
        flex-wrap: wrap;
        /* center, not baseline: the retailer link keeps a 44px tap target, and aligning a
           44px box's baseline against 21px text lines inflated the row by ~30px. */
        align-items: center;
        column-gap: 8px;
    }

    /* The name owns the first line. This restores the full-width fallback declared above
       while the compact purchase fields keep their existing order on the second line. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-material-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-material-cell {
        flex: 1 1 100%;
        min-width: 0;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-material-head,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-material-head {
        min-width: 0;
        align-items: center;
        gap: 6px;
    }

    /* The former single-line layout hid the chevron because its 17px came straight out of
       the material name. The two-line layout removes that trade-off, so collapsed bound
       rows again keep a visible disclosure affordance. */
    #project-material-list tbody[data-bom-select-bound="true"] tr.bom-row:not(.is-expanded) .bom-row-chevron,
    #calculator-material-list tbody[data-bom-select-bound="true"] tr.bom-row:not(.is-expanded) .bom-row-chevron {
        display: inline-flex;
    }

    /* The name is the field worth the width, so the collapsed row gives back some of the
       panel gutter rather than truncating it earlier. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded),
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) {
        padding-left: 14px;
        padding-right: 14px;
    }

    /* The row owns the vertical rhythm now. Cells keeping their own top/bottom padding
       stacked on top of the row's, and the tallest cell (Source, held open by the link's
       44px tap target) set the row height for everyone. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td {
        padding: 0;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-material-primary,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-material-primary {
        min-width: 0;
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
    }

    /* Qty before total, matching how the numbers are read out loud ("29, four thirty-one").
       Column names drop away: on one line "$" already says which number is money. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-quantity-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-quantity-cell {
        order: 2;
        flex: 0 0 auto;
    }

    /* Purchase rows hang a coverage line under the number ("covers 130 fastener"). It is
       wide, it does not shrink, and on a one-line row it took the whole width - two rows
       rendered with no material name at all. It is row detail, so it waits for the
       expansion like the rest. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-quantity-stack small,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-quantity-stack small {
        display: none;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-total-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-total-cell {
        order: 4;
        flex: 0 0 auto;
    }

    /* Natural width, but capped: a row may carry more than one retailer link. The source is
       now secondary text on the purchase-detail line and no longer competes with the name. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-source-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-source-cell {
        order: 5;
        /* Keeping a cap still prevents "McCoy's Building Supply" from taking the whole
           detail line, while short retailers remain intact. */
        flex: 0 0 auto;
        /* The existing 30% cap keeps both dominant retailers intact and only genuinely long
           supplier names truncate. */
        max-width: 30%;
        overflow: hidden;
        white-space: nowrap;
    }

    /* The calculator panel re-adds a column header to every cell via attr(data-bom-label)
       further down this file; on a one-line row that prints a stray "Material" above the
       name, so the collapsed row drops the headers it does not need. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-quantity-cell::before,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-quantity-cell::before,
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-total-cell::before,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-total-cell::before,
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-material-cell::before,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-material-cell::before {
        display: none;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-source-stack,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-source-stack {
        display: block;
    }

    /* Retailer names run long ("McCoy's Building Supply"), and the capped source cell was
       slicing them mid-word. The link becomes a block so text-overflow can apply - a flex
       container cannot ellipsize its own text - and line-height carries the 44px tap target
       that the flex centering used to provide. */
    /* The links stack is a flex container, so it sizes to max-content and pushes past the
       capped cell - the ellipsis has to be able to engage on a block that is actually
       bounded by the cell, not on one that overflows it. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-links-stack,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-links-stack {
        display: block;
        min-width: 0;
        max-width: 100%;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-links-stack a,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-links-stack a {
        display: block;
        padding: 0;
        min-height: 44px;
        line-height: 44px;
        color: rgba(23, 32, 51, 0.6);
        font-size: 0.78rem;
        max-width: 100%;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* Review dependencies are row detail like the rest; they only surface on expansion. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-review-dependencies,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-review-dependencies {
        display: none;
    }

    /* The chevron carries the affordance on a collapsed row, so the Row details button
       steps out of the line - but stays reachable: it is clipped, not display:none, and it
       comes back the moment it takes focus, so the keyboard path is intact. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-explanation-toggle,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-explanation-toggle {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-explanation-toggle:focus-visible,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-explanation-toggle:focus-visible {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip: auto;
    }
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-assembly-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-assembly-cell,
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-unit-price-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-unit-price-cell {
        display: none;
    }

    /* Unit stays. The quantity cell prints the number only - the unit lives in its own
       column - so hiding it left rows reading "38" where the plan means "38 bags". It sits
       straight after the number on the second line and drops its column header. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-unit-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-unit-cell {
        order: 3;
        /* Does not shrink: a unit clipped to "bo..." is worse than keeping its natural
           width on the compact purchase-detail line. */
        flex: 0 0 auto;
        white-space: nowrap;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-unit-cell::before,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-unit-cell::before {
        display: none;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-material-secondary,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-material-secondary {
        display: none;
    }

    /* The Source column header is pure decoration on the compact purchase-detail line; the
       links inside the same cell are not, so the cell stays and only its header folds away. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-source-cell::before,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-source-cell::before {
        display: none;
    }

    /* The maturity badge (Shopping-ready / Estimate-only) is a buying-risk signal, so it is
       taken out of the line's layout but deliberately NOT out of the accessibility tree:
       clipped instead of display:none, it still costs zero height while a screen reader
       continues to announce the row's status without opening the row. */
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-maturity-badge,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) .bom-maturity-badge {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
}

/* Below ~360px the one-line row runs out of room: measured on a 320px phone the material name
   was squeezed to 2px on deck (0px on outdoor-kitchen, which also pushed the page 47px wider
   than the viewport). A name nobody can read defeats the list, and a retailer is the one field
   that is still one tap away in the row expansion, so it steps out here. Deliberately narrower
   than the 640px rules above: at 390px everything fits and nothing is dropped. */
@media (max-width: 360px) {
    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-source-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-source-cell {
        display: none;
    }

    #project-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-material-cell,
    #calculator-material-list tbody[data-bom-row-disclosure-bound="true"] tr.bom-row:not(.is-expanded) > td.bom-material-cell {
        min-width: 8ch;
    }
}

@media print {
    .bom-maturity-toolbar,
    .bom-explorer-toolbar,
    .bom-explorer-summary,
    .lead-panel,
    .project-mobile-summary,
    .feedback-entry {
        display: none !important;
    }

    .table-scroll {
        overflow: visible;
    }

    .project-support-education-cards[hidden],
    .project-support-education-cards {
        display: grid !important;
    }

    /* A Letter/A4 sheet is wider than 640px, so the phone collapse rules normally never
       apply to a printout (verified: a real Letter render still carries the Assembly and
       Unit price columns). Narrow or custom paper can match that width query though, and a
       printed shopping list must never silently drop columns - nobody can tap a row open on
       paper. This restores every cell the collapsed row folds away. */
    #project-material-list tr.bom-row > td,
    #calculator-material-list tr.bom-row > td,
    #project-material-list tr.bom-row .bom-material-secondary,
    #calculator-material-list tr.bom-row .bom-material-secondary,
    #project-material-list tr.bom-row .bom-maturity-badge,
    #calculator-material-list tr.bom-row .bom-maturity-badge {
        display: block !important;
    }

    #project-material-list tr.bom-row,
    #calculator-material-list tr.bom-row {
        flex-wrap: wrap !important;
    }
}

.app-feedback {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 30;
    max-width: min(360px, calc(100vw - 32px));
    padding: 14px 18px;
    border-radius: 18px;
    background: rgba(23, 32, 51, 0.94);
    color: white;
    box-shadow: 0 20px 44px rgba(23, 32, 51, 0.22);
    transform: translateY(18px);
    opacity: 0;
    transition: opacity 180ms ease, transform 180ms ease;
}

.app-feedback.is-visible {
    opacity: 1;
    transform: translateY(0);
}

.app-feedback.is-success {
    background: rgba(33, 93, 47, 0.94);
}

.app-feedback.is-error {
    background: rgba(143, 51, 32, 0.96);
}

.preview-header {
    display: grid;
    gap: 10px;
    background: rgba(255, 244, 225, 0.96);
    color: #8c3f1d;
}

.preview-header-fallback {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: 14px;
}

.preview-header-fallback h2 {
    margin: 0;
}

.preview-header-meta {
    display: flex;
    align-items: start;
    justify-content: end;
    gap: 10px;
    flex-wrap: wrap;
}

.project-preview-status-pill[data-preview-status-tone="success"] {
    border-color: rgba(74, 119, 74, 0.26);
    background: rgba(239, 247, 236, 0.96);
    color: #2f6a3d;
}

/* Update / Reset share one row and the helper line wraps below both, so the block costs
   one row of vertical space instead of three. The hint still sits under the buttons - it
   is forced onto its own line by the 100% basis rather than by stacking the container. */
.project-input-submit {
    margin-top: 18px;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 10px;
}

/* The pair gets its own nowrap row rather than riding the wrapping container: a wrapping
   flex container assigns lines by each item's content width *before* it shrinks anything,
   so on the wrapping container the Spanish pair ("Actualizar estimacion" + "Restablecer
   entradas" want 517px in a 318px sidebar) dropped straight back to two rows, while a
   zero flex-basis fixed Spanish by squeezing English's "Reset inputs" onto two lines for
   no reason. Nowrap keeps each label on one line wherever it fits and only shrinks when
   it genuinely cannot. */
.project-input-actions {
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 10px;
    flex: 1 1 100%;
    min-width: 0;
}

.project-input-actions > .button.primary {
    flex: 1 1 auto;
    min-width: 0;
}

.project-input-actions > .button.secondary {
    flex: 0 1 auto;
    min-width: 0;
}

.project-input-submit > .field-help {
    flex: 1 1 100%;
    margin: 0;
}

.project-action-lock-note {
    margin: 8px 0 0;
    font-size: 0.85rem;
    color: #8c3f1d;
}

.quote-cta-card > .project-action-feedback {
    margin-top: 10px;
}

.quote-cta-card > .project-action-feedback:empty {
    display: none;
}

/* Task C1-4: the opt-in labor estimate card. A standalone sibling that sits directly above
   the quote CTA and borrows its card language, so the two read as one closeout block. */
.labor-estimate-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: var(--we-card-sub);
    border: 1px solid var(--we-border);
    border-radius: 13px;
    padding: 18px 20px;
    box-shadow: none;
}

.labor-estimate-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 18px;
    flex-wrap: wrap;
}

.labor-estimate-copy {
    min-width: 220px;
    flex: 1 1 260px;
}

.labor-estimate-title {
    margin: 0;
    font-family: var(--we-font-serif);
    font-size: 1.06rem;
    font-weight: 700;
    color: var(--we-text);
}

.labor-estimate-summary {
    margin: 3px 0 0;
    max-width: 460px;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--we-text-3);
}

.labor-estimate-figure {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex: 0 0 auto;
    text-align: right;
}

.labor-estimate-figure-label,
.labor-estimate-range {
    font-size: 0.74rem;
    color: var(--we-text-3);
}

.labor-estimate-amount {
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--we-text);
}

.labor-estimate-subtotal-note {
    margin: 0;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--we-text-2);
}

.labor-estimate-basis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 8px 18px;
    margin: 0;
}

.labor-estimate-basis-row dt {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--we-text-4);
}

.labor-estimate-basis-row dd {
    margin: 2px 0 0;
    font-size: 0.84rem;
    color: var(--we-text);
}

.labor-estimate-scope {
    margin: 0;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--we-text-2);
}

.labor-estimate-methodology-title {
    margin: 0 0 4px;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--we-text-4);
}

.labor-estimate-items {
    margin: 0;
    padding-left: 18px;
    display: grid;
    gap: 4px;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--we-text-3);
}

/* D16: quote CTA card closes the right result column, ahead of the full-width material list. */
.quote-cta-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    flex-wrap: wrap;
    background: var(--we-quote-bg);
    border: 1px solid var(--we-quote-border);
    border-radius: 13px;
    padding: 18px 20px;
    box-shadow: none;
}

/* 设计稿 §8:第一行 文案(左)+ 主 CTA(右),次级动作换行到第二行——真实动作数(6)
   与内联邮箱输入使单行排布不可能,这是设计的 flex-wrap 兜底 */
.quote-cta-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    flex-wrap: wrap;
    flex: 1 0 100%;
}

.quote-cta-copy {
    min-width: 220px;
    flex: 1 1 260px;
}

.quote-cta-head > .quote-cta-primary {
    flex: 0 0 auto;
}

.quote-cta-title {
    margin: 0;
    font-family: var(--we-font-serif);
    font-size: 1.06rem;
    font-weight: 700;
    color: var(--we-text);
}

.quote-cta-note {
    margin: 3px 0 0;
    max-width: 360px;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--we-text-3);
}

.quote-cta-head > .quote-cta-primary {
    flex: 0 0 auto;
    white-space: nowrap;
}

.quote-cta-card > .project-action-lock-note {
    margin-top: 10px;
}

.headline-pill {
    display: inline-flex;
    align-items: center;
    min-height: 48px;
    padding: 0 16px;
    border-radius: var(--we-radius-pill);
    background: var(--sage);
    font-weight: 700;
}

.headline-pill-total {
    display: grid;
    gap: 3px;
    min-width: 152px;
    padding: 10px 16px 12px;
    justify-items: start;
}

.headline-pill-total small {
    color: rgba(23, 32, 51, 0.62);
    font-size: 0.62rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.headline-pill-total strong {
    color: #172033;
    font-size: 1.05rem;
    line-height: 1.1;
}

.preview-surfaces {
    /* auto-fit collapses the empty track, so the lone 2D card stays full width until the
       3D card is unhidden; from then on the two cards share the results column. */
    display: grid;
    /* `min(320px, 100%)` instead of a bare 320px: on a 320px-wide phone the preview panel
       is only ~238px, and a hard 320px minimum track overflowed the shell by 41px. */
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    gap: 14px;
    margin-top: 16px;
}

/* On a 1440 desktop the results column is ~852px, so two equal tracks left the 2D canvas
   at 405 logical px -- narrow enough that its annotation lane had to be dropped. The page
   shell stays at the spec's 1240 max width (redesign spec §2.3); the two preview cards
   just stop splitting the column evenly. The 2D preview carries dimension text, callouts
   and the disclosure rail, the 3D preview carries an orbitable model that reads fine
   smaller. `:has()` keeps auto-fit behaviour when the 3D card is absent or hidden, so the
   lone 2D card still spans the full column. */
@media (min-width: 1200px) {
    .preview-surfaces:has(> .preview-surface[data-preview-surface="three"]:not([hidden])) {
        grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    }
}

.preview-surface {
    min-width: 0;
    padding: 12px;
    border-radius: 26px;
    border: 1px solid rgba(216, 207, 192, 0.84);
    background: linear-gradient(180deg, rgba(255, 251, 246, 0.94) 0%, rgba(246, 239, 230, 0.9) 100%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.74), 0 18px 32px rgba(23, 32, 51, 0.08);
}

.preview-surface[hidden] {
    display: none;
}

.preview-surface.is-expanded {
    grid-column: 1 / -1;
}

.preview-surface.is-expanded .preview-stage #preview-canvas {
    height: clamp(320px, 48vh, 560px);
    min-height: clamp(320px, 48vh, 560px);
}

/* D18 gives the expanded 2D canvas a 48vh height budget; the 3D stage takes the same one,
   so widening either card to the column width reads as one system. `!important` for the
   same reason `.three-stage.is-zoomed` needs it: common_three.js writes an inline pixel
   height derived from the stage width, and at 858px wide that is 480-520px regardless of
   the viewport. No `min-height` here on purpose -- bathroom's taller slug contract
   (`clamp(440px, 42vw, 540px)`) is a min-height and has to keep winning. */
.preview-surface.is-expanded [data-three-stage] {
    height: clamp(320px, 48vh, 560px) !important;
}

.preview-surface-header {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
}

.preview-surface-copy {
    display: grid;
    gap: 4px;
    margin-right: auto;
}

.preview-surface-eyebrow {
    margin: 0;
    color: rgba(23, 32, 51, 0.58);
    font-size: 0.68rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.preview-surface-title {
    margin: 0;
    color: #172033;
    font-size: 1rem;
    line-height: 1.2;
}

.preview-stage {
    position: relative;
    margin-top: 10px;
}

body.preview-stage-open {
    overflow: hidden;
}

.preview-stage:focus-visible {
    outline: 2px solid rgba(23, 32, 51, 0.32);
    outline-offset: 6px;
}

.preview-stage.is-zoomed {
    position: fixed;
    inset: 24px;
    z-index: 1200;
    margin-top: 0;
    padding: 18px;
    border-radius: 28px;
    background: rgba(255, 251, 246, 0.98);
    box-shadow: 0 30px 80px rgba(23, 32, 51, 0.28);
}

.preview-stage.is-zoomed #preview-canvas {
    width: 100%;
    height: calc(100vh - 120px);
    min-height: 0;
    object-fit: contain;
}

/* The 3D stage carries the same `.is-zoomed` contract as the 2D stage. `position` and
   `height` need `!important` because the stage collects inline styles from JS:
   common_three.js writes a pixel height sized off the collapsed stage width, and the
   first-use 3D guidance overlay pins `position: relative` on the stage for the rest of
   the session. The fullscreen box has to come from the inset instead. */
.three-stage.is-zoomed {
    position: fixed !important;
    inset: 24px;
    z-index: 1200;
    width: auto;
    height: auto !important;
    min-height: 0;
    margin-top: 0;
    border-radius: 28px;
    background: rgba(255, 251, 246, 0.98);
    box-shadow: 0 30px 80px rgba(23, 32, 51, 0.28);
}

@media (max-width: 720px) {
    .calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail {
        grid-template-columns: 1fr;
    }

    .calculator-layout[data-slug="outdoor-kitchen-builder"] .outdoor-kitchen-preview-rail-chip-wide {
        grid-column: auto;
    }

    .calculator-layout[data-slug="outdoor-kitchen-builder"] #preview-canvas {
        height: 330px;
        min-height: 330px;
    }

    .calculator-layout[data-slug="outdoor-kitchen-builder"] .preview-stage.is-zoomed #preview-canvas {
        min-height: 0;
    }

    .bom-explorer-toolbar,
    .bom-maturity-toolbar {
        grid-template-columns: 1fr;
    }

    .bom-explorer-actions,
    .three-stage-controls,
    .three-stage-layer-controls {
        flex-wrap: wrap;
    }

    .preview-stage.is-zoomed {
        inset: 12px;
        padding: 12px;
        border-radius: 20px;
    }

    .three-stage.is-zoomed {
        inset: 12px;
        border-radius: 20px;
    }

    .preview-stage.is-zoomed #preview-canvas {
        height: calc(100vh - 92px);
    }
}

#preview-canvas,
.three-stage {
    margin-top: 0;
    width: 100%;
    min-height: 320px;
    border-radius: 24px;
    background: radial-gradient(circle at 50% 24%, rgba(255, 249, 239, 0.92) 0%, rgba(232, 221, 205, 0.95) 34%, rgba(202, 186, 165, 0.98) 100%);
    border: 1px solid var(--line);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), inset 0 -22px 42px rgba(79, 57, 36, 0.12);
}

.preview-stage #preview-canvas {
    margin-top: 0;
    display: block;
}

.preview-loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.6);
    border-radius: 24px;
    font-size: 0.88rem;
    color: rgba(23, 32, 51, 0.7);
    z-index: 2;
    pointer-events: none;
}

.preview-stage-hint {
    margin: 12px 0 0;
    color: rgba(23, 32, 51, 0.7);
    font-size: 0.84rem;
    line-height: 1.45;
}

/* D18 two-level preview zoom controls. */
.preview-zoom-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
}

.preview-zoom-button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 8px;
    border: 1px solid #e2dccd;
    background: #fbf9f4;
    color: #8a6a3a;
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    user-select: none;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.preview-zoom-button:hover {
    background: #f2eee5;
    border-color: #c9bfa8;
}

.preview-zoom-button:focus-visible {
    outline: 2px solid #c2582a;
    outline-offset: 1px;
}

.preview-zoom-button[aria-pressed="true"] {
    background: #f2eee5;
    border-color: #c9bfa8;
}

.preview-zoom-button-close {
    border-color: #2a2620;
    background: #2a2620;
    color: #fff;
}

.preview-zoom-button-close:hover {
    background: #454038;
    border-color: #454038;
}

.preview-zoom-button[hidden] {
    display: none;
}

/* `display: contents` keeps the overlay a DOM grouping only. A boxed wrapper would be
   position:fixed and therefore its own stacking context, trapping the scrim below the
   sticky .site-header (z-index 10); as bare root-level children the scrim can sit under
   the lifted stage (1200) while the close button sits above it. */
.preview-fullscreen-overlay {
    display: contents;
}

.preview-fullscreen-scrim {
    position: fixed;
    inset: 0;
    z-index: 1150;
    background: rgba(28, 24, 18, 0.62);
    backdrop-filter: blur(8px);
    animation: previewFullscreenIn 0.18s ease both;
}

/* The layer chips live in the card header, so a lifted stage would leave them behind the
   scrim and the layers unswitchable. preview_expand.js marks the surface `.is-fullscreen`
   and the same slot is lifted onto the overlay layer — no DOM move, so the live re-render
   path and the click handlers stay bound to the one set of chips.
   The chips are pinned to the top band, sharing the row with the ✕ close button (top 34px,
   right 34px, ~32px wide): the 100px side inset keeps the pill clear of it at any width. */
.preview-surface.is-fullscreen .preview-surface-layer-slot:not(:empty) {
    position: fixed;
    left: 50%;
    top: 34px;
    transform: translateX(-50%);
    z-index: 1300;
    display: flex;
    justify-content: center;
    max-width: min(calc(100vw - 200px), 720px);
    padding: 8px 12px;
    border-radius: var(--we-radius-pill);
    border: 1px solid var(--we-border);
    background: var(--we-card);
    box-shadow: var(--we-shadow-card);
    overflow-x: auto;
}

.preview-fullscreen-close {
    position: fixed;
    top: 34px;
    right: 34px;
    z-index: 1300;
}

@keyframes previewFullscreenIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.preview-caption,
.preview-pricing {
    margin: 14px 0 0;
    padding: 12px 14px;
    border-radius: 16px;
    border: 1px solid rgba(216, 207, 192, 0.84);
    background: rgba(255, 251, 246, 0.9);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
    color: rgba(23, 32, 51, 0.82);
    font-size: 0.88rem;
    line-height: 1.45;
}

.preview-pricing {
    color: rgba(23, 32, 51, 0.76);
}

.preview-pricing-details {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 14px 0 0;
}

.preview-pricing-detail {
    display: inline-flex;
    align-items: center;
    min-height: 34px;
    padding: 8px 12px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(216, 207, 192, 0.84);
    background: rgba(255, 251, 246, 0.92);
    color: rgba(23, 32, 51, 0.74);
    font-size: 0.76rem;
    font-weight: 700;
    line-height: 1.35;
}

.preview-stage-controls {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 8px;
    align-items: end;
    max-width: calc(100% - 28px);
}

.calculator-layout[data-slug="garage-conversion-adu-builder"] .preview-stage-controls {
    max-width: min(256px, calc(100% - 196px));
}

.calculator-layout[data-slug="garage-conversion-adu-builder"] .preview-stage-controls {
    position: absolute;
    top: 14px;
    left: 16px;
    right: 16px;
    width: auto;
    max-width: none;
    align-items: center;
    gap: 10px;
}

.calculator-layout[data-slug="deck-builder"] #preview-canvas {
    min-height: 340px;
}

.calculator-layout[data-slug="garage-conversion-adu-builder"] .preview-stage-control-row {
    flex-wrap: nowrap;
    justify-content: center;
    overflow-x: auto;
    padding-bottom: 2px;
}

.calculator-layout[data-slug="garage-conversion-adu-builder"] #preview-canvas {
    min-height: 540px;
}

.preview-stage-control-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: end;
}

.preview-stage-subcontrols {
    padding: 8px 10px;
    border-radius: 18px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 251, 246, 0.76);
    box-shadow: 0 10px 22px rgba(23, 32, 51, 0.08);
    backdrop-filter: blur(8px);
}

.preview-stage-chip {
    min-height: 44px;
    padding: 0 12px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 251, 246, 0.9);
    color: #172033;
    font-size: 0.78rem;
    font-weight: 800;
    box-shadow: 0 10px 22px rgba(23, 32, 51, 0.1);
    backdrop-filter: blur(8px);
}

.preview-stage-chip:hover {
    transform: translateY(-1px);
    box-shadow: 0 14px 26px rgba(23, 32, 51, 0.14);
}

.preview-stage-chip.is-active {
    background: rgba(23, 32, 51, 0.9);
    border-color: rgba(23, 32, 51, 0.2);
    color: #fff8ef;
}

.preview-stage-subchip {
    min-height: 30px;
    padding: 0 10px;
    font-size: 0.72rem;
    font-weight: 700;
    box-shadow: none;
}

.three-stage {
    overflow: hidden;
    cursor: grab;
    touch-action: none;
    position: relative;
    min-height: clamp(320px, 52vw, 520px);
}

.three-stage.is-fallback {
    cursor: default;
}

.three-stage-fallback-canvas {
    display: block;
    width: 100%;
    height: 100%;
}

.three-stage-fallback-note {
    position: absolute;
    left: 16px;
    bottom: 14px;
    max-width: min(360px, calc(100% - 32px));
    margin: 0;
    padding: 10px 12px;
    border-radius: 16px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 251, 246, 0.9);
    box-shadow: 0 14px 28px rgba(23, 32, 51, 0.12);
    color: rgba(23, 32, 51, 0.82);
    font-size: 0.78rem;
    line-height: 1.45;
    backdrop-filter: blur(10px);
}

.three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-layer-controls,
.three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-legend,
.three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-assembly-stack {
    background: rgba(255, 250, 242, 0.88);
    border-color: rgba(23, 32, 51, 0.14);
}

.three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-part-label {
    background: rgba(255, 251, 246, 0.86);
    color: rgba(23, 32, 51, 0.86);
    border-color: rgba(23, 32, 51, 0.12);
}

.three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-callout-chip {
    background: rgba(255, 251, 246, 0.94);
    border-color: rgba(23, 32, 51, 0.14);
    color: #172033;
}

/* 用户 2026-08-26 要求:视图控件(Top view / Orbit / Cutaway)统一钉在舞台右下角。
   原先默认是右上角,再由 8 个 builder slug 各写一遍 bottom:14px 覆盖回右下——
   计算器不在那份名单里,于是只有计算器停在右上角。把默认直接改成右下,那 8 条
   重复覆盖(以及 <=640px 的同款覆盖)就都可以删掉,位置来源只剩这一处。 */
.three-stage-controls {
    position: absolute;
    right: 14px;
    bottom: 14px;
    z-index: 4;
    display: flex;
    gap: 8px;
}

.three-stage-layer-controls {
    position: absolute;
    top: 14px;
    left: 14px;
    right: 96px;
    z-index: 4;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    border-radius: 16px;
    background: rgba(255, 251, 246, 0.78);
    box-shadow: 0 14px 28px rgba(23, 32, 51, 0.12);
    backdrop-filter: blur(10px);
    pointer-events: auto;
}

.three-stage-layer-caption {
    color: rgba(23, 32, 51, 0.62);
    font-size: 0.66rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
}

.three-stage-layer-chip {
    min-height: 32px;
    padding: 0 11px;
    background: rgba(255, 255, 255, 0.94);
    border-color: rgba(23, 32, 51, 0.12);
    font-size: 0.76rem;
    font-weight: 800;
}

.three-stage-layer-chip:not(.is-active) {
    color: rgba(23, 32, 51, 0.72);
}

.three-stage-chip {
    min-height: 44px;
    padding: 0 12px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 251, 246, 0.88);
    color: #172033;
    font-size: 0.82rem;
    font-weight: 700;
    box-shadow: 0 10px 22px rgba(23, 32, 51, 0.1);
    backdrop-filter: blur(8px);
}

.three-stage-chip:hover {
    transform: translateY(-1px);
    box-shadow: 0 14px 26px rgba(23, 32, 51, 0.14);
}

.three-stage-chip.is-active {
    background: rgba(23, 32, 51, 0.9);
    border-color: rgba(23, 32, 51, 0.2);
    color: #fff8ef;
}

.three-stage-tooltip {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 3;
    width: max-content;
    min-width: 180px;
    max-width: min(280px, calc(100% - 32px));
    padding: 8px 10px;
    border-radius: 12px;
    background: rgba(23, 32, 51, 0.92);
    color: #fff8ef;
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1.25;
    white-space: normal;
    pointer-events: none;
    box-shadow: 0 14px 30px rgba(23, 32, 51, 0.22);
    transform: translate(-50%, calc(-100% - 12px));
    opacity: 0;
    transition: opacity 120ms ease;
}

.three-stage-tooltip.is-visible {
    opacity: 1;
}

.three-stage-legend {
    position: absolute;
    left: 14px;
    bottom: 14px;
    z-index: 2;
    max-width: min(260px, calc(100% - 28px));
    padding: 10px 12px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    border-radius: 16px;
    background: rgba(255, 251, 246, 0.86);
    box-shadow: 0 14px 28px rgba(23, 32, 51, 0.12);
    backdrop-filter: blur(10px);
    pointer-events: auto;
}

.three-stage-legend.is-collapsed {
    max-width: min(190px, calc(100% - 28px));
    padding: 8px 10px;
}

/* .three-stage-controls sits bottom-right, sharing the same 14px band as this
   legend's bottom-left anchor. Card-state stage width (~340px) is narrower than the
   expanded legend plus the chip row combined, so they can't sit side by side -- lift
   the legend clear of the 44px-tall chip row instead.
   2026-08-26: this used to be scoped to [data-tool="project"], because back then only
   builders put the controls bottom-right. Calculators now do too, so they need the same
   lift -- their expanded legend is up to 260px wide and would land under the chips. */
.calculator-layout .three-stage-legend {
    bottom: 66px;
}

/* 用户 2026-08-21 要求：放得下时图例与 Top view / Orbit 同行。
   上面那条 bottom: 66px 是无条件抬高，它的注释理由（卡片态 340px 放不下）**只对部分
   页面成立**——实测 pergola 需要 302px（余 38px，放得下），garage-conversion-adu 需要
   397px（差 57px，放不下，它的控件行多一组视图切换）。
   所以改成实测切类：common_three.js 的 canInlineLegendRow() 量出真放得下才加
   .is-inline-row，放不下仍走上面的 66px。舞台缩放时跟着重算——garage 在卡片态
   放不下，展开到 471px 就放得下了。 */
.calculator-layout .three-stage-legend.is-inline-row {
    bottom: 14px;
}

.three-stage-legend-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.three-stage-legend-title {
    margin: 0;
    color: rgba(23, 32, 51, 0.66);
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.three-stage-legend.is-collapsed .three-stage-legend-title {
    display: none;
}

.three-stage-legend-toggle {
    min-height: 30px;
    padding: 0 11px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 255, 255, 0.88);
    color: #172033;
    font-size: 0.74rem;
    font-weight: 800;
    white-space: nowrap;
    box-shadow: 0 8px 18px rgba(23, 32, 51, 0.08);
}

.three-stage-legend-toggle:hover {
    transform: translateY(-1px);
}

.three-stage-legend-body[hidden] {
    display: none;
}

.three-stage-legend-body {
    margin-top: 8px;
}

.three-stage-legend-note {
    margin: 0 0 8px;
    color: rgba(23, 32, 51, 0.7);
    font-size: 0.72rem;
    font-weight: 650;
    line-height: 1.35;
}

.three-stage-legend-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 12px;
}

.three-stage-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    color: #172033;
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1.2;
}

/* .three-stage-legend-item sets display:inline-flex, which defeats the [hidden]
   attribute the same way .button did (see B-07 above). Without this, layer
   filtering sets [hidden] but every entry still renders and takes space. */
.three-stage-legend-item[hidden] {
    display: none;
}

.three-stage-legend-swatch {
    width: 12px;
    height: 12px;
    flex: 0 0 12px;
    border-radius: 999px;
    border: 1px solid rgba(23, 32, 51, 0.16);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
}

.three-stage-callouts {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
}

.three-stage-callout-leaders {
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    pointer-events: none;
    overflow: visible;
}

.three-stage-part-labels {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
}

.three-stage-assembly-stack {
    position: absolute;
    top: 76px;
    right: 14px;
    z-index: 2;
    width: min(184px, calc(100% - 28px));
    padding: 10px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    border-radius: 18px;
    background: rgba(255, 251, 246, 0.88);
    box-shadow: 0 16px 32px rgba(23, 32, 51, 0.14);
    backdrop-filter: blur(10px);
    pointer-events: none;
}

.three-stage-assembly-stack[hidden] {
    display: none;
}

.three-stage-assembly-stack-title {
    margin: 0 0 8px;
    color: rgba(23, 32, 51, 0.66);
    font-size: 0.64rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.three-stage-assembly-stack-list {
    display: grid;
    justify-items: center;
    gap: 6px;
}

.three-stage-assembly-stack-item {
    width: 100%;
    min-height: 30px;
    padding: 7px 8px;
    border-radius: 12px;
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 255, 255, 0.94);
    color: #172033;
    font-size: 0.7rem;
    font-weight: 800;
    line-height: 1.25;
    text-align: center;
    box-shadow: 0 8px 18px rgba(23, 32, 51, 0.08);
}

.three-stage-assembly-stack-item.is-ground {
    background: rgba(223, 232, 215, 0.84);
}

.three-stage-assembly-stack-arrow {
    color: rgba(23, 32, 51, 0.66);
    font-size: 0.92rem;
    font-weight: 900;
    line-height: 1;
}

/* 墙体构造示意图（2026-08-22）。顶部横带是实测唯一真正空白的区域：
   宽屏舞台 471×354 时模型包围盒是 y 97–353，顶上那条没有任何覆盖层。
   不用 .three-stage-assembly-stack 的位置（top:76; right:14）——实测它会压到右栏 chip。 */
.three-stage-wall-assembly {
    position: absolute;
    top: 12px;
    left: 14px;
    /* 宽度跟着舞台走。写死 460px 时，全屏舞台 1437px 下小图只占 32%，
       字比宽屏态还难读——而全屏本该是看得更清楚的那个模式（2026-08-22 复审实测）。
       54% 让宽屏保持在 ~460px（最初实测并验证过的尺寸），全屏放大到 ~780px。
       下限 320px 防窄舞台压字：1024 视口下这个舞台只有 547px 宽。
       高度不写死，交给 aspect-ratio 按 viewBox 的 460:110 算——
       两者必须一致，否则 preserveAspectRatio 会留出空白边。 */
    width: clamp(320px, 54%, 820px);
    aspect-ratio: 460 / 110;
    z-index: 2;
    pointer-events: none;
    overflow: visible;
}

/* 全屏时 preview_expand.js 把分层控件移进舞台顶部中间（实测占 y 10–51），
   而小图放大后横向能到 x=790，会直接压在 chip 上。宽屏态那排控件在舞台外，
   所以只有全屏需要让位。 */
.preview-surface.is-fullscreen .three-stage-wall-assembly {
    top: 72px;
}

.three-stage-wall-assembly[hidden] {
    display: none;
}

.three-stage-wall-assembly-member {
    fill: rgba(145, 103, 60, 0.55);
    stroke: rgba(255, 251, 246, 0.9);
    stroke-width: 0.8;
}

.three-stage-wall-assembly-member.is-header {
    fill: rgba(118, 86, 61, 0.72);
}

/* 过梁端头的断面：比梁身更实，中间那条浅线把 3in 厚分成两根 1.5in 的 2x6。 */
.three-stage-wall-assembly-section {
    /* 要比梁身(is-header)明显更深才看得出这是剖开的端头，
       否则两者混成一块、中间那条线像是随手画的。 */
    fill: rgba(62, 43, 28, 0.95);
    stroke: rgba(255, 251, 246, 0.92);
    stroke-width: 0.8;
}

.three-stage-wall-assembly-section-line {
    fill: none;
    stroke: rgba(255, 251, 246, 0.98);
    stroke-width: 1.4;
}

.three-stage-wall-assembly-dim {
    fill: none;
    stroke: rgba(23, 32, 51, 0.5);
    stroke-width: 0.9;
}

.three-stage-wall-assembly-label {
    fill: rgba(23, 32, 51, 0.78);
    font-size: 8px;
    font-weight: 800;
}

.three-stage-wall-assembly-note {
    fill: rgba(23, 32, 51, 0.5);
    font-size: 7px;
    font-weight: 600;
}

.three-stage-part-label {
    position: absolute;
    left: 0;
    top: 0;
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    padding: 0 9px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(23, 32, 51, 0.08);
    background: rgba(255, 251, 246, 0.72);
    color: rgba(23, 32, 51, 0.78);
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1;
    box-shadow: 0 10px 18px rgba(23, 32, 51, 0.08);
    backdrop-filter: blur(8px);
    transform: translate(-50%, -50%);
    white-space: nowrap;
}

/* 贴合式标签限宽（2026-08-22）。边栏每侧占 30% - 10px，两侧内缩各 18px，
   所以中间通道正好是 100% - 2*18px - 2*(30% - 10px) = 40% - 16px。
   再减去两侧各 6px 的 LABEL_RAIL_GAP（common_three.js），得 40% - 28px——
   这样"左右两栏都被占"时，标签连同两侧间隙**正好塞得进通道**，
   common_three.js 里的夹取分支才有意义（40% - 16px 时是 132 > 120，夹不进去）。
   完整名字进 title，并由左下角图例兜底。三个数字必须一起改。 */
.three-stage-part-label {
    max-width: calc(40% - 28px);
}

.three-stage-part-label-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.three-stage-part-label[hidden] {
    display: none;
}

/* Rail mode (Task 7) can now toggle is-detached onto plain inline part-labels
   (kind:'material' addPartLabel sites), not just the pre-existing edge-callout
   chips. Without this override the base translate(-50%,-50%) centering stays
   active and the label renders offset from the rail column .three-stage-callout
   is positioned at (see .three-stage-callout.is-detached below). */
.three-stage-part-label.is-detached {
    transform: none;
}

/* 实物标注（kind: 'part'）：标签体让开锚点，由画在引线层里的 △ 尖端指认锚点。
   基础规则 .three-stage-part-label 的 translate(-50%, -50%) 会把标签压在锚点上
   （正是 2026-08-21 设计要修的问题），这里必须整条覆盖掉。 */
.three-stage-part-label.has-part-tip.is-tip-left {
    transform: translate(0, -50%);
}

.three-stage-part-label.has-part-tip.is-tip-right {
    transform: translate(-100%, -50%);
}

.three-stage-part-tip {
    opacity: 0.92;
}

/* 必须是一体式省略号 U+2026，不是三个句点。三个句点在小字号下会被读成
   三个独立的点，且与 △ 尖端、● 圆点这两个标记争夺视觉。 */
.three-stage-part-label.is-omission::before {
    content: '…';
    margin-right: 4px;
    color: rgba(23, 32, 51, 0.52);
    font-weight: 900;
    line-height: 1;
}

/* 遮挡降级（D7 的同一套语义，扩到实物）：尖端在 JS 里改成空心描边，
   标签整条变淡。文字照常显示——隐藏会让标签在转动模型时忽隐忽现，
   比指偏更烦人。 */
.three-stage-part-label.is-anchor-occluded {
    opacity: 0.72;
}

.three-stage-part-tip.is-occluded {
    opacity: 0.8;
}

.three-stage-callout {
    position: absolute;
    left: 0;
    top: 0;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    filter: drop-shadow(0 10px 20px rgba(23, 32, 51, 0.12));
    white-space: nowrap;
}

.three-stage-callout[hidden] {
    display: none;
}

.three-stage-callout.is-left {
    transform: translate(calc(-100% - 14px), -50%);
}

.three-stage-callout.is-right {
    transform: translate(14px, -50%);
}

.three-stage-callout.is-detached {
    gap: 0;
    transform: none;
    filter: none;
}

/* E10：边栏 chip 限宽。左右两条 rail 的定位是左 railInset、右 containerWidth
   - railInset - nodeWidth，所以左右不重叠的充要条件是单条宽度 <= 50% - railInset。
   28 = 常规 railInset 18 + 10px 安全间隙；compactOverlay 的 railInset 是 10，间隙更大。
   百分比相对 .three-stage-callouts / .three-stage-part-labels（两者都是 inset: 0，
   等于舞台尺寸），所以这个上限随舞台缩放，宽屏上远大于任何 chip 的自然宽度——
   截断只在窄舞台真正需要时才发生。
   实测依据：卡片态舞台 340px 时，pergola 有 chip 宽 333px（占 98%），
   与对侧 chip 横向重叠 154px。 */
.three-stage-callout.is-detached {
    /* 2026-08-22 由 calc(50% - 28px) 收窄。原值让每侧占 42% 舞台宽，中间只剩 20px，
       而贴合式标签钉在锚点上、锚点在模型上、模型在中间——它们必然压到边栏 chip。
       实测 shed 3/6 层、pergola 3/6 层出现压字，最严重 3284px²。
       30% - 10px：340px 舞台每侧 92px、中间通道 120px；471px 舞台每侧 131px、通道 173px。
       与 common_three.js 的 railChipMaxWidth() 是同一组数，改一处必须改两处。 */
    max-width: calc(30% - 10px);
}

.three-stage-callout.is-detached .three-stage-callout-chip {
    max-width: 100%;
    min-width: 0;
}

/* 2 行后省略号，不是 1 行：140px 宽的 chip 单行只剩约 14 个字符
   （"Ground-contact pr…"），信息量接近于零；2 行约 28–30 字符，多数材料名能读全。
   高度上限约 46px，容量模型实测 chipHeight 会自然吸收。 */
.three-stage-callout.is-detached .three-stage-callout-chip-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-width: 0;
    /* 必须显式放开：nowrap 从 .three-stage-callout 继承下来，不清掉的话文字无法折行，
       -webkit-line-clamp: 2 会退化成 1 行裁切（实测 chip 高度恒为 30px 而非约 46px）。 */
    white-space: normal;
    line-height: 1.25;
    word-break: break-word;
}

.three-stage-callout-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 28px;
    padding: 0 10px;
    border-radius: var(--we-radius-pill);
    border: 1px solid rgba(23, 32, 51, 0.12);
    background: rgba(255, 251, 246, 0.92);
    color: #172033;
    font-size: 0.74rem;
    font-weight: 800;
    line-height: 1;
    backdrop-filter: blur(8px);
}

.three-stage-part-label.has-tooltip,
.three-stage-callout.has-tooltip,
.three-stage-callout.has-tooltip .three-stage-callout-chip {
    cursor: help;
    pointer-events: auto;
}

.three-stage-callout.is-detached .three-stage-callout-chip {
    min-height: 30px;
    padding: 0 11px;
    border-color: rgba(23, 32, 51, 0.14);
    background: rgba(255, 251, 246, 0.84);
    box-shadow: 0 10px 22px rgba(23, 32, 51, 0.12);
}

.three-stage-callout-chip::before {
    content: '';
    width: 8px;
    height: 8px;
    flex: 0 0 8px;
    border-radius: 999px;
    background: var(--callout-color, #172033);
    box-shadow: 0 0 0 1px rgba(23, 32, 51, 0.16);
}

.three-stage-callout-leader-path {
    fill: none;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.three-stage-callout-leader-dot {
    opacity: 0.92;
}

.three-stage-callout-leader-path.is-occluded,
.three-stage-callout-leader-dot.is-occluded {
    opacity: 0.75;
}

/* The renderer canvas is taken out of flow on purpose: as an in-flow child with
   `height: 100%` it fell back to `auto` whenever the stage height was not definite (the
   ⤢ expanded card, where preview_expand.js clears the inline pixel height common_three.js
   wrote), and a canvas with `auto` height is sized from its *backing store* aspect ratio.
   That let a 680x1020 buffer inflate an 858px-wide stage to 1287px tall and paint a
   stretched, stale bitmap into it. Absolute + inset:0 means the stage height is always
   decided by the layout (CSS state rules + the inline height) and the canvas follows it,
   never the other way round. Every other stage child is already absolutely positioned. */
.three-stage canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
}

.three-stage:active {
    cursor: grabbing;
}

@media (max-width: 640px) {
    .calculator-layout[data-slug="garage-conversion-adu-builder"] .preview-stage-control-row {
        justify-content: start;
    }

    .calculator-layout[data-slug="garage-conversion-adu-builder"] #preview-canvas {
        min-height: 480px;
    }

    .calculator-layout[data-slug="deck-builder"] #preview-canvas {
        min-height: 340px;
    }

    .preview-surface {
        padding: 10px;
        border-radius: 22px;
    }

    .preview-surface-header {
        flex-direction: column;
        gap: 8px;
    }

    .calculator-layout[data-slug="bathroom-remodel"] .preview-stage-controls {
        left: 10px;
        right: 10px;
    }

    .calculator-layout[data-slug="bathroom-remodel"] .preview-stage-control-row {
        gap: 6px;
        overflow-x: auto;
        padding-bottom: 4px;
    }

    .calculator-layout[data-slug="bathroom-remodel"] .preview-stage-control-row .preview-stage-chip {
        flex: 0 0 auto;
        padding: 0 8px;
        font-size: 0.7rem;
    }

    .three-stage-layer-controls {
        right: 10px;
        top: 10px;
        gap: 6px;
        padding: 8px 10px;
    }

    .three-stage-controls {
        right: 10px;
        bottom: 10px;
    }

    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-chip {
        min-height: 30px;
        padding: 0 10px;
        font-size: 0.72rem;
    }

    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-controls {
        right: 10px;
        gap: 6px;
        padding: 7px 8px;
        flex-wrap: nowrap;
        overflow-x: auto;
    }

    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-chip {
        flex: 0 0 auto;
        min-height: 28px;
        padding: 0 8px;
        font-size: 0.68rem;
    }

    .three-stage[data-scene-slug="deck-builder"] .three-stage-layer-controls,
    .three-stage[data-scene-slug="shed-builder"] .three-stage-layer-controls,
    .three-stage[data-scene-slug="garage-conversion-adu-builder"] .three-stage-layer-controls,
    .three-stage[data-scene-slug="fence-builder"] .three-stage-layer-controls,
    .three-stage[data-scene-slug="pergola-builder"] .three-stage-layer-controls,
    .three-stage[data-scene-slug="patio-builder"] .three-stage-layer-controls,
    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-controls {
        right: 10px;
    }

    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-controls {
        gap: 5px;
        padding: 7px 8px;
    }

    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-chip {
        min-height: 28px;
        padding: 0 8px;
        font-size: 0.69rem;
    }

    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-legend-toggle {
        min-height: 26px;
        padding: 0 9px;
        font-size: 0.68rem;
    }

    .three-stage-legend {
        left: 10px;
        right: 10px;
        bottom: 10px;
        max-width: none;
        padding: 9px 10px;
    }

    .three-stage-legend-list {
        gap: 7px 10px;
    }

    .three-stage-legend-item {
        font-size: 0.74rem;
    }

    .three-stage-callout {
        gap: 6px;
    }

    .three-stage-assembly-stack {
        top: auto;
        right: 10px;
        bottom: 62px;
        width: min(170px, calc(100% - 20px));
        padding: 9px;
    }

    .three-stage-assembly-stack-item {
        min-height: 28px;
        padding: 6px 7px;
        font-size: 0.66rem;
    }

    .three-stage-part-label {
        min-height: 22px;
        padding: 0 8px;
        font-size: 0.64rem;
    }

    .three-stage-callout.is-left {
        transform: translate(calc(-100% - 10px), -50%);
    }

    .three-stage-callout.is-right {
        transform: translate(10px, -50%);
    }

    .three-stage-callout.is-detached {
        transform: none;
    }

    .three-stage-callout-chip {
        min-height: 24px;
        padding: 0 8px;
        font-size: 0.68rem;
    }

    .three-stage-callout.is-detached .three-stage-callout-chip {
        min-height: 26px;
        padding: 0 9px;
    }

}

.three-stage canvas {
    display: block;
}

/* D3/§9: the material guide is a full-width tabbed card under the material list. */
.spec-reference {
    margin-top: 0;
    padding: 0;
    overflow: hidden;
    background: var(--we-card);
}

/* Redesign-Spec §9 puts the divider *under* the tab row, not between the description and
   the tabs. The tablist is a sibling of the header in the markup, so the rule travels
   instead of the element. */
/* .spec-reference-header {
    padding: 20px 22px 0;
} */

.spec-reference-panel-header,
.spec-reference-list,
.spec-reference-footnote {
    padding: 0 22px;
}

.spec-reference-panel {
    margin-top: 18px;
}

.spec-reference-footnote {
    padding-bottom: 20px;
}

.spec-reference-header .eyebrow {
    padding: 16px 22px;
    border-bottom: 1px solid var(--we-border-row);
    margin: 0;
    color: rgba(23, 32, 51, 0.58);
    font-size: 1.1rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-family: var(--we-font-sans);
}

.spec-reference-header h3 {
    /* margin: 0;
    font: 700 22px var(--we-font-serif); */
    font: 600 18px var(--we-font-serif);
    padding: 0 22px;
    margin: 6px 0;
}

.spec-reference-header p:last-child {
    margin: 0 0 18px 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--we-text-3);
    padding: 0 22px;
}

.spec-reference-tablist {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 16px;
    padding: 0 22px 16px;
    border-bottom: 1px solid var(--we-border-row);
}

.spec-reference-tab {
    /* Spec §9: `padding:8px 15px` alone lands at ~31px, which clears the 24px desktop
       floor but not the 44px touch floor - the mobile override below covers that. */
    min-height: 32px;
    padding: 8px 15px;
    border-radius: var(--we-radius-sm);
    border: 1px solid transparent;
    background: var(--we-band);
    /* Spec §9 writes #7a7365 here, but that is 4.06:1 on the #f2eee5 band - the spec's own
       a11y note only certifies --we-text-3 against *white*. --we-text-2 is 6.56:1 and is the
       colour the shared `thead th` rule already uses on the same band. */
    color: var(--we-text-2);
    font-size: 12.5px;
    font-weight: 600;
    box-shadow: none;
}

.spec-reference-tab:hover {
    background: var(--we-selected);
    color: var(--we-text);
}

.spec-reference-tab.is-active {
    background: var(--we-dark);
    border-color: var(--we-dark);
    color: var(--we-card);
}

/* Spec §14.6 pairs a short label column with the description on the same baseline;
   `align-items: end` left the title floating at the bottom of the note's 3rd line. */
.spec-reference-panel-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 14px;
    margin-bottom: 12px;
}

.spec-reference-panel-header h4 {
    margin: 0;
    font: 600 13.5px var(--we-font-sans);
    color: var(--we-text);
}

.spec-reference-panel-header p {
    margin: 0;
    max-width: 34rem;
    color: var(--we-text-2);
    font-size: 13px;
    line-height: 1.6;
}

.spec-reference-panel strong,
.spec-reference-footnote strong {
    color: var(--we-text);
    font-weight: 600;
}

/* Spec §9 runs the table flush to the card edges (the header band is full-bleed and the
   22px inset lives on the first/last cells), so the wrapper keeps only its job as the
   narrow-screen scroller. */
.spec-reference-table-scroll {
    border: 0;
    border-radius: 0;
    background: transparent;
    overflow-x: hidden;
}

.spec-reference-table {
    margin-top: 0;
    width: 100%;
    min-width: 0;
    table-layout: fixed;
}

.spec-reference-table th,
.spec-reference-table td {
    padding: 12px 10px;
    white-space: normal;
    overflow-wrap: anywhere;
}

.spec-reference-table th:first-child,
.spec-reference-table td:first-child {
    padding-left: 22px;
}

.spec-reference-table th:last-child,
.spec-reference-table td:last-child {
    padding-right: 22px;
}

/* The shared `thead th` rule is already the spec's band + 11px uppercase label (and its
   --we-text-2 clears 4.5:1 on that band, which the spec's #8a827a would not), so the guide
   only keeps the wrapping helpers its long column names need. */
.spec-reference-table th {
    font-weight: 600;
    letter-spacing: 0.04em;
    line-height: 1.4;
    word-break: normal;
    hyphens: auto;
}

.spec-reference-table td {
    font-size: 13px;
    line-height: 1.6;
    color: var(--we-text-2);
}

.spec-reference-list {
    margin: 0;
    list-style: none;
    display: grid;
    gap: 0;
}

/* Spec §14.6: the list variant is the same row rhythm as the table - separator rules, no
   nested card chrome. */
.spec-reference-list li {
    padding: 15px 0;
    border-top: 1px solid var(--we-border-row);
    font-size: 13px;
    line-height: 1.6;
    color: var(--we-text-2);
}

.spec-reference-list li:first-child {
    border-top: 0;
}

.spec-reference-list strong {
    color: var(--we-text);
    font-weight: 600;
}

.spec-reference-footnote {
    margin: 14px 0 0;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
}

.metric-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 16px;
    margin-top: 18px;
}

.metric-grid.wide {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* display:* on an element that is toggled through the hidden attribute beats the UA
   [hidden] rule, so every such component needs its own companion rule. */
.metric-grid[hidden] {
    display: none;
}

.project-more-facts {
    margin-top: 14px;
    padding: 16px 18px;
    border-radius: 20px;
    border: 1px solid rgba(216, 207, 192, 0.84);
    background: rgba(255, 253, 248, 0.9);
}

.project-more-facts[hidden] {
    display: none;
}

.project-more-facts-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    cursor: pointer;
    color: #172033;
    font-size: 0.92rem;
    font-weight: 700;
    list-style: none;
}

.project-more-facts-summary::-webkit-details-marker {
    display: none;
}

.project-more-facts-summary strong {
    display: inline-flex;
    min-width: 28px;
    justify-content: center;
    padding: 4px 8px;
    border-radius: var(--we-radius-pill);
    background: rgba(23, 32, 51, 0.08);
    font-size: 0.78rem;
}

.project-more-facts-grid {
    margin-top: 14px;
}

/* ---- "Plan details & review" accordion (SPEC D4) -------------------------
   One card, one open row at a time. Each row keeps its original element - the
   More project facts <details> and every <article data-project-support-group>
   are only restyled, so common.js keeps finding them through the same
   [data-result-notes] scope and closest('[data-project-support-group]') chain. */
.plan-details-panel {
    padding: 0;
    overflow: hidden;
}

.plan-details-panel-title {
    margin: 0;
    padding: 11px 16px;
    border-bottom: 1px solid var(--we-border-row);
    color: rgba(23, 32, 51, 0.58);
    font-size: 1rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.plan-details-rows > .result-notes {
    display: contents;
}

.plan-details-rows > .result-notes[hidden] {
    display: none;
}

.plan-details-panel .plan-detail-row {
    margin: 0;
    padding: 0 0 2px;
    border: 0;
    border-bottom: 1px solid var(--we-border-row);
    border-radius: 0;
    background: none;
    box-shadow: none;
    display: block;
}

.plan-details-panel .plan-detail-row:last-child,
.plan-details-rows > .result-notes > .plan-detail-row:last-child {
    border-bottom: 0;
}

.plan-details-panel .plan-detail-row-head {
    position: relative;
    display: flex;
    /* explicit: the mobile .project-support-header override stacks the old card header */
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 0;
    /* the whole header is the tap target, so it holds the 44px minimum */
    min-height: 44px;
    padding: 13px 16px;
    cursor: pointer;
    list-style: none;
}

.plan-details-panel .plan-detail-row-label {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--we-text);
}

.plan-details-panel .plan-detail-row-label h3 {
    margin: 0;
    font-family: var(--we-font-sans);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.35;
}

.plan-details-panel .plan-detail-row-label::before {
    content: "";
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--we-dot-neutral);
}

.plan-details-panel [data-project-support-group="assumptions"] .plan-detail-row-label::before {
    background: var(--we-dot-assume);
}

.plan-details-panel [data-project-support-group="scope_exclusions"] .plan-detail-row-label::before {
    background: var(--we-dot-excl);
}

/* Amber marks the rows that carry an outstanding action; the education cards also
   carry data-support-focus but are background reading, so they stay neutral. */
.plan-details-panel [data-project-support-group="contractor_handoff"] .plan-detail-row-label::before,
.plan-details-panel [data-support-focus="shed-slab-scope"] .plan-detail-row-label::before {
    background: var(--we-dot-warn);
}

.plan-details-panel .plan-detail-count {
    flex: none;
    padding: 1px 8px;
    border-radius: var(--we-radius-pill);
    background: var(--we-band);
    color: var(--we-text-4);
    font-size: 0.68rem;
    font-weight: 600;
    min-width: 0;
}

.plan-details-panel .plan-detail-count[hidden] {
    display: none;
}

/* The toggle keeps its JS-written label ("View details" / "Hide details") as its
   accessible name - font-size:0 hides the text while the chevron ::after carries the
   open/closed cue. The whole row header is the pointer target (see
   bindPlanDetailsAccordion in common.js), so the row stays a 44px tap area. */
.plan-details-panel .button.project-support-toggle,
.plan-details-panel .project-more-facts-summary::after {
    color: var(--we-accent-soft);
}

.plan-details-panel .button.project-support-toggle {
    flex: none;
    /* explicit: the mobile block stretches .button.secondary to width:100% */
    width: auto;
    min-width: 24px;
    min-height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--we-radius-sm);
    background: none;
    box-shadow: none;
    font-size: 0;
    line-height: 1;
    letter-spacing: normal;
}

.plan-details-panel .button.project-support-toggle::after {
    content: "\25B8";
    font-size: 14px;
}

.plan-details-panel .button.project-support-toggle[aria-expanded="true"]::after {
    content: "\25BE";
}

.plan-details-panel .button.project-support-toggle:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: 2px;
}

.plan-details-panel .project-more-facts-summary::after {
    content: "\25B8";
    font-size: 14px;
}

.plan-details-panel .project-more-facts[open] > .project-more-facts-summary::after {
    content: "\25BE";
}

.plan-details-panel .project-support-summary,
.plan-details-panel .project-support-reminder,
.plan-details-panel .project-support-details,
.plan-details-panel .project-support-education-cards,
.plan-details-panel .project-more-facts-grid {
    margin: 0;
    padding-left: 32px;
    padding-right: 16px;
}

/* `.project-support-reminder` wears two hats: a top-level <p> in the panel, and the first
   <li> of `.project-support-details` when the group renders its reminder as a list item.
   The 32px above is meant for the top-level role so the <p> lines up with the <ul> beside
   it - on the <li> it stacks on top of the list's own 32px and pushes only the first bullet's
   text 32px further right than its siblings. Scoped reset, matching how the education-cards
   block already re-scopes the same class below. */
.plan-details-panel .project-support-details > .project-support-reminder {
    padding-left: 0;
    padding-right: 0;
}

.plan-details-panel .project-support-details,
.plan-details-panel .project-support-education-cards,
.plan-details-panel .project-more-facts-grid {
    padding-bottom: 14px;
}

.plan-details-panel .project-support-summary,
.plan-details-panel .project-support-reminder {
    padding-bottom: 8px;
    color: var(--we-text-3);
    font-size: 0.8rem;
    line-height: 1.65;
}

.plan-details-panel .project-support-summary:empty,
.plan-details-panel .project-support-reminder:empty {
    display: none;
}

/* The assumptions reminder is literally "<label> - <count>"; the row title and the
   count badge already render both halves, so showing the raw string repeats the same
   fact. The node stays in the DOM because common.js keeps writing to it. */
.plan-details-panel [data-project-support-group="assumptions"] > .project-support-reminder {
    display: none;
}

/* Rows must read as one stack - no per-group card tinting inside the accordion. */
.plan-details-panel .result-note-card[data-project-support-group] {
    background: none;
}

/* Collapsed rows show only the header: the lead-in sentence belongs to the
   expanded body, and common.js only toggles [data-support-details]. */
.plan-details-panel .plan-detail-row:has(> .project-support-details[hidden]) > .project-support-summary,
.plan-details-panel .plan-detail-row:has(> .project-support-details[hidden]) > .project-support-reminder,
.plan-details-panel .project-support-education:has(> .project-support-education-cards[hidden]) > .project-support-education-summary {
    display: none;
}

.plan-details-panel .project-support-details {
    display: grid;
    gap: 8px;
    list-style: disc;
    color: var(--we-text-3);
    font-size: 0.8rem;
    line-height: 1.65;
}

.plan-details-panel .project-support-details > li {
    margin-left: 14px;
}

/* The canonical flat scope list is presented under the two trust-contract labels. */
.plan-details-panel .project-support-details > li.project-support-scope-bucket {
    margin-left: 0;
    list-style: none;
}

.project-support-scope-bucket {
    scroll-margin-top: calc(var(--site-header-height, 72px) + 16px);
    border-top: 1px solid var(--we-border-row);
}

.project-support-scope-bucket:first-of-type {
    border-top: 0;
}

.project-support-scope-bucket-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 0 4px;
}

.project-support-scope-bucket-label {
    margin: 0;
    color: var(--we-text-2);
    font-size: 0.82rem;
    font-weight: 650;
}

.project-support-scope-bucket-items {
    display: grid;
    gap: 8px;
    margin: 2px 0 10px;
    padding: 0 0 0 18px;
    list-style: disc;
}

.project-support-scope-bucket-items > li {
    margin-left: 14px;
}

.plan-details-panel .project-more-facts-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    margin-top: 0;
}

.plan-details-panel .project-more-facts-grid .metric-card {
    padding: 9px 12px;
    border-radius: var(--we-radius-sm);
    background: var(--we-card-sub);
    border-color: var(--we-border-row);
    box-shadow: none;
}

.plan-details-panel .project-more-facts-grid .metric-card span {
    margin-bottom: 2px;
    font-size: 0.66rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.plan-details-panel .project-more-facts-grid .metric-card strong {
    font-family: var(--we-font-mono);
    font-size: 0.82rem;
}

/* Education rows nest one level deeper - keep their own card chrome. */
.plan-details-panel .project-support-education-cards > .plan-detail-row {
    padding: 0;
    border-bottom: 1px solid var(--we-border-row);
}

.plan-details-panel .project-support-education-cards > .plan-detail-row:last-child {
    border-bottom: 0;
}

.plan-details-panel .project-support-education-cards .plan-detail-row-head {
    padding-left: 0;
    padding-right: 0;
}

/* These three education topics are inline content, not nested disclosures. */
.project-education-shell .project-support-education-cards .plan-detail-row-head {
    cursor: default;
}

.plan-details-panel .project-support-education-cards .project-support-summary,
.plan-details-panel .project-support-education-cards .project-support-reminder,
.plan-details-panel .project-support-education-cards .project-support-details {
    padding-left: 16px;
    padding-right: 0;
}

.result-notes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 14px;
    margin-top: 18px;
}

.result-notes[hidden] {
    display: none;
}

.project-support-card {
    display: grid;
    gap: 12px;
}

.project-support-card[data-support-focus="shed-slab-scope"] {
    border-left: 4px solid #b45309;
    background: #fff7ed;
    padding-left: 16px;
}

.project-support-header {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: 12px;
}

.project-support-header .project-support-summary,
.project-support-header .project-support-reminder {
    margin: 0;
    color: rgba(23, 32, 51, 0.72);
    font-size: 0.88rem;
    line-height: 1.5;
}

.project-support-details {
    display: grid;
    gap: 8px;
    margin: 0;
    padding-left: 18px;
}

.project-support-details[hidden] {
    display: none;
}

.project-support-toggle {
    white-space: nowrap;
}

.project-support-toggle[hidden] {
    display: none !important;
}

.project-support-education {
    display: grid;
    gap: 12px;
}

.project-support-education-cards {
    display: grid;
    gap: 12px;
    padding-left: 14px;
    border-left: 2px solid rgba(15, 42, 67, 0.14);
}

.project-support-education[hidden],
.project-support-education-cards[hidden] {
    display: none;
}

.result-note-card[data-project-support-group="technical_basis"] {
    background: rgba(250, 247, 241, 0.92);
}

.project-boundary-summary[hidden],
.result-note-card[hidden] {
    display: none !important;
}

.material-theme-summary {
    margin: 14px 0 0;
    padding: 16px 18px;
    border-radius: 20px;
    border: 1px solid rgba(70, 98, 76, 0.16);
    background: linear-gradient(135deg, rgba(241, 247, 239, 0.98), rgba(230, 239, 228, 0.92));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
    color: #172033;
}

.material-theme-summary[data-material-theme-tone="warning"] {
    border-color: rgba(180, 101, 43, 0.22);
    background: linear-gradient(135deg, rgba(255, 247, 236, 0.98), rgba(245, 231, 210, 0.92));
}

.material-theme-summary[data-material-theme-tone="error"] {
    border-color: rgba(143, 51, 32, 0.24);
    background: linear-gradient(135deg, rgba(255, 242, 238, 0.98), rgba(246, 224, 218, 0.92));
}

.material-theme-summary[data-material-theme-tone="accent"] {
    border-color: rgba(51, 80, 119, 0.2);
    background: linear-gradient(135deg, rgba(239, 245, 253, 0.98), rgba(227, 237, 249, 0.92));
}

.material-theme-eyebrow {
    margin: 0;
    color: rgba(70, 98, 76, 0.9);
    font-size: 0.74rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.material-theme-title {
    margin: 6px 0 0;
    font-size: 1rem;
    line-height: 1.35;
}

.material-theme-copy {
    margin: 8px 0 0;
    color: rgba(23, 32, 51, 0.82);
    font-size: 0.92rem;
    line-height: 1.5;
}

.result-note-card {
    padding: 18px;
    border: 1px solid rgba(216, 207, 192, 0.84);
    border-radius: 20px;
    background: rgba(255, 251, 246, 0.9);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}

.result-note-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.result-note-header h3 {
    margin: 0;
    font-size: 0.94rem;
}

.result-note-header span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    min-height: 30px;
    padding: 0 10px;
    border-radius: var(--we-radius-pill);
    background: rgba(223, 232, 215, 0.78);
    color: #172033;
    font-size: 0.78rem;
    font-weight: 800;
}

.result-note-list {
    margin: 12px 0 0;
    padding-left: 18px;
    display: grid;
    gap: 8px;
    color: var(--ink);
}

.result-note-list li {
    line-height: 1.5;
}

.result-note-empty {
    margin: 12px 0 0;
    color: var(--muted);
    font-size: 0.9rem;
}

.scope-exclusion-groups {
    display: grid;
    gap: 10px;
    margin-top: 12px;
}

.scope-exclusion-group {
    padding: 12px 14px;
    border-radius: 16px;
    border: 1px solid rgba(216, 207, 192, 0.72);
    background: rgba(255, 253, 249, 0.92);
}

.scope-exclusion-group[data-scope-exclusion-tone="warning"] {
    border-color: rgba(180, 101, 43, 0.28);
    background: rgba(255, 247, 236, 0.96);
}

.scope-exclusion-group[data-scope-exclusion-tone="error"] {
    border-color: rgba(143, 51, 32, 0.28);
    background: rgba(255, 241, 237, 0.96);
}

.scope-exclusion-group h4 {
    margin: 0;
    font-size: 0.88rem;
}

.scope-exclusion-group .result-note-list {
    margin-top: 10px;
}

/* ---------------------------------------------------------------------------
   Lead panel = the page-foot repeat of the Quote CTA (user ruling 2026-07-28:
   both cards stay, only the styling is unified). It therefore borrows the whole
   Redesign-Spec §8 card: #fbf2ea fill, #ecccb4 hairline, 13px radius, 18/20
   padding, Spectral 17 title, 12.5px note, and the same button pair.

   The builder and the calculator carried two different looks before this
   (translucent warm rail vs plain white/grey card); one rule set now drives
   both, since `.lead-panel` is the only class they share.

   `position: sticky` is gone on purpose: the panel is the LAST child of its
   grid container on both tools, so its sticky containing block has zero slack
   below it. Measured on deck-builder at 1440x1000, the panel's page-Y is 5589
   at every scroll offset with sticky ON and with sticky forced OFF, and
   document height is 6305 either way - the declaration never moved a pixel.
   --------------------------------------------------------------------------- */
.lead-panel {
    align-self: start;
    background: var(--we-quote-bg);
    border: 1px solid var(--we-quote-border);
    border-radius: 13px;
    padding: 18px 20px;
    box-shadow: none;
}

.lead-panel > h2 {
    margin: 0;
    font: 700 17px var(--we-font-serif);
    color: var(--we-text);
}

.lead-panel > p:not(.lead-plan-summary) {
    margin: 3px 0 0;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--we-text-3);
}

.lead-context {
    display: flex;
    flex-wrap: wrap;
    gap: 9px;
    margin: 14px 0 16px;
}

.lead-plan-summary {
    margin: 12px 0 0;
    padding: 12px 14px;
    border: 1px solid var(--we-quote-outline);
    border-radius: var(--we-radius-sm);
    background: var(--we-card);
    color: var(--we-text-2);
    font-size: 13px;
    line-height: 1.6;
}

/* Same white-on-warm pill language as the secondary buttons beside them. These are
   non-interactive <span>s, so they are outside the 44px touch contract. */
.lead-context-chip {
    display: inline-flex;
    align-items: center;
    min-height: 0;
    padding: 7px 13px;
    border-radius: var(--we-radius-pill);
    border: 1px solid var(--we-quote-outline);
    background: var(--we-card);
    color: var(--we-text-2);
    font-size: 12.5px;
    font-weight: 600;
}

.lead-context-chip[hidden] {
    display: none;
}

.lead-context-chip[data-lead-context-chip="posture"] {
    border-color: var(--we-quote-border);
    background: var(--we-selected);
    color: var(--we-accent-deep);
}

.lead-panel .button {
    min-height: 0;
    padding: 11px 15px;
    border-radius: var(--we-radius-sm);
    font-size: 13px;
    white-space: nowrap;
}

.lead-panel .button.primary {
    padding: 11px 18px;
    font-weight: 600;
}

.lead-panel .button.secondary {
    border-color: var(--we-quote-outline);
    color: var(--we-text-2);
}

/* The calculator's lead panel is a full capture form rather than a single CTA; the
   controls follow the same 13px / 9px-radius chrome as the buttons above. */
.lead-panel label {
    font-size: 13px;
    color: var(--we-text-2);
}

/* The consent copy follows the panel down to 13px, which shortens the line box the inline
   Privacy Policy anchor inflates with `padding-block` (see the touch-target block at the
   end of this sheet). 13px of reclaim left it at 41px; 15px puts it back over 44. */
.lead-panel .consent-line span a {
    padding-block: 15px;
}

/* `.lead-panel label` above sets `gap: 6px` at higher specificity than `.consent-line`'s
   own `gap: 12px` (0,1,1 beats 0,1,0), which was quietly squeezing the checkbox/copy gap
   down to 6px inside the calculator's capture form. Restore the intended 12px gap here at
   `.lead-panel .consent-line` (0,2,0), which outranks both. */
.lead-panel .consent-line {
    gap: 12px;
}

.lead-panel input,
.lead-panel select {
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    border-radius: var(--we-radius-sm);
    font-size: 13px;
}

.lead-panel input:focus-visible,
.lead-panel select:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: 1px;
}

/* The capture form's own fields/rows/button share one consistent vertical rhythm. Scoped to
   `form` (not `.button.primary` generally) so it doesn't reach the project page's lead-panel
   quote-CTA anchor, which sits in the same `.lead-panel` outside any `<form>`. */
.lead-panel form {
    display: grid;
    gap: 14px;
    margin-top: 16px;
}

.lead-panel form .button {
    width: 100%;
}

@media (max-width: 640px) {
    .lead-panel .button {
        min-height: 44px;
    }
}

.contractor-report-output {
    margin-top: 18px;
    padding: 16px 18px;
    border: 1px solid rgba(216, 207, 192, 0.84);
    border-radius: 20px;
    background: rgba(255, 251, 246, 0.9);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
    color: #172033;
    font-family: inherit;
    font-size: 0.88rem;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
}

.project-mobile-summary {
    display: none;
    transition: opacity 160ms ease, transform 160ms ease, visibility 160ms ease;
}

.tool-stage-nav {
    display: none;
}

@media (max-width: 960px) {
    /* 吸顶偏移必须等于站点头的**真实**高度，而这个高度不是常量:.nav-row 是
       flex-wrap 的单行,换行点随语言与 UA 变化。实测 129px 的上界跨 348-372px:
       EN 真机/桌面 UA 为 348/365px,ES 为 356/372px,两种语言并不相同。
       现在由 site.js 的 trackHeaderHeight() 实测写入 --site-header-height,两边不再
       各自猜数;移动端菜单展开把 header 撑高时也跟着走。
       --site-header-effective-height 是当前 sticky 偏移:顶栏下滑隐藏时为 0,
       否则跟随物理高度。
       兜底 73px 是 JS 跑起来之前的值,对 365px 以上都成立。 */
    .site-header {
        transition: transform 160ms ease;
        will-change: transform;
    }

    .site-header.is-scroll-hidden {
        transform: translateY(-100%);
    }

    .tool-stage-nav {
        position: sticky;
        top: var(--site-header-effective-height, var(--site-header-height, 73px));
        z-index: 9;
        display: block;
        margin-top: 12px;
        border-block: 1px solid var(--we-border);
        background: rgba(251, 249, 244, 0.96);
        backdrop-filter: blur(8px);
    }

    .tool-stage-nav-links {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 4px;
        padding-block: 6px;
    }

    .tool-stage-nav a {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
        padding: 0 6px;
        border-radius: var(--we-radius-sm);
        color: var(--we-text-2);
        font-size: 0.78rem;
        font-weight: 700;
        text-align: center;
    }

    .tool-stage-nav a:focus-visible,
    .tool-stage-nav a:hover {
        background: var(--we-band);
        color: var(--we-accent);
    }
}

@media (prefers-reduced-motion: reduce) {
    .site-header {
        transition: none;
    }
}

.project-mobile-summary-copy {
    display: grid;
    gap: 4px;
}

.project-mobile-summary-copy strong {
    font-size: 0.95rem;
    line-height: 1.35;
}

.project-mobile-summary-secondary {
    font-size: 0.78rem;
    color: var(--muted);
    line-height: 1.4;
}

.project-mobile-adu-decisions {
    display: grid;
    gap: 6px;
    margin: 4px 0;
}

.project-mobile-adu-decision-chip {
    display: grid;
    gap: 2px;
    padding: 7px 9px;
    border: 1px solid rgba(216, 207, 192, 0.9);
    border-radius: 8px;
    background: rgba(255, 253, 248, 0.88);
}

.project-mobile-adu-decision-chip > span {
    color: var(--muted);
    font-size: 0.66rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.project-mobile-adu-decision-chip > strong {
    color: var(--ink);
    font-size: 0.76rem;
    line-height: 1.35;
}

.project-mobile-adu-decision-chip[data-decision-tone="warning"] {
    border-color: rgba(181, 109, 23, 0.36);
    background: rgba(181, 109, 23, 0.09);
}

.project-mobile-adu-decision-chip[data-decision-tone="danger"],
.project-mobile-adu-decision-chip[data-decision-tone="error"] {
    border-color: rgba(151, 41, 37, 0.34);
    background: rgba(151, 41, 37, 0.08);
}

.project-mobile-summary-label {
    font-size: 0.7rem;
    color: var(--muted);
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.project-mobile-summary-status {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    min-height: 28px;
    padding: 0 10px;
    border-radius: var(--we-radius-pill);
    background: rgba(23, 32, 51, 0.08);
    color: #172033;
    font-size: 0.74rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.project-mobile-summary-status[hidden] {
    display: none;
}

.project-mobile-summary-reviews {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    min-height: 28px;
    padding: 0 10px;
    border-radius: var(--we-radius-pill);
    background: rgba(181, 109, 23, 0.12);
    color: #8a4f12;
    font-size: 0.74rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.project-mobile-summary-reviews[hidden] {
    display: none;
}

.project-mobile-summary-reviews[role="button"] {
    cursor: pointer;
}

.project-mobile-summary-reviews[role="button"]:focus-visible {
    outline: 2px solid rgba(140, 63, 29, 0.42);
    outline-offset: 2px;
}

.project-mobile-summary-status[data-planning-tone="warning"] {
    background: rgba(181, 109, 23, 0.12);
    color: #8a4f12;
}

.project-mobile-summary-status[data-planning-tone="error"] {
    background: rgba(151, 41, 37, 0.12);
    color: #972925;
}

.project-mobile-summary-status[data-planning-tone="success"] {
    background: rgba(52, 104, 73, 0.12);
    color: #346849;
}

.project-mobile-summary-total {
    font-size: 0.88rem;
    color: var(--accent-strong);
    font-weight: 700;
}

.project-mobile-summary-meta {
    font-size: 0.78rem;
    color: var(--muted);
}

.metric-card {
    padding: 18px;
}

.metric-card span {
    display: block;
    font-size: 0.84rem;
    color: var(--muted);
    margin-bottom: 6px;
}

.metric-card strong {
    font-size: 1.2rem;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 16px;
}

tr.is-layer-clickable {
    cursor: pointer;
    transition: opacity 140ms ease, background-color 140ms ease;
}

tr.is-layer-focus {
    background: rgba(192, 90, 43, 0.08);
}

tr.is-layer-muted {
    opacity: 0.48;
}

th,
td {
    text-align: left;
    padding: 14px 10px;
    border-bottom: 1px solid var(--we-border-row);
    vertical-align: top;
}

thead th {
    /* background: var(--we-band); */
    background: var(--we-quote-outline);
    color: var(--we-text-2);
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.table-scroll {
    max-width: 100%;
    overflow-x: auto;
}

.link-cell {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

.disclosure,
.microcopy {
    margin:0 0 8px;
    font-size: 0.82rem;
    color: var(--muted);
}

.total-row {
    margin-top: 20px;
    font-size: 1.1rem;
}

.total-row-meta {
    margin-top: 8px;
}

.consent-line {
    display: grid;
    grid-template-columns: 20px 1fr;
    align-items: start;
    gap: 12px;
}

.consent-line input {
    min-height: 20px;
    margin-top: 4px;
}

.prose-page,
.quote-form {
    max-width: 840px;
}

.quote-form {
    display: grid;
    gap: 12px;
    margin-top: 24px;
}

.pill-list {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    padding: 0;
    list-style: none;
}

.pill-list li {
    padding: 10px 14px;
    border-radius: var(--we-radius-pill);
    background: var(--sage);
}

/* Spec §3.2: top border + --we-card-sub band, three columns at 1.4fr 1fr 1fr.
   Horizontal insets stay with .site-shell rather than the mock's inner padding:40px,
   so the footer keeps lining up with the page above it at every breakpoint. */
.site-footer {
    margin-top: 56px;
    padding: 44px 0;
    background: var(--we-card-sub);
    border-top: 1px solid var(--we-border);
}

.site-footer p,
.site-footer a {
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.6;
}

.footer-grid {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr;
    gap: 32px;
}

/* The brand column keeps the serif wordmark; the other two are sans column headings.
   All three are <h2> in the markup, so they are separated structurally. */
.footer-grid > div:first-child > h2 {
    margin: 0 0 10px;
    font: 700 17px/1.3 var(--we-font-serif);
}

.footer-grid > div:first-child > p {
    max-width: 300px;
}

.footer-grid > div + div > h2 {
    margin: 0 0 10px;
    font: 600 14px/1.4 var(--we-font-sans);
}

.site-footer ul {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 7px;
}

/* The homepage body runs on the 1160 .home-shell while the footer uses the 1240
   .site-shell, so the footer overhung the content it sits under by 40px a side.
   Tool pages are already 1240 and must stay there, hence scoping by page. */
body:has(.home-shell) .site-footer .footer-grid {
    width: min(1160px, calc(100% - 80px));
}

.notice.success {
    padding: 14px 18px;
    border-radius: 18px;
    background: #ebf7ea;
    border: 1px solid #9fcb9c;
    color: #215d2f;
}

.notice.error {
    padding: 14px 18px;
    border-radius: 18px;
    background: #fff2ee;
    border: 1px solid #d4a08f;
    color: #7a2f1f;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

@media (max-width: 960px) {
    .preview-header {
        gap: 12px;
    }

    .preview-header-fallback {
        flex-wrap: wrap;
    }

    .preview-header-meta {
        flex: 1 0 100%;
        justify-content: start;
        width: 100%;
    }

    .project-preview-status-pill {
        max-width: 100%;
    }

    .project-support-header {
        flex-direction: column;
        align-items: stretch;
    }
}

@media (max-width: 960px) {
    .hero-grid,
    .calculator-layout,
    .results-layout,
    .dashboard-grid,
    .footer-grid,
    .card-grid,
    .compact,
    .result-notes,
    .metric-grid,
    .metric-grid.wide {
        grid-template-columns: 1fr;
    }

    .calculator-layout[data-tool="project"] {
        grid-template-columns: 1fr;
        gap: 24px;
    }

    /* Kept as its own block (not merged into the selector list above) so the pinned
       `[data-tool="project"] { grid-template-columns: 1fr }` assertion still matches. */
    .calculator-layout[data-tool="calculator"] {
        grid-template-columns: 1fr;
        gap: 24px;
    }


    .page-head-total {
        flex: 1 0 100%;
        width: 100%;
        min-width: 0;
        text-align: left;
    }

    .page-head-total-summary {
        margin: 6px 0 0;
        text-align: left;
    }

    .calculator-layout[data-slug="bathroom-remodel"],
    .calculator-layout[data-slug="garage-conversion-adu-builder"],
    .results-layout[data-slug="bathroom-remodel"] {
        grid-template-columns: 1fr;
        gap: 24px;
    }

    .calculator-layout[data-slug="bathroom-remodel"] .metric-grid {
        grid-template-columns: 1fr;
    }

    .calculator-layout[data-slug="bathroom-remodel"] .preview-stage-control-row .preview-stage-chip {
        min-height: 44px;
        min-width: 44px;
    }

    /* 移动端 header 的排列顺序:品牌 / Sign in / Get Quotes ……… Menu
       展开的导航另起一行,排在所有东西之后。

       **为什么原来 Menu 会在按下时弹到最右**:.nav-toggle、.account-control、
       .site-header .nav-cta 三个兄弟元素原本都带 margin-left: auto。在会换行的
       flex 容器里,auto 外边距吃掉的是**它所在那一行**的剩余空间,所以谁在哪一行
       会随内容变化——关闭时第 1 行是"品牌 / Menu / Sign in",两个 auto 平分空隙,
       Menu 停在中间;打开时 .site-nav 变成 width:100% 独占一行、把 Sign in 和
       Get Quotes 挤到第 3 行,第 1 行只剩"品牌 / Menu",Menu 就独吞了全部空隙
       弹到最右。

       改法:**整行只保留一个 auto 外边距**,挂在右侧集群的第一项 .site-header
       .nav-cta(Get Quotes)上——它把空隙全部顶到品牌名后面,于是 Get Quotes 和
       紧随其后的 Menu 贴在一起、整体靠右。挂在 .nav-toggle 上的话空隙会落在
       Get Quotes 与 Menu 之间,两个按钮被撑开。
       再用 order 把展开的导航排到最后,这样它换行时不会把上面的东西挤走。

       用 order 而不是挪 markup 是因为桌面端需要 .site-nav 紧跟品牌名。DOM 顺序
       保持"toggle 在它所控制的 site-nav 之前"——这是无障碍上正确的关系,
       aria-controls 也已经指向它。 */
    /* 不要在这里加 margin-left: auto——空隙会落在 Get Quotes 和 Menu 之间,
       把两个按钮撑开。auto 外边距只放在右侧集群的第一项(.site-header .nav-cta)。 */
    .nav-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        order: 3;
    }

    .site-nav {
        display: none;
        width: 100%;
        padding: 12px 0 6px;
        flex-direction: column;
        /* 展开后右对齐,和贴在右侧的 Menu 按钮呼应 */
        align-items: flex-end;
        order: 4;
        margin-left: 0;
        margin-right: 0;
    }

    /* 右侧集群的第一项。整行**唯一**的 auto 外边距放在这里:它把剩余空间全部
       顶到品牌名后面,Get Quotes 与紧随其后的 Menu 就贴在一起、整体靠右。 */
    .site-header .nav-cta {
        min-height: 44px;
        width: auto;
        order: 2;
        margin-left: auto;
    }

    /* Sign in 收进 Menu:移动端默认不显示,跟随导航一起展开。
       这样首行只剩 品牌 / Get Quotes …… Menu(115+105+76+24=320 ≤ 345 可用宽度),
       header 从两行压到一行。

       **为什么只收 Sign in、不收 Get Quotes**:两种做法省下的高度完全一样
       (都是压到一行),但 Get Quotes 指向 /quotes,是 leads 漏斗的入口,而首页、
       博客、各列表页的正文里**没有**任何备份报价入口(只有 project/calculator
       页有)。收掉它等于在移动端把这些页面的转化路径整个藏起来。
       Sign in 是"魔法链接跨设备保存方案"的工具型功能,藏进菜单成本低得多。

       .account-control 在 DOM 里正好紧跟 .site-nav,所以用兄弟选择器就够,
       不需要改标记、更不需要复制一份。
       注意:移动端导航本来就依赖 JS(无 JS 时 .site-nav 也展不开),
       Sign in 进入同一套机制没有引入新的降级层级。

       **隐藏规则必须写成 .site-header .account-control,不能只写 .account-control。**
       媒体查询**不增加优先级**:本块在 6555 行,而无条件的基础规则
       `.account-control { display: flex }` 在 6992 行——同为 (0,1,0) 时后写的赢,
       只写 .account-control 会被它整个盖掉,Sign in 根本不会被隐藏。
       (旁边的 .site-header .nav-cta 本来就是带作用域写的,同一个道理。)
       展开用的兄弟选择器是 3 个类 (0,3,0),压得住这条 (0,2,0),顺序无关。 */
    .site-header .account-control {
        order: 5;
        display: none;
    }

    .site-nav.is-open ~ .account-control {
        display: flex;
        width: 100%;
    }

    .account-signin-summary {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    .site-nav.is-open {
        display: flex;
    }

    .nav-row {
        flex-wrap: wrap;
        padding: 12px 0;
    }

    .hero,
    .page-head {
        padding-top: 36px;
    }

    .panel-actions,
    .hero-actions,
    .page-links {
        flex-direction: column;
    }

    .button.primary,
    .button.secondary,
    .nav-cta,
    .panel-actions .button {
        width: 100%;
        justify-content: center;
        text-align: center;
    }

    /* The estimate / reset pair keeps its single row at every width; stacking them cost
       three rows of vertical space directly above the fold on phones. */
    .panel-actions.project-input-submit {
        flex-direction: row;
        flex-wrap: wrap;
    }

    .panel-actions.project-input-submit .project-input-actions > .button {
        width: auto;
    }

    input,
    select,
    textarea {
        width: 100%;
    }

    table {
        min-width: 640px;
    }

    .spec-reference-header,
    .spec-reference-tablist,
    .spec-reference-panel-header,
    .spec-reference-list,
    .spec-reference-footnote {
        padding-left: 16px;
        padding-right: 16px;
    }

    .spec-reference-table th:first-child,
    .spec-reference-table td:first-child {
        padding-left: 16px;
    }

    .spec-reference-table th:last-child,
    .spec-reference-table td:last-child {
        padding-right: 16px;
    }

    .spec-reference-panel-header {
        display: grid;
        gap: 8px;
        align-items: start;
    }

    .spec-reference-table-scroll {
        overflow-x: auto;
    }

    .spec-reference-table {
        min-width: 680px;
        table-layout: auto;
    }

    .project-stage-summary {
        grid-template-columns: 1fr;
    }

    .project-side-column > .metric-grid,
    .project-side-column .trust-contract-grid {
        grid-template-columns: 1fr;
    }

    .preview-stage-control-row {
        flex-wrap: nowrap;
        width: 100%;
        overflow-x: auto;
        justify-content: start;
        padding-bottom: 4px;
    }

    .preview-stage-chip {
        flex: 0 0 auto;
        min-height: 44px;
        min-width: 44px;
    }

    .project-mobile-summary {
        position: fixed;
        right: 16px;
        bottom: calc(16px + env(safe-area-inset-bottom, 0px));
        left: 16px;
        z-index: 11;
        display: grid;
        gap: 12px;
        padding: 14px 16px;
        border: 1px solid rgba(216, 207, 192, 0.92);
        border-radius: 22px;
        background: rgba(255, 251, 246, 0.96);
        box-shadow: 0 18px 36px rgba(23, 32, 51, 0.16);
        backdrop-filter: blur(12px);
    }

    .project-mobile-summary-toggle {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        width: 100%;
        min-height: 44px;
        padding: 0;
        border: none;
        background: none;
        font: inherit;
        color: inherit;
        text-align: left;
        cursor: pointer;
    }

    .project-mobile-summary-toggle-label {
        font-size: 0.7rem;
        color: var(--muted);
        font-weight: 800;
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }

    .project-mobile-summary-toggle-total {
        margin-left: 8px;
        font-size: 0.95rem;
        color: var(--accent-strong);
        font-weight: 700;
    }

    .project-mobile-summary-toggle-icon {
        flex: 0 0 auto;
        width: 10px;
        height: 10px;
        border-right: 2px solid var(--muted);
        border-bottom: 2px solid var(--muted);
        transform: rotate(45deg);
        transition: transform 160ms ease;
    }

    .project-mobile-summary-toggle[aria-expanded="true"] .project-mobile-summary-toggle-icon {
        transform: rotate(-135deg);
    }

    .project-mobile-summary-details {
        display: grid;
        gap: 12px;
    }

    .project-mobile-summary-details[hidden] {
        display: none;
    }

    .calculator-layout[data-tool="project"],
    .calculator-layout[data-tool="calculator"],
    .results-layout {
        padding-bottom: 120px;
    }

    /* 卡片态 3D 摘要页脚在移动端要给固定定位的 .project-mobile-summary
       （CURRENT PLAN 价格条，bottom:16px+safe-area / 高度约74px，见上方 :7067）留净空，
       否则页脚最后一行文字会被价格条压住/裁切。120px 沿用上面 :7140 同一条通用净空常量。
       页脚隐藏时（syncFooterSummaryReserve 量高阶段）margin 不计入布局，不影响地板测量。
       用 [data-tool="project"] 加特异性：universal 的 `.three-stage-summary-footer { margin:
       0 16px 16px }`（site.css:9849,晚于本条源码序）specificity 同为 (0,1,0)，源序在后会赢，
       仅靠这条 media block 本身赢不了，必须加限定选择器。 */
    .calculator-layout[data-tool="project"] .three-stage-summary-footer {
        margin-bottom: calc(120px + env(safe-area-inset-bottom, 0px));
    }

    .planning-notes summary {
        padding: 14px 16px;
    }

    .planning-note-list {
        padding: 0 16px 16px 34px;
    }

    .app-feedback {
        right: 16px;
        left: 16px;
        bottom: 16px;
        max-width: none;
    }
}

@media (max-width: 720px) {
    .calculator-layout[data-slug="deck-builder"] .preview-stage-control-row {
        justify-content: flex-start;
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        padding-bottom: 4px;
        scrollbar-width: none;
    }

    .calculator-layout[data-slug="deck-builder"] .preview-stage-control-row::-webkit-scrollbar {
        display: none;
    }

    .calculator-layout[data-slug="deck-builder"] .preview-stage-chip {
        flex: 0 0 auto;
        min-height: 44px;
        white-space: nowrap;
    }

    .three-stage[data-scene-slug="deck-builder"] .three-stage-layer-controls {
        left: 10px;
        right: 10px;
        max-width: calc(100% - 20px);
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        padding: 7px 8px;
    }

    .three-stage[data-scene-slug="deck-builder"] .three-stage-layer-chip {
        flex: 0 0 auto;
        min-height: 44px;
        padding: 0 10px;
        font-size: 0.72rem;
    }

    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-controls {
        left: 10px;
        right: 10px;
        max-width: calc(100% - 20px);
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        padding: 7px 8px;
        scrollbar-width: none;
    }

    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-controls::-webkit-scrollbar {
        display: none;
    }

    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-chip {
        flex: 0 0 auto;
        min-height: 44px;
        padding: 0 10px;
        font-size: 0.72rem;
    }

    .three-stage[data-scene-slug="fence-builder"] .three-stage-layer-controls {
        left: 10px;
        right: 10px;
        max-width: calc(100% - 20px);
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        padding: 7px 8px;
    }

    .three-stage[data-scene-slug="fence-builder"] .three-stage-layer-controls::-webkit-scrollbar {
        display: none;
    }

    .three-stage[data-scene-slug="fence-builder"] .three-stage-layer-chip {
        flex: 0 0 auto;
        min-height: 44px;
        padding: 0 10px;
        font-size: 0.72rem;
    }

    .three-stage[data-scene-slug="pergola-builder"] .three-stage-layer-controls {
        left: 10px;
        right: 10px;
        max-width: calc(100% - 20px);
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        padding: 7px 8px;
        scrollbar-width: none;
    }

    .three-stage[data-scene-slug="pergola-builder"] .three-stage-layer-controls::-webkit-scrollbar {
        display: none;
    }

    .three-stage[data-scene-slug="pergola-builder"] .three-stage-layer-chip {
        flex: 0 0 auto;
        min-height: 44px;
        padding: 0 10px;
        font-size: 0.72rem;
    }

    .three-stage[data-scene-slug="fence-builder"] .three-stage-controls {
        bottom: 32px;
    }

    /* 32px controls offset + 44px control-row height + 8px gap. */
    .three-stage[data-scene-slug="fence-builder"] .three-stage-legend {
        bottom: 84px;
    }

    .three-stage[data-scene-slug="shed-builder"] .three-stage-layer-chip {
        flex: 0 0 auto;
        min-height: 44px;
        padding: 0 10px;
    }

    .three-stage[data-scene-slug="deck-builder"] .three-stage-layer-summary {
        left: 10px;
        right: 10px;
        bottom: 64px;
        max-width: calc(100% - 20px);
        font-size: 0.78rem;
    }

    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-summary {
        left: 10px;
        right: 10px;
        bottom: 96px;
        max-width: calc(100% - 20px);
        font-size: 0.78rem;
    }

    .three-stage[data-scene-slug="fence-builder"] .three-stage-layer-summary {
        left: 10px;
        right: 10px;
        bottom: 96px;
        max-width: calc(100% - 20px);
        font-size: 0.78rem;
    }

    .three-stage[data-scene-slug="pergola-builder"] .three-stage-layer-summary {
        left: 10px;
        right: 10px;
        bottom: 96px;
        max-width: calc(100% - 20px);
        font-size: 0.78rem;
    }
}

/* S-P2-1: 3D first-use guidance overlay */
.three-guide-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(23, 32, 51, 0.72);
    color: #fff;
    font-size: 0.95rem;
    z-index: 10;
    text-align: center;
    backdrop-filter: blur(4px);
}
.three-guide-overlay p { margin: 0 0 12px; }
.three-guide-overlay button {
    min-height: 44px;
    padding: 8px 24px;
    border: 1px solid rgba(255,255,255,0.4);
    border-radius: 6px;
    background: rgba(255,255,255,0.15);
    color: #fff;
    font-weight: 700;
    cursor: pointer;
}

/* S-P2-4: 3D retry button */
.three-retry-btn {
    display: block;
    margin: 12px auto;
    min-height: 44px;
    padding: 8px 24px;
    border: 1px solid rgba(23, 32, 51, 0.2);
    border-radius: 6px;
    background: #fff;
    color: #172033;
    font-weight: 700;
    cursor: pointer;
}

/* Field tooltip hint */
.field-tooltip {
    display: block;
    font-size: 0.75rem;
    color: #666;
    margin-top: -2px;
    margin-bottom: 4px;
    line-height: 1.3;
}

/* Account sign-in control (header) — quiet, native to the pill/terracotta system */
.account-control {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 10px 12px;
}

.account-signin-form {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* Compact header field: override the global input{width:100%} and match the pill cluster */
.account-control input[type="email"] {
    width: auto;
    min-width: 190px;
    max-width: 220px;
    min-height: 44px;
    padding: 0 16px;
    border-radius: var(--we-radius-sm);
    background: var(--panel);
}

.account-control input[type="email"]::placeholder {
    color: var(--muted);
}

/* Sign-in button: the existing .button.secondary pill, sized down for the 76px header */
.account-signin-form .button {
    min-height: 44px;
    padding: 10px 18px;
    font-size: 0.92rem;
    box-shadow: none;
}

.account-signed-in {
    color: var(--muted);
    font-size: 0.88rem;
    white-space: nowrap;
}

/* "Signed in as <完整邮箱>" 只留给读屏:可见文本是缩略后的邮箱(见 layouts/site.php),
   离屏保留完整语义。用离屏而不是 display:none —— 后者读屏也读不到。 */
.account-signed-in-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.account-logout-form {
    display: inline-flex;
}

/* Sign out: a quiet text button that firms into an outlined pill on hover */
.account-logout-form .button {
    min-height: 44px;
    padding: 8px 14px;
    font-size: 0.88rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--we-radius-sm);
    color: var(--muted);
    box-shadow: none;
}

.account-logout-form .button:hover,
.account-logout-form .button:focus-visible {
    color: var(--ink);
    border-color: var(--line);
    background: var(--panel);
}

/* Neutral status line ("check your email") drops onto its own row below the form.
   Left-aligned to match .account-signin-note directly above it inside the popover;
   right-aligning it left the two lines of copy disagreeing with each other. */
.account-signin-status {
    flex-basis: 100%;
    margin: 2px 0 0;
    text-align: left;
    font-size: 0.8rem;
    color: var(--accent-strong);
}

/* Mobile: sign-in control stays inline in the header row; only the popover form stacks */
@media (max-width: 960px) {
    .account-signin-form {
        width: 100%;
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
    }

    .account-control input[type="email"] {
        max-width: none;
        width: 100%;
    }

    .account-signin-form .button {
        width: 100%;
    }
}
.material-compare{
    padding: 13px 16px 11px;
    border-bottom: 1px solid rgb(238, 230, 217);
}
.material-compare-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 10px;
    margin: 12px 0;
}

.material-compare-grid[hidden] {
    display: none;
}

.material-compare-card.is-current {
    border: 2px solid rgba(23, 32, 51, 0.55);
}

@keyframes total-pulse {
    0% { transform: scale(1); }
    35% { transform: scale(1.12); color: #8a4f12; }
    100% { transform: scale(1); }
}

.project-mobile-summary-total.is-total-pulse,
.project-mobile-summary-toggle-total.is-total-pulse {
    display: inline-block;
    animation: total-pulse 480ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .project-mobile-summary-total.is-total-pulse,
    .project-mobile-summary-toggle-total.is-total-pulse {
        animation: none;
    }
}

.quick-dimension-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}

.quick-dimension-chip {
    min-height: 32px;
    padding: 2px 10px;
    border: 1px solid rgba(216, 207, 192, 0.9);
    border-radius: var(--we-radius-pill);
    background: rgba(255, 253, 248, 0.9);
    font-size: 0.78rem;
    cursor: pointer;
}

.quick-dimension-chip:hover {
    border-color: rgba(23, 32, 51, 0.4);
}

.account-signin {
    position: relative;
}

.account-signin-summary {
    list-style: none;
    cursor: pointer;
    padding: 8px 10px;
    border: none;
    background: none;
    font-size: 13.5px;
    font-weight: 400;
    color: #6b6558;
}

.account-signin-summary:hover {
    color: var(--we-accent);
}

.account-signin-summary::-webkit-details-marker {
    display: none;
}

.account-signin-pop {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    z-index: 20;
    width: min(320px, calc(100vw - 32px));
    padding: 14px 16px;
    border: 1px solid rgba(216, 207, 192, 0.92);
    border-radius: 16px;
    background: rgba(255, 251, 246, 0.98);
    box-shadow: 0 18px 36px rgba(23, 32, 51, 0.16);
    display: grid;
    gap: 10px;
}

.account-signin-note {
    font-size: 0.8rem;
    color: var(--muted);
    margin: 0;
}

@media (max-width: 720px) {
    .account-signin-pop {
        position: fixed;
        top: 72px;
        right: 16px;
        left: 16px;
        width: auto;
    }
}

/* Phase 4 (2026-07-26): the calculator BOM shares the builder's seven-column card
   treatment at <=640px (see the shared #project-material-list / #calculator-material-list
   block above), so the legacy eight-column [data-bom-cards-ready] card rules are gone.
   `data-bom-cards-ready` still ships from applyBomResponsiveCellLabels and gates the
   JS-labelled cells below. */
.saved-plans-link {
    display: inline-block;
    margin-left: 10px;
    font-size: 0.8rem;
    font-weight: 600;
    text-decoration: underline;
}

/* No-email save row: the "View saved plans" link and the email upsell button sit right next
   to each other with no text node between them (both are created via document.createElement),
   so they need their own gap or they'd run together. */
.saved-plans-link + .plan-save-upsell {
    margin-left: 10px;
}

@media (max-width: 640px) {
    #calculator-material-list[data-bom-cards-ready] tbody td[data-bom-label]::before {
        content: attr(data-bom-label);
    }
}

.admin-console-nav { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: .5rem; }
.admin-console-nav a { font-weight: 600; text-decoration: none; }
.admin-runbook > summary { cursor: pointer; list-style: none; }
.admin-runbook > summary h2 { display: inline; }
.admin-runbook[open] > summary { margin-bottom: 1rem; }

/* D18: in fullscreen the floating ✕ claims the stage's top-right corner, so the existing
   in-stage chip row has to step aside. Declared here, after the slug overrides that also
   set `right` on that row, so it wins on every slug. */
.preview-stage.is-zoomed .preview-stage-controls {
    right: 62px;
}

/* D18: the 32px desktop zoom buttons grow to the 44px touch target on mobile. */
@media (max-width: 640px) {
    .preview-zoom-button {
        min-height: 44px;
        min-width: 44px;
        width: 44px;
        height: 44px;
        font-size: 14px;
    }

    .preview-fullscreen-close {
        top: 20px;
        right: 20px;
    }

    /* At 44px touch size the close button owns the whole top band, so the chips drop to
       their own row immediately below it instead of sharing the line. */
    .preview-surface.is-fullscreen .preview-surface-layer-slot:not(:empty) {
        top: 76px;
        max-width: calc(100vw - 24px);
    }

    .preview-stage.is-zoomed .preview-stage-controls {
        right: 72px;
    }
}

/* ---------------------------------------------------------------------------
   Batch 6 — mobile close-out for the Warm Editorial builder topology.
   Declared last so each rule beats the same-specificity desktop declaration it
   overrides without needing an id or !important.
   --------------------------------------------------------------------------- */

/* D18: ⤢ expands one preview surface to the full width of the results column, which
   only means anything while `.preview-surfaces` can hold two tracks (~736px viewport
   and up). Below the 720px breakpoint that grid is single-track, so the wide control is
   hidden and ⛶ (fullscreen) is the only enlargement affordance on phones. The floating
   ✕ is left alone: it stays `hidden` until a wide state exists, so it is only reachable
   as the escape hatch for a state entered before a resize. */
@media (max-width: 720px) {
    .preview-zoom-button[data-preview-zoom-wide] {
        display: none;
    }
}

@media (max-width: 640px) {
    /* Summary card row packs to two columns instead of auto-fit (which produced a single
       320px-wide column on small phones). The Material theme card is taller than a metric
       card because it carries a description line, so it spans the whole row rather than
       stretching its row-mates to match. */
    .project-summary-metrics {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .project-summary-metrics > .material-theme-summary {
        grid-column: 1 / -1;
    }

    /* Card-mode BOM rows: the whole row toggles Quantity basis (gesture A), but the
       visible "Row details" affordance is a bare 14px text button, so it gets its
       own 44px tap target. */
    #project-material-list .bom-explanation-toggle,
    #calculator-material-list .bom-explanation-toggle {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        margin-top: 0;
    }
}

/* The two mobile-summary jump links are bare `#id` anchors, so the browser parks the
   target at viewport top — underneath the sticky header. Measured heights: 57px on desktop,
   73px from 365px up to 960px, 129px at 360px and below — that is where .nav-row wraps,
   not at 640px as this note used to claim. #calculator-lead-panel joins
   this group for the same reason: the calculator's quote-cta-card "Compare contractor quotes"
   is a bare in-page anchor too (see calculator.php), not a smooth-scroll JS jump. */
#project-input-form,
#calculator-input-form,
#project-preview,
#calculator-preview,
#project-material-list,
#calculator-material-list,
#project-plan-review,
#calculator-plan-review,
#calculator-lead-panel {
    scroll-margin-top: 72px;
}

/* 移动端锚点要同时避开站点头和吸顶的 .tool-stage-nav。后者实测 58px 高、
   上方 margin 12px,所以是 header 真实高度 + 70px。原先写死 144px/216px:
   144px 在 365px 宽下还算贴近(实际 131px),216px 则整整多滚 85px,点"Materials"
   会把标题甩到导航条下方一大截。和上面的 sticky 偏移共用同一个变量。 */
@media (max-width: 960px) {
    #project-input-form,
    #calculator-input-form,
    #project-preview,
    #calculator-preview,
    #project-material-list,
    #calculator-material-list,
    #project-plan-review,
    #calculator-plan-review,
    #calculator-lead-panel {
        scroll-margin-top: calc(var(--site-header-effective-height, var(--site-header-height, 73px)) + 70px);
    }
}

/* ============================================================================
   Phase 2R - builder design-fidelity refactor (Redesign-Spec §1/§2/§5.3/§5.4/§12)
   ========================================================================== */

/* --- Project status shell: rail anchor + slug snapshots (the status strip is gone;
   its headline/status/chips now live in the page-head title strip) ---------- */
.project-status-shell {
    margin-top: 16px;
}

/* Deck and the other non-regulated builders leave the anchor empty, so the shell must not
   add a band of its own space between the risk banner and the three-zone layout. */
.project-status-shell:has(> .project-summary-anchor:empty) {
    margin-top: 0;
}

/* --- Left column: presets -> Inputs -> numbered step cards ---------------- */
.calculator-presets,
.calculator-scope-summary {
    border-radius: var(--we-radius-card);
}

.input-panel > h2 {
    margin: 18px 0 10px;
    color: var(--we-text-4);
    font-family: var(--we-font-sans);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.project-stage {
    border-radius: 13px;
    overflow: hidden;
}

/* `.project-stage summary` (0,1,1) and `.project-stage:not([open]) summary` (0,2,1) are
   declared earlier and would win on padding / gap / background, so the step-head skin is
   written one step heavier rather than relying on source order. */
.project-stage > summary.project-stage-summary,
.project-stage:not([open]) > summary.project-stage-summary {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 10px;
    padding: 13px 15px;
    background: var(--we-band);
    color: var(--we-accent-dim);
}

/* §12: unified disclosure arrows. */
.project-stage-summary::after {
    content: "\25B8";
    color: var(--we-accent-soft);
    font-size: 12px;
    line-height: 1;
}

.project-stage[open] > .project-stage-summary::after {
    content: "\25BE";
}

.project-stage-kicker {
    min-height: 0;
    padding: 0;
    background: none;
    color: var(--we-accent-dim);
    font-family: var(--we-font-mono);
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0;
}

.project-stage-kicker::after {
    content: "\00B7";
    margin-left: 8px;
    color: var(--we-accent-soft);
}

.project-stage-title-group strong {
    color: var(--we-accent-dim);
    font-family: var(--we-font-sans);
    font-size: 12.5px;
    font-weight: 600;
}

.project-stage-count {
    min-height: 0;
    padding: 1px 8px;
    background: rgba(255, 255, 255, 0.72);
    color: var(--we-text-3);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: none;
}

.project-scope-first-notes {
    margin-bottom: 10px;
}

/* --- Preview: two standalone cards, no outer "Live preview" shell --------- */
.preview-panel {
    display: block;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

.preview-surfaces {
    margin-top: 0;
}

.preview-surface {
    display: flex;
    flex-direction: column;
    padding: 0;
    border-radius: var(--we-radius-card);
    background: var(--we-card);
    box-shadow: var(--we-shadow-card);
    overflow: hidden;
}

.preview-surface-header {
    align-items: center;
    gap: 10px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--we-border-row);
    background: none;
}

.preview-surface .preview-header {
    display: block;
    background: none;
    color: inherit;
}

.preview-surface-eyebrow,
.preview-surface .preview-header > .eyebrow {
    margin: 0;
    color: var(--we-accent);
    font-family: var(--we-font-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.preview-surface-title {
    margin: 0;
    color: var(--we-text);
    font-family: var(--we-font-sans);
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.3;
}

.preview-surface-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: end;
    margin-left: auto;
}

.preview-surface-layer-slot:empty {
    display: none;
}

/* The layer chips move from an in-stage overlay into the card header (design §5.4-1),
   so the absolute placement of `.preview-stage-controls` is neutralised in that slot.
   Calculator pages have no slot and keep the overlay. */
.preview-surface-layer-slot .preview-stage-controls {
    position: static;
    top: auto;
    right: auto;
    left: auto;
    flex-direction: row;
    align-items: center;
    max-width: none;
    gap: 6px;
}

.preview-surface .preview-stage {
    margin: 0;
    padding: 12px 16px;
}

.preview-surface .preview-caption {
    margin: 0;
    padding: 0 16px 12px;
    max-width: none;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
    text-align: left;
}

/* 3D card stays visible from first paint; the striped placeholder carries the
   opt-in button and disappears once the renderer has taken over the stage (§12). */
.preview-three-placeholder {
    display: grid;
    justify-items: center;
    align-content: center;
    gap: 10px;
    min-height: 190px;
    margin: 12px 16px 16px;
    padding: 20px;
    border-radius: var(--we-radius-sm);
    background: repeating-linear-gradient(45deg, #f4f0e7, #f4f0e7 10px, #efe9dd 10px, #efe9dd 20px);
}

[data-three-stage]:not([hidden]) + .preview-three-placeholder {
    display: none;
}

.preview-three-placeholder-note {
    margin: 0;
    max-width: 42ch;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
    text-align: center;
}

/* --- Related takeoffs: full-width chip row after the material guide ------- */
.project-related-calculators {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 20px;
    background: var(--we-card);
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-card);
    box-shadow: var(--we-shadow-card);
}

.project-related-calculators-copy {
    display: grid;
    gap: 4px;
    margin: 0;
    flex: 1 1 260px;
    min-width: 0;
}

.project-related-calculators-copy > .eyebrow {
    padding: 11px 16px;
    border-bottom: 1px solid var(--we-border-row);
    margin: 0;
    color: rgba(23, 32, 51, 0.58);
    font-size: 1rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.project-related-calculators-note {
    margin: 0;
    padding: 11px 16px;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
}

.project-related-calculators-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.project-related-link {
    display: inline-flex;
    align-items: center;
    min-height: 34px;
    padding: 7px 13px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-card);
    color: var(--we-text-2);
    font-size: 12.5px;
    text-decoration: none;
}

.project-related-link:hover,
.project-related-link:focus-visible {
    border-color: var(--we-accent);
    color: var(--we-accent-deep);
}

@media (max-width: 640px) {
    .preview-surface-controls {
        margin-left: 0;
        width: 100%;
        justify-content: start;
    }

    .preview-three-placeholder {
        min-height: 160px;
    }
}

/* T1 companion: the rules above re-declare `display`, which would defeat the UA
   `[hidden]` behaviour for the surfaces and the 3D stage. */
.preview-surface[hidden],
.preview-three-placeholder[hidden],
.project-related-calculators[hidden],
.project-related-link[hidden] {
    display: none;
}

/* Phase 2R follow-up: the step head is a single 13.5px line (design §5.3); the stage
   description moved into the body so the head stays compact. */
.project-stage-description {
    margin: 0 0 12px;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
}

/* Layer chips in the card header follow the design's 11px chip scale rather than the
   overlay-sized buttons they used inside the stage. */
.preview-surface-layer-slot .preview-stage-control-row {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    justify-content: end;
}

.preview-surface-layer-slot .preview-stage-chip {
    min-height: 0;
    padding: 4px 11px;
    white-space: nowrap;
    border: 0;
    border-radius: var(--we-radius-sm);
    background: var(--we-band);
    color: var(--we-text-2);
    font-family: var(--we-font-sans);
    font-size: 11px;
    font-weight: 500;
    line-height: 1.35;
    box-shadow: none;
}

.preview-surface-layer-slot .preview-stage-chip.is-active,
.preview-surface-layer-slot .preview-stage-chip[aria-selected="true"] {
    background: var(--we-dark);
    color: #ffffff;
}

.preview-surface-header .preview-zoom-controls {
    flex: none;
}

/* The 2D card foot carries one line only: the estimator caption (design §5.4). */
.preview-surface .preview-caption {
    border: 0;
    background: none;
    font-size: 12px;
}

/* Callouts the 2D builder moved off the drawing because the inline canvas is too narrow
   to place them without covering the figure. Populated by the builder (deck.js today),
   hidden whenever nothing was deferred, so it costs nothing on the other pages. */
.preview-plan-annotations {
    padding: 0 12px 12px;
}

.preview-plan-annotations[hidden] {
    display: none;
}

.preview-plan-annotations-title {
    margin: 0 0 4px;
    color: var(--we-text-2);
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.preview-plan-annotations-list {
    display: flex;
    flex-direction: column;
    gap: 3px;
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--we-text);
    font-size: 12px;
    line-height: 1.45;
}

.preview-plan-annotations-list li::before {
    content: "";
    display: inline-block;
    width: 5px;
    height: 5px;
    margin-right: 7px;
    border-radius: 50%;
    background: var(--we-text-3);
    vertical-align: middle;
}

@media (max-width: 720px) {
    .preview-surface-layer-slot .preview-stage-control-row {
        flex-wrap: nowrap;
        overflow-x: auto;
        justify-content: start;
    }

    /* Scroll affordance for the layer chip row (data-scroll-hint is set by
       syncChipRowScrollHint in common.js). Fading the chips themselves is what makes
       this readable: an edge shadow painted on the row's own background sits *behind*
       the opaque chips and is invisible except in the gaps between them. */
    .preview-surface-layer-slot .preview-stage-control-row[data-scroll-hint="end"] {
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
        mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
    }

    .preview-surface-layer-slot .preview-stage-control-row[data-scroll-hint="start"] {
        -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 34px), transparent);
        mask-image: linear-gradient(to left, #000 calc(100% - 34px), transparent);
    }

    .preview-surface-layer-slot .preview-stage-control-row[data-scroll-hint="both"] {
        -webkit-mask-image: linear-gradient(to right, transparent, #000 34px, #000 calc(100% - 34px), transparent);
        mask-image: linear-gradient(to right, transparent, #000 34px, #000 calc(100% - 34px), transparent);
    }


    .preview-surface-layer-slot .preview-stage-chip {
        flex: 0 0 auto;
        min-height: 44px;
        min-width: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* Card head layout: title on the left, zoom buttons pinned top-right, and the 2D layer
   chips on their own full-width row underneath. At the half-column width of the two-card
   grid four chips plus two buttons cannot share one line, so the chips get the second row
   instead of pushing the buttons out of the corner. */
.preview-surface-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    row-gap: 10px;
}

.preview-surface-copy {
    grid-column: 1;
    grid-row: 1;
}

.preview-surface-controls {
    display: contents;
}

.preview-surface-header .preview-zoom-controls {
    grid-column: 2;
    grid-row: 1;
}

.preview-surface-layer-slot {
    grid-column: 1 / -1;
    grid-row: 2;
    min-width: 0;
}

/* Slug overrides written for the old in-stage overlay (garage-ADU pins the chip row to
   the stage corners, bathroom stretches it) must not follow the chips into the card
   header slot, or the row detaches and floats over the page. Declared last and one class
   heavier than those overrides so it wins on every slug without !important. */
.calculator-layout[data-slug] .preview-surface-layer-slot .preview-stage-controls {
    position: static;
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
    width: auto;
    max-width: none;
    flex-direction: row;
    align-items: center;
    gap: 6px;
}

.calculator-layout[data-slug] .preview-surface-layer-slot .preview-stage-control-row {
    justify-content: end;
}

@media (max-width: 720px) {
    .calculator-layout[data-slug] .preview-surface-layer-slot .preview-stage-control-row {
        justify-content: start;
    }
}

/* §2.2 eyebrow / label ladder: 11px uppercase, .1em inside a card. The left column's
   "Quick presets" and "Inputs" labels are card-internal section labels. */
.input-panel > h2,
.input-panel .calculator-presets .calculator-scope-eyebrow {
    font-size: 11px;
    letter-spacing: 0.1em;
}

/* ===== 2026-07-26 左列输入栏对齐设计稿 §5.3(用户指令:去嵌套壳/复制文字与预设样式) =====
   Phase 4:选择器由 [data-tool="project"] 放宽为 [data-tool](同特异度),让 calculator
   页复用同一套左列皮肤;.project-* 类只存在于 builder 页,放宽后仍只命中 builder。 */

/* 外壳去除:input-panel 是平铺列,卡片壳只留给预设卡与分步卡本身 */
.calculator-layout[data-tool] > .input-panel {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0 2px 24px 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.calculator-layout[data-tool] .calculator-presets {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

/* 小标题:600 12px / .08em / 大写 / #9a9284(设计稿左列) */
.calculator-layout[data-tool] .input-panel > h2,
.calculator-layout[data-tool] .calculator-presets .calculator-scope-eyebrow {
    margin: 0;
    font: 600 18px var(--we-font-sans);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--we-text-4);
    padding-bottom: 8px;
}

.calculator-layout[data-tool] .input-panel > h2 {
    margin-top: 4px;
}

.calculator-layout[data-tool] .calculator-presets-copy {
    margin-bottom: 10px;
}

/* 预设卡:圆点 + 标题 12.5/600 + 副文 11(设计稿状态色) */
.calculator-layout[data-tool] .calculator-preset-grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.calculator-layout[data-tool] .project-preset-button,
.calculator-layout[data-tool] .calculator-preset-button {
    display: grid;
    grid-template-columns: 9px minmax(0, 1fr);
    gap: 2px 10px;
    align-items: start;
    min-height: 0;
    padding: 11px 13px;
    border: 1px solid var(--we-border);
    border-radius: 11px;
    background: var(--we-card);
    box-shadow: none;
    text-align: left;
    color: var(--we-text);
}

.calculator-layout[data-tool] .project-preset-button::before,
.calculator-layout[data-tool] .calculator-preset-button::before {
    content: "";
    grid-row: 1 / span 2;
    width: 9px;
    height: 9px;
    margin-top: 4px;
    border-radius: 50%;
    background: #d8d0c2;
}

.calculator-layout[data-tool] .project-preset-button strong,
.calculator-layout[data-tool] .calculator-preset-button strong {
    display: block;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.35;
}

.calculator-layout[data-tool] .project-preset-button small,
.calculator-layout[data-tool] .calculator-preset-button small {
    display: block;
    grid-column: 2;
    font-size: 11px;
    line-height: 1.4;
    color: var(--we-text-4);
}

.calculator-layout[data-tool] .project-preset-button.is-active,
.calculator-layout[data-tool] .calculator-preset-button.is-active {
    background: var(--we-selected);
    border-color: var(--we-accent);
    box-shadow: none;
}

.calculator-layout[data-tool] .project-preset-button.is-active::before,
.calculator-layout[data-tool] .calculator-preset-button.is-active::before {
    background: var(--we-accent);
}

/* 分步卡节奏与字段样式(设计稿:body 15px;label 11 #9a9284;输入框等宽 600) */
.calculator-layout[data-tool] .project-stage-stack {
    gap: 12px;
    margin-bottom: 0;
}

.calculator-layout[data-tool] .project-stage-body {
    padding: 15px;
    gap: 14px;
}

.calculator-layout[data-tool] .input-panel .field-group label {
    font-size: 11px;
    color: var(--we-text-4);
    font-weight: 400;
}

.calculator-layout[data-tool] .input-panel .field-group input[type="number"],
.calculator-layout[data-tool] .input-panel .field-group input[type="text"] {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--we-border-input);
    border-radius: 8px;
    background: var(--we-card-sub);
    font: 600 13px var(--we-font-mono);
    color: var(--we-text);
}

.calculator-layout[data-tool] .input-panel .field-group select {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--we-border-input);
    border-radius: 8px;
    background: var(--we-card-sub);
    font-size: 13px;
    color: var(--we-text);
}

/* 主按钮:整宽 13px 内距 / 10px 圆角(设计稿) */
.calculator-layout[data-tool] .project-input-submit {
    margin: 0;
}

.calculator-layout[data-tool] .project-input-actions .button.primary {
    /* Was width: 100%, which pushed Reset onto its own row. */
    width: auto;
    flex: 1 1 auto;
    padding: 13px;
    border-radius: 10px;
    font-size: 14px;
}

.calculator-layout[data-tool] .input-panel .assumption-note {
    font-size: 12px;
    line-height: 1.5;
    padding: 10px 12px;
    border-radius: 9px;
}

.calculator-layout[data-tool] .project-stage-body .field-group-header h3 {
    font: 600 13.5px var(--we-font-sans);
    color: var(--we-text);
}

/* 2026-07-26 分步卡字段对齐设计:输入框 ~38px 高(去全局 56px min-height),
   移动端仍保 44px 触控 */
.calculator-layout[data-tool] .input-panel .field-group input[type="number"],
.calculator-layout[data-tool] .input-panel .field-group input[type="text"],
.calculator-layout[data-tool] .input-panel .field-group select {
    min-height: 0;
    line-height: 1.3;
}

.calculator-layout[data-tool] .input-panel .field-help {
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--we-text-4);
}

.calculator-layout[data-tool] .input-panel .quick-dimension-chip {
    min-height: 0;
    padding: 4px 10px;
    font-size: 11px;
    border-radius: var(--we-radius-pill);
}

@media (max-width: 640px) {
    .calculator-layout[data-tool] .input-panel .field-group input[type="number"],
    .calculator-layout[data-tool] .input-panel .field-group input[type="text"],
    .calculator-layout[data-tool] .input-panel .field-group select {
        min-height: 44px;
        /* iOS Safari auto-zooms any focused input whose computed font-size is
           below 16px and does not zoom back out on blur. The base rules set
           13px (mono shorthand at ~:7559); 16px here disables the zoom. */
        font-size: 16px;
    }

    .calculator-layout[data-tool] .input-panel .quick-dimension-chip {
        min-height: 44px;
    }
}

/* ===== Phase 4 (2026-07-26): material-calculator page, design §14 =====
   Only the pieces the calculator does not inherit from the builder skin above. */

/* §14.3: calculators have few inputs, so the groups list straight down under section
   labels instead of the builder's numbered step accordion. */
.calculator-layout[data-tool="calculator"] .input-panel .field-group {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

.calculator-layout[data-tool="calculator"] .input-panel .field-group-header {
    margin-bottom: 8px;
}

.calculator-layout[data-tool="calculator"] .input-panel .field-group-header h3 {
    font: 600 15px var(--we-font-sans);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--we-text-4);
}

.calculator-layout[data-tool="calculator"] .input-panel > .panel-actions {
    margin-top: 18px;
    gap: 8px;
}

.calculator-layout[data-tool="calculator"] .input-panel .project-input-actions .button.primary {
    /* Was width: 100%, which pushed Reset onto its own row on calculator pages the same
       way the builder rule did. */
    width: auto;
    flex: 1 1 auto;
    justify-content: center;
}

/* §14.2: the title-strip total card carries the status / review pills the calculator's
   old right-column summary card used to own. */
.calculator-head-total .page-head-total-statuses {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
}

.calculator-head-total .page-head-total-statuses:empty {
    display: none;
}

.calculator-head-total .page-head-total-meta {
    margin: 0;
    color: var(--we-text-3);
    font-size: 12px;
    line-height: 1.5;
}

.calculator-head-total .page-head-total-meta[hidden] {
    display: none;
}

/* §14.4: Compare scenario is a native <details> so it ships collapsed without JS;
   compare.js only flips the panel's own hidden state when a scenario is pinned. */
.scenario-compare-panel {
    display: block;
}

.scenario-compare-panel[hidden] {
    display: none;
}

.scenario-compare-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    cursor: pointer;
    list-style: none;
}

.scenario-compare-head::-webkit-details-marker {
    display: none;
}

.scenario-compare-title {
    font: 600 11px var(--we-font-sans);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--we-text-4);
}

.scenario-compare-head::after {
    content: "▸";
    color: var(--we-accent-soft, #b08050);
    font-size: 12px;
}

.scenario-compare-panel[open] > .scenario-compare-head::after {
    content: "▾";
}

.scenario-compare-body {
    margin-top: 14px;
}

.scenario-compare-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 12px;
}

.scenario-compare-actions .button[hidden] {
    display: none;
}

/* Pinned and current can be different measures (estimated total vs priced subtotal). When
   they are, each amount carries its own basis on a second line instead of the table
   implying one shared meaning. */
.scenario-compare-basis {
    display: block;
    margin-top: 2px;
    color: var(--we-text-4);
    font-size: 11px;
    font-weight: 600;
    line-height: 1.35;
}

/* Pointer-user confirmation that the pin landed; keyboard/AT users get the focus move. */
.scenario-pin-status {
    align-self: center;
    color: var(--we-text-3);
    font-size: 12px;
    font-weight: 700;
}

.scenario-pin-status[hidden] {
    display: none;
}

.project-scenario-table-scroll {
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
}

.project-scenario-compare-table {
    min-width: 680px;
}

.project-scenario-compare-table th,
.project-scenario-compare-table td {
    min-width: 150px;
    vertical-align: top;
}

.project-scenario-compare-table th:first-child {
    min-width: 180px;
}

.project-scenario-delta {
    display: block;
    margin-top: 2px;
    color: var(--we-text-3);
    font-size: 11px;
    font-weight: 700;
}

.project-scenario-bom-changes {
    margin-top: 18px;
    border-top: 1px solid var(--we-border);
    padding-top: 16px;
}

.project-scenario-bom-changes h3,
.project-scenario-bom-group h4 {
    margin: 0;
}

.project-scenario-bom-group {
    margin-top: 14px;
}

.project-scenario-bom-group ul {
    display: grid;
    gap: 6px;
    margin: 8px 0 0;
    padding-left: 20px;
}

.project-scenario-bom-group li > span {
    display: block;
}

/* Touch targets: the guide tabs are 36px of chrome, which is fine under a mouse but under
   the 44px minimum for a finger. */
@media (max-width: 640px) {
    .spec-reference-tab {
        min-height: 44px;
    }
}

/* T1 companion: the field declares its own display, so `hidden` needs an explicit rule
   once the Line status control is relocated into the shared explorer controls row. */
.bom-explorer-field[hidden] {
    display: none;
}

/* The 2D card's caption sits under the stage, matching the builder's preview-caption. */
.preview-surface-note {
    margin: 0;
    padding: 0 16px 16px;
    color: var(--we-text-3);
    font-size: 12.5px;
    line-height: 1.5;
}

/* ---------------------------------------------------------------------------
   2D / 3D preview card parity (design §5.4-1 + §12).
   Declared last so these rules beat the same-specificity in-stage overlay
   declarations they neutralise, without needing an id or !important.
   --------------------------------------------------------------------------- */

/* Layer navigation is card chrome for both surfaces now: common_three.js renders the 3D
   layer chips into `[data-preview-controls-slot="three"]` with the 2D chip classes. The
   `.three-stage-layer-controls` class stays on the scrolling row (the mobile
   keep-active-chip-visible helpers resolve their scroller through it), so the in-stage
   overlay chrome it carries has to be reset here. The slug-scoped variants of that class
   are descendants of `.three-stage` and never reach this host. */
.preview-surface-layer-slot .three-stage-layer-controls {
    position: static;
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
    z-index: auto;
    width: auto;
    max-width: none;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    backdrop-filter: none;
}

/* The 3D stage and its striped placeholder occupy exactly the same box — same inset,
   radius and 340px baseline height as the 2D canvas — so opting into 3D swaps the
   content without moving the card, and both cards read as one system. `flex: 1` lets
   the body grow to the stretched grid-row height when the 2D card is taller. */
.preview-surface [data-three-stage],
.preview-surface .preview-three-placeholder {
    flex: 1 1 auto;
    margin: 12px 16px 16px;
    min-height: 340px;
    border: 0;
    border-radius: var(--we-radius-sm);
    box-shadow: none;
}

/* Before the renderer paints, the stage shows the same warm stripes as the placeholder
   instead of the old cool radial gradient. */
.preview-surface [data-three-stage] {
    width: auto;
    background: repeating-linear-gradient(45deg, #f4f0e7, #f4f0e7 10px, #efe9dd 10px, #efe9dd 20px);
}

/* Bathroom keeps its taller 3D stage (existing slug contract), so its placeholder has to
   match it or opting into 3D would jump the card. */
.calculator-layout[data-slug="bathroom-remodel"] .preview-surface .preview-three-placeholder {
    min-height: clamp(440px, 42vw, 540px);
}

/* Fullscreen is its own fixed panel and must beat the in-card body box above. */
.preview-surface .three-stage.is-zoomed {
    margin: 0;
    min-height: 0;
    border-radius: 28px;
    background: rgba(255, 251, 246, 0.98);
    box-shadow: 0 30px 80px rgba(23, 32, 51, 0.28);
}

/* 2026-07-26 用户标注:①行内单元格垂直居中(与设计稿行 align-items:center 一致)
   ②材料名下方副文/Explain 与材料名文字左缘对齐,不与折叠三角对齐 */
#project-material-list .bom-row > td,
#calculator-material-list .bom-row > td {
    vertical-align: middle;
}

#project-material-list .bom-row > td.bom-material-cell,
#calculator-material-list .bom-row > td.bom-material-cell {
    vertical-align: top;
}

#project-material-list .bom-material-secondary,
#calculator-material-list .bom-material-secondary,
#project-material-list .bom-material-cell .bom-review-dependencies,
#calculator-material-list .bom-material-cell .bom-review-dependencies,
#project-material-list .bom-explanation-toggle,
#calculator-material-list .bom-explanation-toggle {
    /* chevron 宽 1em + .bom-material-head 的 8px 间距 */
    margin-left: calc(1em + 8px);
}

@media (max-width: 640px) {
    #project-material-list .bom-row > td,
    #calculator-material-list .bom-row > td,
    #project-material-list .bom-row > td.bom-material-cell,
    #calculator-material-list .bom-row > td.bom-material-cell {
        vertical-align: top;
    }
}

/* 2026-07-26 Quote CTA 对齐设计稿 §8:按钮组右置一行,13px 字号、11px 内距 */
.quote-cta-card > .quote-cta-actions {
    flex: 1 0 100%;
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
    margin-top: 0;
}

.quote-cta-card .button {
    min-height: 0;
    padding: 11px 15px;
    border-radius: 9px;
    font-size: 13px;
    white-space: nowrap;
}

.quote-cta-card .button.primary {
    padding: 11px 18px;
    font-weight: 600;
}

.quote-cta-card .button.secondary {
    border-color: var(--we-quote-outline);
    color: var(--we-text-2);
}

.quote-cta-card > .project-action-lock-note,
.quote-cta-card > .project-action-feedback {
    flex: 1 0 100%;
    margin-top: 4px;
}

@media (max-width: 640px) {
    .quote-cta-card .button {
        min-height: 44px;
    }
}


/* ============================================================================
   Phase 5 X3 - touch-target contract (≥44px on mobile, ≥20px checkboxes anywhere)
   ============================================================================
   The Phase 5 audit measured five recurring classes of undersized interactive targets
   at 390px across all 16 planner pages, plus one that is undersized on the desktop too.
   These rules sit at the end of the sheet so they win on source order against the base
   declarations they tighten, without having to escalate specificity.

   Deliberately NOT covered:
   - The support-row chevron (`[data-support-toggle]`, 24x24). Its row header
     `.plan-detail-row-head` is already the pointer target - `bindPlanDetailsAccordion()`
     in common.js forwards row-head clicks to the button - and measures 44-56px tall with
     `cursor: pointer`. Verified by clicking the row *label* and watching aria-expanded flip
     false -> true. The button below only gains a 44px hit box so that a direct hit on the
     chevron is compliant as well; its visual size, its `aria-expanded` state and the
     "click the row head = toggle" contract are all unchanged.
   - Site chrome (header / nav / footer) - that is Phase 0/6 scope, not these 16 pages. */

/* --- Checkboxes -------------------------------------------------------------
   The field checkboxes were 13px tall and stretched across the row: a Warm Editorial
   field-skin miss, not a mobile-only bug, so this is set at every breakpoint.

   A native checkbox cannot carry a 44px tap target and a 22px glyph at the same time -
   measured on the real page, Chrome ignores `padding` on `appearance: auto` checkboxes
   (a 22px box with 11px padding still reports a 22x22 border box). So the control is
   skinned: the element itself is a 44x44 transparent hit box, and the visible 22px
   control is painted as a centred background image.

   Negative margins pull the extra 11px back out of the layout on all four sides, so the
   glyph lands exactly where the old 22px box did and nothing around it shifts. The offsets
   are not symmetric because `.input-panel label { display: grid }` (0,1,1) beats
   `.checkbox-row { display: flex }` (0,1,0): these rows are single-column grids with the
   control stacked above its label, and the control carries Chrome's 4px UA left margin.

   The glyph colours are literals rather than var() because a data: URI cannot read custom
   properties; they mirror --we-card (#ffffff) and --we-accent (#c2582a). */
.checkbox-row input[type="checkbox"],
.consent-line input[type="checkbox"] {
    -webkit-appearance: none;
    appearance: none;
    box-sizing: border-box;
    flex: none;
    justify-self: start;
    width: 44px;
    height: 44px;
    min-width: 0;
    min-height: 0;
    max-width: none;
    padding: 0;
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='.75' y='.75' width='20.5' height='20.5' rx='5.5' fill='%23ffffff' stroke='%23c9c0ae' stroke-width='1.5'/%3E%3C/svg%3E") center no-repeat;
    cursor: pointer;
}

.checkbox-row input[type="checkbox"]:checked,
.consent-line input[type="checkbox"]:checked {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Crect x='.75' y='.75' width='20.5' height='20.5' rx='5.5' fill='%23c2582a' stroke='%23c2582a' stroke-width='1.5'/%3E%3Cpath d='M6 11.4l3.4 3.4L16 8.2' fill='none' stroke='%23ffffff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* The outline hugs the 22px glyph instead of ringing the invisible 44px hit box. */
.checkbox-row input[type="checkbox"]:focus-visible,
.consent-line input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--we-accent);
    outline-offset: -10px;
}

.checkbox-row input[type="checkbox"] {
    /* 22px glyph, so 11px of inset to reclaim; +4px UA left margin, +1px optical top. */
    margin: -10px -11px -11px -7px;
    background-size: 22px 22px;
}

/* The lead-consent control sits in a fixed 20px grid track, so its glyph stays 20px and
   the reclaim is 12px a side - which lands the hit box's right edge exactly on the 32px
   text column start, with no overlap onto the consent copy or its Privacy Policy link. */
.consent-line input[type="checkbox"] {
    margin: -8px -12px -12px -12px;
    background-size: 20px 20px;
}

/* The Privacy Policy link is inline mid-sentence, so it cannot be given a 44px box without
   breaking the line. Vertical padding on an inline (non-inline-block) element grows the hit
   and paint area without contributing to line height, so the anchor answers touch over 45px
   while the consent paragraph lays out exactly as before. */
.consent-line span a {
    padding-block: 13px;
}

/* `.plan-details-panel .plan-detail-row-head` carries min-height:44px and cursor:pointer, but
   the satellite support cards ("Contractor handoff pack", "Planning education") render outside
   that panel and so picked up neither. The click already worked there - bindPlanDetailsAccordion
   delegates from the bound root, and clicking the row label flips aria-expanded on both - so the
   rows were silently clickable with no pointer affordance and no guaranteed 44px head. */
.plan-detail-row-head {
    min-height: 44px;
    cursor: pointer;
}

@media (max-width: 640px) {
    /* BOM "Source" column retailer links: 19px tall stacked anchors, the single largest
       group of undersized targets (up to 44 on one page). */
    .bom-links-stack a,
    td.link-cell a,
    .link-cell a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 4px 0;
    }

    /* Related builder / calculator chips. */
    .project-related-link {
        min-height: 44px;
    }

    /* "Project context" disclosure on the 9 calculator pages - a 14px summary line. */
    summary.calculator-scope-eyebrow {
        display: flex;
        align-items: center;
        min-height: 44px;
    }

    /* Mobile summary review pill: a real role="button" jump control at 28px. */
    .project-mobile-summary-reviews[role="button"] {
        min-height: 44px;
        padding: 0 14px;
    }

    /* Shed quick-dimension chips are 44px tall but only 40px wide. */
    .quick-dimension-chip {
        min-width: 44px;
        min-height: 44px;
    }

    /* Chevron hit box only - font-size:0 + the ::after glyph keep it visually 24px. */
    .plan-details-panel .button.project-support-toggle {
        min-width: 44px;
        min-height: 44px;
    }

    /* C2: the BOM unit-price pencil is a 24px glyph, and its inline input is a row-level
       control the audit never saw. Both are new touch targets in the busiest table on the
       page, so they carry the same 44px minimum as the explorer fields. */
    .bom-price-override-toggle {
        min-width: 44px;
        min-height: 44px;
    }

    .bom-price-override-input {
        min-width: 44px;
        min-height: 44px;
    }
}

/* 2026-07-27 移动端触控:站点头尾的公共链接(logo 与页脚清单)此前 19–27px 高。
   仅在触控断点加高,桌面版式不变。 */
@media (max-width: 640px) {
    .site-header .brand {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }

    .site-footer .footer-grid ul a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }
}

/* ============================================================================
   Blog publishing - scoped editorial presentation
   ============================================================================ */
.blog-index,
.blog-article,
.content-editor-shell {
    padding-bottom: 64px;
}

.blog-index-head,
.blog-article-hero,
.content-editor-head {
    padding: 52px 0 28px;
}

.blog-index-head {
    max-width: 72ch;
}

.blog-index-head > p:last-child,
.blog-article-hero > p {
    max-width: 68ch;
}

.blog-card-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
}

.blog-card {
    display: grid;
    align-content: start;
    gap: 12px;
    overflow: hidden;
}

.blog-card > img,
.blog-card-cover-fallback {
    width: 100%;
    aspect-ratio: 3 / 2;
    border-radius: var(--we-radius-sm);
    object-fit: cover;
    background: var(--we-band);
}

.blog-card > .eyebrow,
.blog-card > h2,
.blog-card > p {
    margin: 0;
}

.blog-card > h2 {
    font-size: clamp(22px, 2.3vw, 30px);
    line-height: 1.15;
}

.blog-featured-card {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    margin-bottom: 24px;
}

.blog-featured-card > img,
.blog-featured-card > .blog-card-cover-fallback {
    grid-row: 1 / span 5;
    align-self: start;
}

.blog-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 18px;
    margin-top: 28px;
}

.blog-pagination a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-inline: 10px;
    font-weight: 600;
}

.blog-article-hero {
    max-width: 980px;
}

.blog-article-hero > h1 {
    max-width: 24ch;
    font-size: clamp(36px, 5vw, 58px);
    line-height: 1.04;
}

.blog-article-hero > img,
.blog-article-hero > .blog-article-cover-fallback {
    display: block;
    width: 100%;
    margin-top: 28px;
    aspect-ratio: 3 / 2;
    border-radius: var(--we-radius-card);
    object-fit: cover;
    background: var(--we-band);
}

.blog-trust-row,
.blog-current-as-of {
    font-size: 13px;
}

.blog-article-layout {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(220px, 1fr);
    gap: 24px;
    align-items: start;
    max-width: 1060px;
}

.blog-reading-column {
    grid-column: 1;
    grid-row: 1 / span 2;
    min-width: 0;
}

.blog-toc-rail {
    position: sticky;
    top: 80px;
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
}

.blog-meta-rail {
    grid-column: 2;
    grid-row: 2;
    display: grid;
    gap: 16px;
    min-width: 0;
}

.blog-utility-rail > .panel {
    padding: 18px;
    box-shadow: none;
}

.blog-utility-rail h2 {
    margin: 0 0 10px;
    font-size: 18px;
}

.blog-utility-rail ul,
.blog-utility-rail ol,
.blog-related-articles ul {
    margin: 0;
    padding-left: 20px;
}

.blog-utility-rail li,
.blog-related-articles li {
    margin-bottom: 8px;
    color: var(--we-text-2);
    line-height: 1.5;
}

.blog-toc-rail summary {
    display: flex;
    align-items: center;
    min-height: 44px;
    color: var(--we-text);
    font-weight: 700;
    cursor: pointer;
}

.blog-toc-rail nav {
    margin-top: 10px;
}

.blog-prose {
    width: 100%;
    max-width: 72ch;
    margin: 0;
    font-size: 17px;
    line-height: 1.75;
}

.blog-prose h2,
.blog-prose h3 {
    margin: 1.8em 0 0.55em;
    line-height: 1.2;
}

/* Blog TOC links are bare `#id` anchors, so keep their heading targets below the
   sticky header at each responsive header height. */
.blog-prose h2[id],
.blog-prose h3[id] {
    scroll-margin-top: 72px;
}

@media (max-width: 960px) {
    .blog-prose h2[id],
    .blog-prose h3[id] {
        scroll-margin-top: 88px;
    }
}

@media (max-width: 640px) {
    .blog-prose h2[id],
    .blog-prose h3[id] {
        scroll-margin-top: 144px;
    }
}

/* Mobile 44px tap targets for the 3D overlay chips that per-scene rules still
   shrink below the minimum. Kept last in the file so it wins the source-order
   tie against the equally specific per-scene overrides above. */
@media (max-width: 640px) {
    .three-stage-layer-chip,
    .three-stage-legend-toggle,
    .three-stage[data-scene-slug="bathroom-remodel"] .three-stage-layer-chip,
    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-chip,
    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-layer-chip,
    .three-stage[data-scene-slug="outdoor-kitchen-builder"] .three-stage-legend-toggle {
        min-height: 44px;
    }
}

.blog-prose p,
.blog-prose ul,
.blog-prose ol,
.blog-prose blockquote {
    margin: 0 0 1.2em;
}

.blog-prose img {
    display: block;
    height: auto;
    margin: 24px auto;
    border-radius: var(--we-radius-sm);
}

.blog-prose a {
    overflow-wrap: anywhere;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.blog-prose pre,
.blog-prose table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

.blog-prose table {
    min-width: 0;
}

.blog-prose pre {
    padding: 16px;
    border-radius: var(--we-radius-sm);
    background: var(--we-dark);
    color: var(--we-card-sub);
    font-family: var(--we-font-mono);
    font-size: 14px;
    line-height: 1.6;
}

.blog-prose code {
    font-family: var(--we-font-mono);
}

.blog-card h2 > a,
.content-editor-head a,
.content-editor-list td > a,
.content-editor-list .pagination a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

.blog-related-articles {
    max-width: 72ch;
    margin-top: 24px;
}

.content-editor-shell {
    display: grid;
    gap: 20px;
}

.content-editor-layout {
    grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
    padding-top: 0;
}

.content-editor-layout > div,
.content-editor-layout > aside {
    display: grid;
    align-content: start;
    gap: 20px;
    min-width: 0;
}

.content-editor-layout label:has(> span),
.content-editor-filters label {
    display: grid;
    gap: 6px;
    margin-bottom: 14px;
}

.content-editor-layout textarea {
    width: 100%;
    max-width: 100%;
    padding: 14px 16px;
    border: 1px solid var(--we-border-input);
    background: var(--we-card);
    line-height: 1.55;
    resize: vertical;
}

.content-editor-workspace {
    margin-top: 18px;
}

.content-editor-tabs {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--we-border);
}

.content-editor-tabs > button {
    min-height: 44px;
    padding: 8px 14px;
    border-bottom: 3px solid transparent;
    background: transparent;
    color: var(--we-text-2);
    font-weight: 700;
}

.content-editor-tabs > button[aria-selected="true"] {
    border-bottom-color: var(--we-accent);
    color: var(--we-accent-deep);
}

.content-editor-upload {
    display: grid;
    gap: 12px;
    margin-top: 16px;
    padding: 16px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-card-sub);
}

.content-editor-upload > h3,
.content-editor-upload > label,
.content-editor-upload > p {
    margin: 0;
}

.content-editor-filter-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
    align-items: end;
}

.content-editor-filter-grid > label {
    margin: 0;
}

.content-editor-list {
    min-width: 0;
    overflow: hidden;
}

.content-editor-list .table-wrap {
    max-width: 100%;
    overflow-x: auto;
}

/* 文章列表按翻译组分带。同一篇的 EN/ES 已由 SQL 排到相邻,这里让一组共用一条
   底色、下一组翻转,并在组的第一行画一条比普通行线(--we-border-row)更重的分隔线。
   两套配色下表格都坐在白色 .panel 上,--we-band 在两者中均可见。 */
.content-editor-list tbody tr.is-group-alt > td {
    background: var(--we-band);
}

.content-editor-list tbody tr.is-group-start:not(:first-child) > td {
    border-top: 2px solid var(--we-border);
}

@media (max-width: 760px) {
    .blog-index,
    .blog-article,
    .content-editor-shell {
        padding-bottom: 40px;
    }

    .blog-index-head,
    .blog-article-hero,
    .content-editor-head {
        padding: 36px 0 22px;
    }

    .blog-card-grid,
    .blog-featured-card,
    .content-editor-filter-grid {
        grid-template-columns: 1fr;
    }

    .blog-article-layout {
        grid-template-columns: 1fr;
    }

    .content-editor-layout {
        grid-template-columns: 1fr;
    }

    .blog-featured-card > img,
    .blog-featured-card > .blog-card-cover-fallback,
    .blog-reading-column,
    .blog-toc-rail,
    .blog-meta-rail {
        grid-column: auto;
        grid-row: auto;
    }

    .blog-toc-rail {
        position: static;
    }

    .blog-article-hero > h1 {
        font-size: clamp(32px, 10vw, 44px);
    }

    .blog-card,
    .blog-prose,
    .blog-utility-rail > .panel {
        padding: 18px;
    }

    .blog-utility-rail a,
    .blog-related-articles a,
    .blog-card .eyebrow a,
    .content-editor-tabs > button {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }
}

/* 2026-07-30 /account/projects 视觉对齐 deck-builder:卡片网格换成堆叠计划行。
   Account saved-plans visual contract —— 由 tests/unit/AccountProjectsVisualContractTest.php 钉住。
   本块必须留在文件末尾:.account-plan-name 靠源码顺序覆盖 h1,h2,h3,.brand 的衬线规则,
   往前挪就会让用户自定义的长计划名重新以大衬线体折行。
   堆叠断点用 960px(而非最初的 640px):.account-plan-action 是 .button.secondary,
   line 6209-6216 的全局规则在 ≤960px 让所有 secondary 按钮 width:100%;这里若仍在
   640px 才纵向堆叠,641-960px 会出现行仍是 row 但按钮已满宽、把 flex:none 的名字列
   挤压到 0 的破相区间。用同一个既有断点驱动两条规则,二者不会再失步。 */
.account-plans {
    padding-bottom: 48px;
}

.account-plan-list {
    display: grid;
    gap: 14px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.account-plan-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 18px;
    background: var(--we-card);
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-card);
    box-shadow: var(--we-shadow-card);
}

.account-plan-row:hover,
.account-plan-row:focus-within {
    border-color: var(--we-accent-soft);
}

.account-plan-copy {
    min-width: 0;
}

.account-plan-name {
    margin: 0;
    font-family: var(--we-font-sans);
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.35;
    overflow-wrap: anywhere;
}

.account-plan-meta {
    display: block;
    margin-top: 4px;
    color: var(--we-text-2);
    font-size: 0.85rem;
    line-height: 1.5;
}

.account-plan-action {
    flex: none;
}

.account-plan-controls {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 8px;
}

.account-plan-rename {
    display: flex;
    align-items: center;
    gap: 8px;
}

.account-plan-rename input[type="text"] {
    width: min(220px, 100%);
    min-height: 44px;
}

/* The delete control is a <summary>, so it must lose the native disclosure marker and the
   display:list-item that would otherwise beat the shared .button rule's inline-flex. The
   two-step confirmation has to work without JavaScript, which is why it is a <details>
   rather than a button plus confirm(); see account_projects.php. */
.account-plan-delete {
    min-height: 44px;
    color: var(--we-danger, #8c2f2f);
    cursor: pointer;
    list-style: none;
}

.account-plan-delete::-webkit-details-marker {
    display: none;
}

.account-plan-delete-wrap {
    display: inline-flex;
}

/* Open, the confirmation takes its own full-width line under the other controls. It first
   lived inside the right-hand column, where there was room for neither the sentence nor the
   button: the copy wrapped to two right-aligned lines and shoved the button beneath it.
   `flex: 1 0 100%` makes .account-plan-controls (already flex-wrap: wrap) break the line,
   so the panel gets the width of the whole card instead of the leftover gutter. */
.account-plan-delete-wrap[open] {
    flex: 1 0 100%;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
}

/* Scoped to [open] on purpose. An author `display` on a <details> child overrides the UA's
   `details:not([open]) > *:not(summary) { display: none }`, which is still how Firefox and
   Safari hide the panel -- setting it unconditionally leaves the confirmation permanently
   visible there while Chromium (which hides via ::details-content) looks correct. */
.account-plan-delete-wrap[open] form {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    margin: 0;
}

.account-plan-delete-copy {
    margin: 0;
    color: var(--we-text-2);
    font-size: 0.86rem;
    line-height: 1.45;
    text-align: left;
}

.account-plan-delete-submit {
    flex: none;
    min-height: 44px;
    color: var(--we-danger, #8c2f2f);
}

.account-plans-empty {
    padding: 22px 20px;
    background: var(--we-card-sub);
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-card);
}

.account-plans-empty p {
    margin: 0;
    color: var(--we-text-2);
}

@media (max-width: 960px) {
    .account-plan-row {
        flex-direction: column;
        align-items: stretch;
    }

    .account-plan-action {
        justify-content: center;
        width: 100%;
    }

    .account-plan-controls {
        flex-direction: column;
        align-items: stretch;
    }

    .account-plan-rename,
    .account-plan-rename input[type="text"],
    .account-plan-controls form,
    .account-plan-controls .account-plan-delete-wrap,
    .account-plan-controls .button {
        width: 100%;
    }

    /* `flex: 1 0 100%` must not survive here. .account-plan-controls turns into a column at
       this breakpoint, so flex-basis stops meaning width and starts meaning height: the
       panel grew a full card's worth of empty vertical space and slid out past the right
       edge. The controls already stack full width, so the panel needs no line break -- just
       its own column, sentence above button. */
    .account-plan-delete-wrap[open],
    .account-plan-delete-wrap[open] form {
        flex: none;
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
    }
}
/* End: Account saved-plans visual contract */

/* footer 顶部的偏好细条:语言切换 + 配色切换。
   SPEC: docs/superpowers/specs/2026-08-10-palette-language-toggle-design.md §4.5

   两个控件都从原位置搬到这里了:
   - 语言切换原在 header。移动端 .nav-row 是 flex-wrap: wrap,它会占掉首行空间。
   - 配色按钮原在右下浮动。那个角落已被 .app-feedback 提示条(bottom:20/z-30)
     和移动端通栏摘要条 .project-mobile-summary(bottom:16+safe-area/z-11)占着,
     要靠一层层让位偏移躲开。
   搬进 footer 后两个问题一起消失,也不再需要 1536px 断点和移动端让位估算值。

   放在 footer **顶部**而不是底部:.project-mobile-summary 是 fixed 贴视口底部,
   而站点只给 <main> 里的 calculator/results 布局留了 padding-bottom: 120px、
   没给 footer 留。放 footer 末尾时实测被它压住(偏好条 739-804,摘要条 758-832),
   Warm 按钮下半截在 project 页永久点不到。 */
.footer-prefs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px 20px;
    margin-bottom: 28px;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--we-border);
}

/* 全部颜色走令牌,所以它自己也会跟着配色变。 */
.palette-toggle {
    min-height: 44px;
    padding: 10px 18px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-pill);
    background: var(--we-card);
    color: var(--we-text-2);
    font-family: var(--we-font-sans);
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
}

.palette-toggle:hover {
    color: var(--we-accent);
}

.palette-toggle[aria-pressed="true"] {
    border-color: var(--we-accent);
    color: var(--we-accent);
}

/* 语言快速切换。现居 footer 的 .footer-prefs 细条内(见上方注释)。
   SPEC: docs/superpowers/specs/2026-08-10-palette-language-toggle-design.md §5 */
.language-switch {
    display: flex;
    align-items: center;
    gap: 8px;
}

.language-switch a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding: 0 8px;
    color: var(--we-text-3);
    font-family: var(--we-font-sans);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.04em;
}

.language-switch a:hover {
    color: var(--we-accent);
}

.language-switch a.is-current {
    color: var(--we-text);
}

/* 反馈入口。与 .palette-toggle 同一套令牌,所以它会跟着配色方案变色。
   命名刻意避开 .app-feedback —— 那是右下角的 toast 提示条,是另一回事。
   SPEC: docs/superpowers/specs/2026-08-11-user-feedback-design.md §8 */
.feedback-entry {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding: 10px 18px;
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-pill);
    background: var(--we-card);
    color: var(--we-text-2);
    font-family: var(--we-font-sans);
    font-size: 0.82rem;
    font-weight: 600;
}

.feedback-entry:hover {
    color: var(--we-accent);
}

/* 反馈页的分类选择。每一项是"标题 + 一句说明",不用 <select> ——
   <option> 里塞不下两行文字,而只给分类名用户看不懂该选哪个。

   五项共用**一个**外框、内部用细分隔线,而不是五张各自带边框的卡片:
   后者在 840px 宽的表单里占掉近 400px 高,五道边框也显得碎。现在整体是
   一个列表,高度约为原来的 70%。
   SPEC: docs/superpowers/specs/2026-08-11-user-feedback-design.md §8.3 */
.feedback-choices {
    margin: 0 0 20px;
    padding: 0;
    border: 0;
}

/* legend 留在 fieldset 上、边框画在内层容器上 —— <legend> 会压在 fieldset
   自己的边框上,那样首行会被截出一个豁口。 */
.feedback-choices legend {
    margin-bottom: 10px;
    padding: 0;
    color: var(--we-text);
    font-family: var(--we-font-sans);
    font-size: 0.9rem;
    font-weight: 600;
}

.feedback-choice-list {
    border: 1px solid var(--we-border);
    border-radius: var(--we-radius-sm);
    background: var(--we-card);
    /* 让首尾两项的底色/强调条被容器圆角裁掉,不会顶出直角。 */
    overflow: hidden;
}

/* **选择器必须带上 .feedback-choice-list 这一层**,而且布局要用 grid 而不是 flex。
   两个坑都是全站既有规则造成的,实测才发现:

   1. `.feedback-choice` 本身是个 <label>,全站有
      `.input-panel label, .lead-panel label, .quote-form label { display: grid; gap: 6px; }`
      (site.css:1356)。它是 0,2,0,单靠 `.feedback-choice`(0,1,0)压不过它。
   2. 更要命的是裸元素规则 `input, select { width: 100%; min-height: 56px; ... }`
      (site.css:3286)对单选框同样生效 —— 实测那个圆点被撑成 807×56px,把文字
      挤成 85px 窄条疯狂换行,每项高达 207px。

   解法照站内既有的 `.consent-line`(site.css:6314):用 `grid-template-columns:
   20px 1fr` 把控件框在第一列里,`width:100%` 于是等于 20px,不必去对抗那条
   全站规则。 */
.feedback-choice-list .feedback-choice {
    display: grid;
    grid-template-columns: 20px 1fr;
    align-items: start;
    gap: 10px;
    padding: 11px 14px;
    border-top: 1px solid var(--we-border-row);
    /* 常态就占住 3px,选中时只换颜色 —— 否则选中会让整行左右跳一下。 */
    border-left: 3px solid transparent;
    cursor: pointer;
}

.feedback-choice-list .feedback-choice:first-child {
    border-top: 0;
}

.feedback-choice-list .feedback-choice:hover {
    background: var(--we-card-sub);
}

.feedback-choice-list .feedback-choice:has(input:checked) {
    border-left-color: var(--we-accent);
    background: var(--we-card-sub);
}

/* 键盘 Tab 到单选框时,整行要有可见的焦点提示 —— 原生 outline 只圈住那个
   小圆点,在一整行里很难注意到。 */
.feedback-choice-list .feedback-choice:has(input:focus-visible) {
    outline: 2px solid var(--we-accent);
    outline-offset: -2px;
}

/* 同 .consent-line input:把裸元素规则给的 56px 最小高度收回来。宽度不用管,
   grid 的 20px 列已经框住了。 */
.feedback-choice-list .feedback-choice input {
    min-height: 20px;
    margin-top: 3px;
    padding: 0;
    accent-color: var(--we-accent);
}

.feedback-choice-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.feedback-choice-label {
    color: var(--we-text);
    font-family: var(--we-font-sans);
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.4;
}

.feedback-choice-hint {
    color: var(--we-text-3);
    font-size: 0.78rem;
    line-height: 1.45;
}

/* 表单字段下方的一句说明。站内**没有**通用的 .field-hint 类,所以自带一个;
   不要写成 .field-hint —— 那个类不存在,写了就是一行没样式的裸文字。 */
.feedback-field-hint {
    display: block;
    margin-top: 6px;
    color: var(--we-text-3);
    font-size: 0.78rem;
    line-height: 1.5;
}

/* 来源提示块里的参数快照:可能很长,不能把 .notice 撑破。 */
.feedback-source-params {
    display: block;
    margin-top: 4px;
    overflow-wrap: anywhere;
    color: var(--we-text-3);
    font-size: 0.78rem;
}

/* 后台详情里的完整正文:保留用户敲的换行,同时不让长行撑破表格。 */
.feedback-detail-message {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: var(--we-font-sans);
    font-size: 0.85rem;
}

/* 蜜罐。离屏而不是 display:none —— 部分机器人会跳过 display:none 的字段。 */
.feedback-hp {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ---------------------------------------------------------------------------
   宽屏(>=1536px):两个控件从 footer 的位置**浮出**,回到最初的版式——
   ENG/ES 贴 header 右缘、Warm 贴右下角,两者同为 right: 20px,纵向同轴对齐。
   窄屏(<1536px):不加任何定位,老老实实待在 footer 的 .footer-prefs 细条里。

   为什么用 fixed 而不是复制一份标记到 header:
   标记只有一份、在 footer 里,CSS 没法把元素从 footer 搬进 header。复制会带来
   重复的 nav 地标、重复链接,以及两个同名 data-palette-toggle(JS 用
   querySelector 只会接上第一个)。而 footer 没有 transform / filter /
   backdrop-filter,不构成 fixed 的包含块,所以 fixed 直接相对视口定位,
   一份标记就能在两种版式间切换。
   (对照:.site-header 带 backdrop-filter,**是** fixed 的包含块——所以反过来
   把控件放在 header 里再 fixed 是行不通的,会被限制在 header 盒内。)

   为什么门槛是 1536px:header 通栏但 .site-shell 是 min(1240px, 100%-80px)
   居中,右侧空白 =(100vw - 1240)/2。切换器实测宽 86px + 20px 右边距 = 106px,
   再留 24px 不贴住 Get Quotes,需要 130px 空白 → 100vw >= 1500。取 1536 有
   42px 余量,且 1536 正好是 1920 屏 125% 缩放的逻辑宽度,这批用户能吃到。
   1440 只有 100px 空白,浮出去会压在 Get Quotes 上,所以退回 footer 版式。
   --------------------------------------------------------------------------- */
@media (min-width: 1536px) {
    /* 两个控件都脱离文档流后,这条细带只剩边框和留白,会在 footer 顶部留下一条
       空线,所以把它的装饰一并抹掉(display:flex 且无在流子元素 → 高度为 0)。
       不能用 display:none——那会连带隐藏 fixed 的子元素。 */
    .footer-prefs {
        margin-bottom: 0;
        padding-bottom: 0;
        border-bottom: 0;
    }

    /* 贴 header 右缘。header 高 56px(.nav-row 的 min-height),这里用等高盒 +
       align-items:center 精确纵向居中,不用 top:50% + translate 那种依赖自身
       高度的写法。z-index 要高于 .site-header 的 10 才压得住。 */
    .language-switch {
        position: fixed;
        top: 0;
        right: 20px;
        z-index: 11;
        height: 56px;
    }

    /* 回到右下角。让开 .app-feedback 提示条(right:20 / bottom:20 / z-index:30)
       所以抬到 84px;z-index 12 低于提示条但几何上本来就不重叠。
       与上面的 .language-switch 同为 right: 20px —— 这就是"纵向同轴对齐"。 */
    .palette-toggle {
        position: fixed;
        right: 20px;
        bottom: 84px;
        z-index: 12;
        box-shadow: var(--we-shadow-float);
    }

    /* 反馈入口浮到配色按钮上方。右下角三者纵向排布(都 right: 20px,同轴):
         .app-feedback 提示条  bottom: 20px  / z-index: 30
         .palette-toggle      bottom: 84px  / z-index: 12
         .feedback-entry      bottom: 140px / z-index: 12
       140px 的来由:配色按钮 84 + 自身高 44 = 顶边 128,再留 12px 间距。
       这个值是算出来的,必须在宽屏实机量过 —— 按钮实际高度受字体渲染影响。 */
    .feedback-entry {
        position: fixed;
        right: 20px;
        bottom: 140px;
        z-index: 12;
        box-shadow: var(--we-shadow-float);
    }
}

/* Plan actions: Save / Share / Export. The row is deliberately secondary - the filled
   "Get free quotes" CTA above it stays the only primary action on the card. */
.plan-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

@media (max-width: 960px) {
    .plan-share {
        position: relative;
        width:100%;
    }

    /* Carries over the narrow-screen sizing the pre-redesign action row had: without it
       the row has no narrow-screen layout at all and each action sits at content width,
       which reads as a ragged stack next to the full-width `.plan-share` wrapper above.
       Child combinator on purpose - `.plan-share-item` lives in the column-direction
       share panel, where a 180px basis would become a 180px row height. */
    .plan-actions > .button {
        width: auto;
        flex: 1 1 180px;
        min-width: 140px;
    }

    /* The Share trigger is nested inside `.plan-share`, so the rule above misses it;
       stretch it to match the actions on either side. */
    .plan-share-trigger {
        width: 100%;
    }
}
.plan-share {
    position: relative;
}

.plan-share-caret {
    margin-left: 0.25rem;
}

.plan-share-panel {
    position: absolute;
    /* One below .app-feedback's z-index: 30 lane (see note near its definition) —
       the panel must never cover the feedback toast, since it's the only
       fallback surface for failed copy/email actions. */
    z-index: 29;
    top: calc(100% + 0.5rem);
    left: 0;
    width: 20rem;
    max-width: calc(100vw - 2rem);
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    background: var(--we-surface, #ffffff);
    border: 1px solid var(--we-quote-outline, #d8d8d8);
    border-radius: 0.75rem;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.16);
}

.plan-share-panel[hidden] {
    display: none;
}

/* Scoped to .quote-cta-card, matching (0,2,0) specificity: these items are also
   `.button` inside that same card, and `.quote-cta-card .button { min-height: 0 }`
   (above) would otherwise win the cascade on desktop and collapse the 44px target. */
.quote-cta-card .plan-share-item {
    width: 100%;
    justify-content: flex-start;
    text-align: left;
    min-height: 2.75rem;
}

.plan-share-item[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

.plan-share-lock-note,
.plan-share-email-note {
    margin: 0;
    font-size: 0.8125rem;
    opacity: 0.75;
}

.plan-share-lock-note[hidden] {
    display: none;
}

.plan-share-email {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--we-quote-outline, #d8d8d8);
}

/* Was a <label> for the address input; the input is gone (the server picks the
   recipient), so it is a plain <p> now and needs the browser margin removed. */
.plan-share-email-label {
    margin: 0;
}

.plan-share-email-row {
    display: flex;
    gap: 0.375rem;
}

.plan-share-email-row .button {
    min-height: 2.75rem;
}

.plan-share-qr {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--we-quote-outline, #d8d8d8);
}

.plan-share-qr[hidden] {
    display: none;
}

.plan-share-qr-image svg {
    display: block;
    width: 5rem;
    height: 5rem;
}

.plan-save-status {
    flex: 1 0 100%;
    margin: 0.5rem 0 0;
    font-size: 0.875rem;
}

.plan-save-status[hidden] {
    display: none;
}

.plan-save-status-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    align-items: center;
    margin-top: 0.375rem;
}

.plan-save-status-row input {
    min-height: 2.75rem;
}

/* Scanning a code with the same phone that shows it is pointless, and the drawer needs the
   room, so the QR block is desktop-only. */
@media (max-width: 900px) {
    .plan-share-qr {
        display: none;
    }

    .plan-share-panel {
        position: fixed;
        top: auto;
        right: 0;
        bottom: 0;
        left: 0;
        width: auto;
        max-width: none;
        border-radius: 1rem 1rem 0 0;
        padding: 1rem;
    }
}

/* 卡片态的 3D 摘要页脚。视觉对齐 2D 预览卡片底部的 .preview-caption（site.css:4651）。
   .preview-surface 是 flex column，垂直 margin 不折叠：舞台已有 margin-bottom: 16px，
   16px 的间隔须单一由舞台下边距提供，页脚上边距须为 0，否则叠加成 30px。
   宽屏/全屏下由下面的规则隐藏，改由舞台内的 .three-stage-layer-summary 接手。 */
.three-stage-summary-footer {
    box-sizing: border-box;
    margin: 0 16px 16px;
    padding: 12px 14px;
    border-radius: 16px;
    border: 1px solid rgba(216, 207, 192, 0.84);
    background: rgba(255, 251, 246, 0.9);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
    color: rgba(23, 32, 51, 0.82);
    font-size: 0.88rem;
    line-height: 1.45;
}

.three-stage-summary-footer[hidden] {
    display: none;
}

/* 卡片态：页脚可见，舞台内覆盖层隐藏。 */
.preview-surface[data-preview-surface="three"]:not(.is-expanded):not(.is-fullscreen) .three-stage-layer-summary {
    display: none;
}

/* 宽屏 / 全屏：舞台内覆盖层接手，页脚隐藏。 */
.preview-surface.is-expanded .three-stage-summary-footer,
.preview-surface.is-fullscreen .three-stage-summary-footer {
    display: none;
}

@media (min-width: 721px) {
    .preview-surface[data-preview-surface="three"]:not(.is-expanded):not(.is-fullscreen) [data-three-stage] {
        min-height: var(--three-card-stage-min-height, 340px);
    }

    /* bathroom 的高舞台契约（site.css:1192-1194，clamp(440,42vw,540)）选择器优先级 (0,3,0)
       低于上面通用地板规则的 (0,5,0)，被盖过。这条选择器优先级 (0,7,0) 补回来，
       用 max() 同时满足地板测出来的值与 bathroom 自己的契约下限，两者取大。 */
    .calculator-layout[data-slug="bathroom-remodel"]
        .preview-surface[data-preview-surface="three"]:not(.is-expanded):not(.is-fullscreen) [data-three-stage] {
        min-height: max(var(--three-card-stage-min-height, 340px), clamp(440px, 42vw, 540px));
    }

    .calculator-layout[data-slug="garage-conversion-adu-builder"]
        .preview-surface[data-preview-surface="three"] .preview-surface-layer-slot .preview-stage-controls,
    .calculator-layout[data-slug="garage-conversion-adu-builder"]
        .preview-surface[data-preview-surface="three"] .preview-surface-layer-slot .preview-stage-control-row {
        width: 100%;
        min-width: 0;
    }

    .calculator-layout[data-slug="garage-conversion-adu-builder"]
        .preview-surface[data-preview-surface="three"] .preview-surface-layer-slot .preview-stage-control-row {
        justify-content: start;
        overflow-x: auto;
    }
}
