/* ===== injected styles ===== */ (function(){ if(document.getElementById('apa-bundle-css')) return; var s=document.createElement('style'); s.id='apa-bundle-css'; s.textContent="/* ============================================================\n All Pro Assemble — Design System\n Type: Bricolage Grotesque (display) + Hanken Grotesk (UI/body)\n Color: warm paper + warm ink + amber trades accent\n ============================================================ */\n\n:root {\n /* --- accent (overridable by Tweaks) — blue reference system --- */\n --accent: #1D6FF2; /* primary blue */\n --accent-strong: #1557C7;\n --accent-soft: #E9F1FE;\n --on-accent: #FFFFFF;\n\n /* --- neutrals: off-white paper + near-navy ink --- */\n --paper: #F8F7F4;\n --paper-2: #F1F0EB;\n --paper-3: #E7E6E0;\n --ink: #1A1A2E;\n --ink-2: #3A3B4F;\n --muted: #6B7280;\n --line: #E6E5DF;\n --line-2: #D4D3CC;\n\n /* dark surfaces */\n --night: #0F1117;\n --night-2: #1A1C24;\n --night-line: #2B2E38;\n --on-night: #F8F7F4;\n --on-night-muted: #9CA3AF;\n\n /* --- type --- */\n --font-display: 'Inter', system-ui, -apple-system, sans-serif;\n --font-body: 'Inter', system-ui, -apple-system, sans-serif;\n\n /* --- spacing / radius --- */\n --r-sm: 8px;\n --r: 12px; /* cards */\n --r-lg: 16px;\n --r-xl: 24px;\n --shadow-sm: 0 1px 2px oklch(0.2 0.02 70 / 0.06), 0 2px 6px oklch(0.2 0.02 70 / 0.05);\n --shadow: 0 4px 12px oklch(0.2 0.02 70 / 0.07), 0 12px 32px oklch(0.2 0.02 70 / 0.08);\n --shadow-lg: 0 8px 24px oklch(0.2 0.02 70 / 0.10), 0 28px 64px oklch(0.2 0.02 70 / 0.12);\n\n /* motion (scaled by Tweaks) */\n --motion: 1;\n}\n\n/* accent palettes set on */\nhtml[data-accent=\"blue\"] {\n --accent: #1D6FF2;\n --accent-strong: #1557C7;\n --accent-soft: #E9F1FE;\n --on-accent: #FFFFFF;\n}\nhtml[data-accent=\"green\"] {\n --accent: oklch(0.62 0.135 156);\n --accent-strong: oklch(0.54 0.145 158);\n --accent-soft: oklch(0.95 0.05 156);\n --on-accent: oklch(0.99 0.01 156);\n}\nhtml[data-accent=\"red\"] {\n --accent: oklch(0.60 0.185 25);\n --accent-strong: oklch(0.52 0.195 25);\n --accent-soft: oklch(0.95 0.045 30);\n --on-accent: oklch(0.99 0.01 25);\n}\n\n/* font pairing on */\nhtml[data-font=\"sharp\"] {\n --font-display: 'Space Grotesk', system-ui, sans-serif;\n --font-body: 'Hanken Grotesk', system-ui, sans-serif;\n}\nhtml[data-font=\"editorial\"] {\n --font-display: 'Fraunces', Georgia, serif;\n --font-body: 'Hanken Grotesk', system-ui, sans-serif;\n}\n\n* { box-sizing: border-box; }\nhtml { scroll-behavior: smooth; }\n@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }\n\nbody {\n margin: 0;\n font-family: var(--font-body);\n background: var(--paper);\n color: var(--ink);\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n line-height: 1.55;\n overflow-x: hidden;\n}\n\nh1, h2, h3, h4 {\n font-family: var(--font-display);\n font-weight: 700;\n line-height: 1.02;\n letter-spacing: -0.02em;\n margin: 0;\n text-wrap: balance;\n}\np { text-wrap: pretty; }\n\n::selection { background: var(--accent); color: var(--on-accent); }\n\na { color: inherit; text-decoration: none; }\nbutton { font-family: inherit; cursor: pointer; }\n\n.wrap { width: min(1240px, 92vw); margin-inline: auto; }\n.wrap-narrow { width: min(960px, 92vw); margin-inline: auto; }\n\n/* section rhythm */\nsection { position: relative; }\n.sec-pad { padding-block: clamp(72px, 9vw, 132px); }\n\n/* eyebrow label */\n.eyebrow {\n display: inline-flex; align-items: center; gap: 9px;\n font-family: var(--font-body);\n font-weight: 700; font-size: 12.5px;\n letter-spacing: 0.16em; text-transform: uppercase;\n color: var(--accent-strong);\n}\n.eyebrow::before {\n content: \"\"; width: 22px; height: 2px; border-radius: 2px;\n background: var(--accent);\n}\n.eyebrow.center::before { display: none; }\n\n.h-xxl { font-size: clamp(2.6rem, 6.4vw, 5.3rem); }\n.h-xl { font-size: clamp(2.1rem, 4.6vw, 3.6rem); }\n.h-lg { font-size: clamp(1.7rem, 3vw, 2.5rem); }\n.lead { font-size: clamp(1.05rem, 1.5vw, 1.28rem); color: var(--ink-2); line-height: 1.5; }\n\n/* ---------- buttons ---------- */\n.btn {\n display: inline-flex; align-items: center; justify-content: center; gap: 10px;\n border: none; border-radius: 999px;\n font-weight: 700; font-size: 1rem; letter-spacing: -0.01em;\n padding: 15px 26px;\n transition: transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .25s, background .2s, color .2s;\n white-space: nowrap;\n}\n.btn:active { transform: translateY(1px) scale(.99); }\n.btn-primary { background: var(--accent); color: var(--on-accent); box-shadow: 0 6px 18px rgba(29, 111, 242, 0.30); }\n.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(29, 111, 242, 0.42); background: var(--accent-strong); }\n.btn-ink { background: var(--ink); color: var(--paper); }\n.btn-ink:hover { transform: translateY(-2px); box-shadow: var(--shadow); }\n.btn-ghost { background: transparent; color: var(--ink); border: 1.5px solid var(--line-2); }\n.btn-ghost:hover { border-color: var(--ink); transform: translateY(-2px); }\n.btn-light { background: var(--paper); color: var(--ink); }\n.btn-light:hover { transform: translateY(-2px); box-shadow: var(--shadow); }\n.btn-lg { padding: 17px 32px; font-size: 1.06rem; }\n.btn-arrow svg { transition: transform .2s; }\n.btn-arrow:hover svg { transform: translateX(4px); }\n\n/* ---------- cards ---------- */\n.card {\n background: var(--paper);\n border: 1px solid var(--line);\n border-radius: var(--r);\n box-shadow: var(--shadow-sm);\n}\n\n/* ---------- placeholder image ---------- */\n.ph {\n position: relative; overflow: hidden;\n background:\n repeating-linear-gradient(135deg,\n oklch(0.90 0.012 80) 0 14px,\n oklch(0.93 0.010 82) 14px 28px);\n color: var(--muted);\n display: grid; place-items: center;\n}\n.ph::after {\n content: attr(data-label);\n font-family: 'Hanken Grotesk', monospace;\n font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;\n color: oklch(0.48 0.012 72);\n background: oklch(0.985 0.006 85 / 0.78);\n padding: 6px 12px; border-radius: 999px;\n border: 1px solid var(--line-2);\n backdrop-filter: blur(2px);\n text-align: center; max-width: 80%;\n}\n.ph.dark {\n background:\n repeating-linear-gradient(135deg,\n oklch(0.28 0.012 66) 0 14px,\n oklch(0.31 0.012 66) 14px 28px);\n}\n.ph.dark::after {\n color: var(--on-night-muted);\n background: oklch(0.22 0.012 66 / 0.7);\n border-color: var(--night-line);\n}\n\n/* ---------- chips / badges ---------- */\n.chip {\n display: inline-flex; align-items: center; gap: 8px;\n padding: 8px 14px; border-radius: 999px;\n font-weight: 600; font-size: 0.88rem;\n background: var(--paper-2); border: 1px solid var(--line);\n color: var(--ink-2);\n}\n.chip svg { color: var(--accent-strong); }\n\n/* ---------- reveal animation ----------\n Default = visible. Hidden state is \"armed\" only when the render loop is\n alive (html.anim, added via rAF). If rAF is frozen, content stays visible. */\n.reveal { opacity: 1; transform: none; }\nhtml.anim .reveal {\n opacity: 0;\n transform: translateY(calc(26px * var(--motion)));\n}\nhtml.anim .reveal.in {\n opacity: 1;\n transform: none;\n transition: opacity .7s cubic-bezier(.2,.7,.3,1), transform .7s cubic-bezier(.2,.7,.3,1);\n transition-delay: var(--reveal-delay, 0ms);\n}\n@media (prefers-reduced-motion: reduce) {\n html.anim .reveal { opacity: 1 !important; transform: none !important; }\n}\n\n/* utility */\n.stack { display: flex; flex-direction: column; }\n.row { display: flex; }\n.center { text-align: center; }\n.mx-auto { margin-inline: auto; }\n.grid { display: grid; }\n\n/* star */\n.stars { display: inline-flex; gap: 2px; color: var(--accent); }\n\n\n/* ============================================================\n components.css — section-specific styles\n ============================================================ */\n\n.ink-accent { color: var(--accent-strong); }\n\n/* ---------------- NAV ---------------- */\n.nav {\n position: fixed; inset: 0 0 auto 0; z-index: 100;\n transition: background .3s, box-shadow .3s, border-color .3s, backdrop-filter .3s;\n border-bottom: 1px solid transparent;\n}\n.nav-scrolled {\n background: oklch(0.985 0.006 85 / 0.82);\n backdrop-filter: blur(14px) saturate(1.4);\n border-bottom-color: var(--line);\n box-shadow: 0 6px 24px oklch(0.2 0.02 70 / 0.05);\n}\n.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 76px; gap: 24px; }\n.nav-links { display: flex; gap: 4px; }\n.nav-link {\n padding: 9px 14px; border-radius: 999px; font-weight: 600; font-size: 0.96rem;\n color: var(--ink-2); transition: background .2s, color .2s;\n}\n.nav-link:hover { background: var(--paper-3); color: var(--ink); }\n.nav-actions { display: flex; align-items: center; gap: 14px; }\n.nav-phone { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; font-size: 0.95rem; color: var(--ink); }\n.nav-phone svg { color: var(--accent-strong); }\n.nav-burger { display: none; background: none; border: none; color: var(--ink); padding: 6px; }\n.nav-mobile { display: none; }\n\n/* ---- mega-menu / dropdown (desktop) ---- */\n.nav-item { position: relative; display: flex; align-items: center; }\n.nav-trigger { background: none; border: none; display: inline-flex; align-items: center; gap: 5px; }\n.nav-caret { color: var(--muted); transition: transform .25s ease; }\n.nav-item.open .nav-caret { transform: rotate(180deg); color: var(--accent-strong); }\n.nav-item.open > .nav-trigger { background: var(--paper-3); color: var(--ink); }\n\n.mega, .dropdown {\n position: absolute; top: calc(100% + 8px); left: 0;\n background: var(--paper); border: 1px solid var(--line);\n border-radius: var(--r-lg); box-shadow: var(--shadow-lg);\n padding: 22px; z-index: 90;\n opacity: 0; transform: translateY(8px); pointer-events: none;\n transition: opacity .22s cubic-bezier(.2,.7,.3,1), transform .22s cubic-bezier(.2,.7,.3,1);\n}\n.nav-item.open .mega, .nav-item.open .dropdown { opacity: 1; transform: none; pointer-events: auto; }\n@media (prefers-reduced-motion: reduce) {\n .mega, .dropdown { transition: none; }\n}\n\n/* amber column header — matches the .eyebrow / \"WHAT WE DO\" label style */\n.mega-h {\n font-size: 0.72rem; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;\n color: var(--accent-strong); margin-bottom: 10px; padding: 0 12px;\n}\n.mega-cols { display: grid; grid-template-columns: repeat(3, minmax(190px, 220px)); gap: 26px; }\n.mega-col ul, .dropdown ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }\n\n/* main column + simple-menu links — amber left-border on hover */\n.mega-link, .dd-link {\n display: flex; align-items: center; gap: 9px;\n padding: 9px 12px; border-radius: 0 10px 10px 0;\n border-left: 2px solid transparent;\n font-weight: 600; font-size: 0.95rem; color: var(--ink-2);\n transition: background .15s, color .15s, border-color .15s;\n}\n.mega-link:hover, .dd-link:hover {\n background: var(--accent-soft); color: var(--accent-strong); border-left-color: var(--accent);\n}\n.dropdown { min-width: 240px; padding: 10px; }\n.dd-link svg { color: var(--accent-strong); flex: 0 0 auto; }\n\n/* Add-Ons strip — spans the bottom row, smaller + muted to separate it */\n.mega-services { padding-bottom: 18px; }\n.mega-addons { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }\n.addon-h { padding: 0 2px; margin-bottom: 12px; }\n.addon-row { display: flex; flex-wrap: wrap; gap: 6px 26px; }\n.addon-link {\n font-size: 0.85rem; font-weight: 600; color: var(--muted);\n padding: 4px 0; border-bottom: 2px solid transparent;\n transition: color .15s, border-color .15s;\n}\n.addon-link:hover { color: var(--accent-strong); border-bottom-color: var(--accent); }\n\n/* context-aware Services dropdown — focused on one category's sub-services */\n.mega-focused { min-width: 340px; max-width: 460px; padding-top: 14px; }\n.mega-back { display: inline-flex; align-items: center; gap: 7px; background: none; border: none; padding: 4px 12px 12px; font-weight: 700; font-size: 0.85rem; color: var(--muted); transition: color .15s; cursor: pointer; }\n.mega-back:hover { color: var(--accent-strong); }\n.mega-focused .mega-h { margin-bottom: 8px; }\n.mega-sub-list { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 18px; list-style: none; margin: 0; padding: 0; }\n@media (max-width: 520px) { .mega-sub-list { grid-template-columns: 1fr; } }\n.m-back { background: none; border: none; text-align: left; width: 100%; color: var(--muted); font-weight: 700; cursor: pointer; }\n.m-back:hover { color: var(--accent-strong); }\n\n/* Locations panel — states in 2 rows of 3, sub-areas as muted chips */\n.loc-panel { padding: 22px; }\n.loc-grid { display: grid; grid-template-columns: repeat(3, minmax(190px, 1fr)); gap: 22px 28px; }\n.loc-state-name {\n display: inline-flex; align-items: center; gap: 7px;\n font-family: var(--font-display); font-weight: 800; font-size: 1.02rem; color: var(--ink);\n margin-bottom: 9px; transition: color .15s;\n}\n.loc-state-name svg { color: var(--accent-strong); flex: 0 0 auto; }\n.loc-state-name:hover { color: var(--accent-strong); }\n.loc-chips { display: flex; flex-wrap: wrap; gap: 6px; }\n.loc-chip {\n font-size: 0.8rem; font-weight: 600; color: var(--ink-2);\n background: var(--paper-2); border: 1px solid var(--line);\n padding: 4px 10px; border-radius: 999px; transition: all .15s;\n}\n.loc-chip:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }\n\n@media (max-width: 1180px) { .nav-phone { display: none; } }\n\n@media (max-width: 940px) {\n .nav-links, .nav-phone { display: none; }\n .nav-cta { display: none; }\n .nav-burger { display: grid; place-items: center; }\n .nav-mobile {\n display: flex; flex-direction: column; gap: 2px;\n padding: 10px 6vw 22px; background: var(--paper);\n border-bottom: 1px solid var(--line); box-shadow: var(--shadow);\n max-height: calc(100vh - 76px); overflow-y: auto;\n }\n .nav-mobile-link { padding: 14px 12px; border-radius: 12px; font-weight: 700; font-size: 1.08rem; border-bottom: 1px solid var(--line); }\n .nav-mobile-link:hover { background: var(--paper-3); }\n\n /* mobile accordion */\n .m-acc { border-bottom: 1px solid var(--line); }\n .m-acc-head {\n width: 100%; display: flex; align-items: center; justify-content: space-between;\n background: none; border: none; padding: 14px 12px;\n font-family: var(--font-display); font-weight: 700; font-size: 1.08rem; color: var(--ink);\n }\n .m-acc-head svg { color: var(--muted); transition: transform .25s ease; flex: 0 0 auto; }\n .m-acc.open .m-acc-head svg { transform: rotate(180deg); color: var(--accent-strong); }\n .m-acc-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .3s ease; }\n .m-acc.open .m-acc-body { grid-template-rows: 1fr; }\n .m-acc-inner { overflow: hidden; }\n .m-group { padding: 2px 10px 12px; }\n .m-group-h {\n font-size: 0.7rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase;\n color: var(--accent-strong); margin: 12px 0 4px;\n }\n .m-sublink { display: block; padding: 9px 12px; border-radius: 9px; font-weight: 600; font-size: 1rem; color: var(--ink-2); }\n .m-sublink:hover { background: var(--paper-3); }\n\n /* mobile level-2 nested accordion (Services groups / Location states) */\n .m-acc-inner { padding: 4px 8px 10px; }\n .m-acc2 { border-top: 1px solid var(--line); }\n .m-acc2:first-child { border-top: none; }\n .m-acc2-head {\n width: 100%; display: flex; align-items: center; justify-content: space-between;\n background: none; border: none; padding: 12px 8px;\n font-weight: 800; font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase;\n color: var(--accent-strong);\n }\n .m-acc2-head svg { color: var(--muted); transition: transform .25s ease; flex: 0 0 auto; }\n .m-acc2.open .m-acc2-head svg { transform: rotate(180deg); color: var(--accent-strong); }\n .m-acc2-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s ease; }\n .m-acc2.open .m-acc2-body { grid-template-rows: 1fr; }\n .m-acc2-inner { overflow: hidden; padding-bottom: 6px; }\n\n .m-phone {\n display: inline-flex; align-items: center; gap: 9px;\n padding: 16px 12px 8px; font-weight: 700; font-size: 1.05rem; color: var(--ink);\n }\n .m-phone svg { color: var(--accent-strong); }\n}\n\n/* ---------------- HERO shared ---------------- */\n.hero { padding-top: 116px; }\n.hero-cta-row { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 30px; }\n.hero-cta-row.center { justify-content: center; }\n.hero-proof { display: flex; flex-wrap: wrap; gap: 18px 26px; margin-top: 34px; }\n.hero-proof-item { display: inline-flex; align-items: center; gap: 9px; font-weight: 600; font-size: 0.95rem; color: var(--ink-2); }\n.hero-proof-item svg { color: var(--accent-strong); }\n\n.rating-pill {\n display: inline-flex; align-items: center; gap: 10px;\n padding: 10px 16px; border-radius: 999px;\n background: var(--paper); border: 1px solid var(--line); box-shadow: var(--shadow-sm);\n font-size: 0.92rem; color: var(--ink-2);\n}\n.rating-pill strong { color: var(--ink); }\n.rating-pill.on-dark { background: oklch(1 0 0 / 0.1); border-color: oklch(1 0 0 / 0.18); color: var(--on-night-muted); backdrop-filter: blur(6px); }\n.rating-pill.on-dark strong { color: var(--on-night); }\n\n/* HERO A — split */\n.hero-split-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 56px; align-items: center; padding-block: 40px 80px; }\n.hero-split .h-xxl { letter-spacing: -0.035em; }\n.hero-media { position: relative; }\n.hero-media-main {\n position: relative; aspect-ratio: 4/4.4; border-radius: var(--r-xl);\n overflow: hidden; box-shadow: var(--shadow-lg);\n transform: rotate(1.4deg);\n}\n.hero-media-card {\n position: absolute; left: -28px; bottom: 64px; padding: 8px;\n border-radius: 999px; transform: rotate(-2deg); animation: floaty 6s ease-in-out infinite;\n}\n.hero-media-badge {\n position: absolute; right: -16px; top: 40px;\n display: flex; align-items: center; gap: 12px;\n background: var(--ink); color: var(--paper);\n padding: 14px 18px; border-radius: var(--r); box-shadow: var(--shadow-lg);\n transform: rotate(2.5deg); animation: floaty 7s ease-in-out infinite reverse;\n}\n.hero-media-badge > svg { flex: 0 0 auto; background: var(--accent); color: var(--on-accent); border-radius: 8px; padding: 5px; box-sizing: content-box; }\n.hero-media-badge strong { display: block; font-size: 1.2rem; font-family: var(--font-display); line-height: 1; }\n.hero-media-badge span { font-size: 0.8rem; color: oklch(0.78 0.01 80); }\n@keyframes floaty { 0%,100% { translate: 0 0; } 50% { translate: 0 calc(-9px * var(--motion)); } }\n@media (prefers-reduced-motion: reduce) { .hero-media-card, .hero-media-badge { animation: none; } }\n\n/* HERO B — full bleed */\n.hero-full { padding-top: 0; min-height: 92vh; display: grid; place-items: center; position: relative; overflow: hidden; }\n.hero-full-scrim { position: absolute; inset: 0; background:\n radial-gradient(120% 90% at 50% 0%, oklch(0.2 0.012 65 / 0.45), oklch(0.18 0.012 65 / 0.86)); }\n.hero-full-inner { position: relative; text-align: center; display: flex; flex-direction: column; align-items: center; padding-block: 140px 90px; }\n.hero-full .h-xxl { letter-spacing: -0.04em; }\n\n/* HERO C — bold */\n.hero-bold { padding-top: 130px; padding-bottom: 40px; }\n.hero-bold-title {\n font-size: clamp(3.2rem, 12vw, 10rem); line-height: 0.92; letter-spacing: -0.04em;\n margin-top: 12px; text-transform: uppercase; font-weight: 800;\n}\n.hero-bold-grid {\n display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: auto auto;\n gap: 18px; margin-top: 30px;\n}\n.hero-bold-media { position: relative; border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow); min-height: 200px; }\n.hero-bold-media.a { grid-row: span 2; min-height: 360px; }\n.hero-bold-media.b { aspect-ratio: 16/10; }\n.hero-bold-media.c { aspect-ratio: 16/10; }\n.hero-bold-copy { display: flex; flex-direction: column; justify-content: center; }\n\n/* ---------------- STATS ---------------- */\n.stats-strip { background: var(--ink); color: var(--paper); }\n.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; padding-block: 40px; }\n.stat-item { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 6px; position: relative; }\n.stat-item:not(:last-child)::after { content: \"\"; position: absolute; right: -12px; top: 14%; height: 72%; width: 1px; background: oklch(1 0 0 / 0.12); }\n.stat-ico { color: var(--accent); margin-bottom: 4px; }\n.stat-num { font-family: var(--font-display); font-weight: 800; font-size: clamp(1.9rem, 3.6vw, 2.9rem); line-height: 1; letter-spacing: -0.02em; }\n.stat-label { font-size: 0.92rem; color: oklch(0.78 0.01 80); }\n@media (max-width: 680px) {\n .stats-grid { grid-template-columns: 1fr 1fr; gap: 30px 16px; }\n .stat-item:nth-child(2)::after { display: none; }\n}\n\n/* ---------------- SECTION HEAD ---------------- */\n.sec-head { display: grid; grid-template-columns: 1.4fr 1fr; gap: 32px; align-items: end; margin-bottom: 54px; }\n.sec-head-sub { max-width: 420px; align-self: end; }\n@media (max-width: 760px) { .sec-head { grid-template-columns: 1fr; gap: 18px; margin-bottom: 40px; } }\n\n/* ---------------- SERVICES ---------------- */\n.svc-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }\n.svc-card { position: relative; padding: 26px 24px 22px; display: flex; flex-direction: column; transition: transform .25s cubic-bezier(.2,.7,.3,1), box-shadow .25s, border-color .25s; overflow: hidden; }\n.svc-card::before { content: \"\"; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform .3s; }\n.svc-card:hover { transform: translateY(-6px); box-shadow: var(--shadow); border-color: var(--line-2); }\n.svc-card:hover::before { transform: scaleX(1); }\n.svc-tag { position: absolute; top: 16px; right: 16px; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent-strong); background: var(--accent-soft); padding: 5px 10px; border-radius: 999px; }\n.svc-ico { width: 56px; height: 56px; border-radius: 16px; background: var(--accent-soft); color: var(--accent-strong); display: grid; place-items: center; margin-bottom: 18px; transition: background .25s, color .25s; }\n.svc-card:hover .svc-ico { background: var(--accent); color: var(--on-accent); }\n.svc-name { font-size: 1.16rem; letter-spacing: -0.02em; }\n.svc-desc { font-size: 0.94rem; color: var(--muted); margin: 9px 0 18px; flex: 1; line-height: 1.5; }\n.svc-foot { display: flex; align-items: center; justify-content: space-between; padding-top: 14px; border-top: 1px solid var(--line); }\n.svc-from { font-size: 0.9rem; color: var(--muted); }\n.svc-from strong { color: var(--ink); font-size: 1.05rem; }\n.svc-link { display: inline-flex; align-items: center; gap: 5px; background: none; border: none; font-weight: 700; color: var(--ink); padding: 6px 0; transition: gap .2s, color .2s; }\n.svc-link:hover { color: var(--accent-strong); gap: 9px; }\n@media (max-width: 960px) { .svc-grid { grid-template-columns: 1fr 1fr; } }\n@media (max-width: 540px) { .svc-grid { grid-template-columns: 1fr; } }\n\n.svc-banner {\n margin-top: 22px; background: var(--night); color: var(--on-night);\n border-radius: var(--r-lg); padding: 34px 38px;\n display: flex; align-items: center; justify-content: space-between; gap: 28px; flex-wrap: wrap;\n position: relative; overflow: hidden;\n}\n.svc-banner::after { content: \"\"; position: absolute; right: -60px; top: -60px; width: 220px; height: 220px; border-radius: 50%; background: radial-gradient(circle, rgba(29, 111, 242, 0.35), transparent 70%); }\n\n/* ---------------- PROCESS ---------------- */\n.process { background: var(--paper-2); }\n.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; margin-top: 58px; position: relative; }\n.steps-line { position: absolute; top: 28px; left: 12%; right: 12%; height: 2px; background: repeating-linear-gradient(90deg, var(--line-2) 0 8px, transparent 8px 16px); }\n.step { position: relative; text-align: center; display: flex; flex-direction: column; align-items: center; }\n.step-num { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: var(--on-accent); font-family: var(--font-display); font-weight: 800; font-size: 0.95rem; display: grid; place-items: center; margin-bottom: 18px; box-shadow: 0 0 0 6px var(--paper-2); z-index: 1; }\n.step-ico { width: 64px; height: 64px; border-radius: 20px; background: var(--paper); border: 1px solid var(--line); color: var(--ink); display: grid; place-items: center; margin-bottom: 18px; box-shadow: var(--shadow-sm); }\n.step-title { font-size: 1.2rem; }\n.step-desc { font-size: 0.94rem; color: var(--muted); margin-top: 9px; line-height: 1.5; max-width: 24ch; }\n@media (max-width: 860px) { .steps { grid-template-columns: 1fr 1fr; gap: 36px 18px; } .steps-line { display: none; } }\n@media (max-width: 460px) { .steps { grid-template-columns: 1fr; } }\n\n/* ---------------- QUOTE ---------------- */\n.quote-sec { background: var(--paper-2); }\n\n/* wizard */\n.wizard { overflow: hidden; }\n.wizard-head { padding: 26px 32px; border-bottom: 1px solid var(--line); background: var(--paper); }\n.wizard-progress { display: flex; gap: 8px; }\n.wz-dot { flex: 1; display: flex; align-items: center; gap: 10px; opacity: 0.5; transition: opacity .3s; }\n.wz-dot.active, .wz-dot.done { opacity: 1; }\n.wz-dot-n { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 0.9rem; background: var(--paper-3); color: var(--ink-2); flex: 0 0 auto; transition: background .3s, color .3s; }\n.wz-dot.active .wz-dot-n { background: var(--accent); color: var(--on-accent); }\n.wz-dot.done .wz-dot-n { background: var(--ink); color: var(--paper); }\n.wz-dot-l { font-weight: 700; font-size: 0.92rem; }\n@media (max-width: 640px) { .wz-dot-l { display: none; } .wz-dot { flex: 0 0 auto; } .wizard-progress { gap: 12px; } }\n\n.wizard-body { padding: 38px 32px; min-height: 340px; }\n.wz-pane { animation: fadeUp .4s cubic-bezier(.2,.7,.3,1); }\n@keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }\n.wz-q { font-size: 1.5rem; letter-spacing: -0.02em; margin-bottom: 24px; }\n\n.wz-svc-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }\n.wz-svc { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 20px 12px; border-radius: var(--r); border: 1.5px solid var(--line); background: var(--paper); color: var(--ink-2); font-weight: 600; font-size: 0.9rem; text-align: center; transition: all .2s; }\n.wz-svc:hover { border-color: var(--line-2); transform: translateY(-2px); }\n.wz-svc.sel { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); box-shadow: 0 0 0 3px var(--accent-soft); }\n@media (max-width: 720px) { .wz-svc-grid { grid-template-columns: 1fr 1fr; } }\n\n.wz-field { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--line); }\n.wz-field.col { flex-direction: column; align-items: stretch; gap: 12px; }\n.wz-field label { font-weight: 700; font-size: 1.02rem; }\n.wz-field.col > label { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); font-weight: 800; }\n\n.stepper { display: inline-flex; align-items: center; gap: 4px; background: var(--paper-3); border-radius: 999px; padding: 4px; }\n.stepper button { width: 38px; height: 38px; border-radius: 50%; border: none; background: var(--paper); color: var(--ink); display: grid; place-items: center; box-shadow: var(--shadow-sm); transition: background .15s, transform .15s; }\n.stepper button:hover { background: var(--accent); color: var(--on-accent); }\n.stepper button:active { transform: scale(.92); }\n.stepper-val { min-width: 34px; text-align: center; font-family: var(--font-display); font-weight: 800; font-size: 1.3rem; }\n\n.wz-addons { display: grid; gap: 10px; }\n.wz-addon { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: var(--r-sm); border: 1.5px solid var(--line); background: var(--paper); transition: all .18s; text-align: left; }\n.wz-addon:hover { border-color: var(--line-2); }\n.wz-addon.sel { border-color: var(--accent); background: var(--accent-soft); }\n.wz-check { width: 22px; height: 22px; border-radius: 7px; border: 2px solid var(--line-2); display: grid; place-items: center; color: var(--on-accent); flex: 0 0 auto; transition: all .15s; }\n.wz-addon.sel .wz-check { background: var(--accent); border-color: var(--accent); }\n.wz-addon-name { flex: 1; font-weight: 600; font-size: 0.96rem; }\n.wz-addon-price { font-weight: 800; color: var(--ink); }\n\n.wz-urg { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n.wz-urg-opt { display: flex; flex-direction: column; gap: 3px; padding: 14px; border-radius: var(--r-sm); border: 1.5px solid var(--line); background: var(--paper); text-align: left; transition: all .18s; }\n.wz-urg-opt.sm { padding: 11px 12px; }\n.wz-urg-opt:hover { border-color: var(--line-2); }\n.wz-urg-opt.sel { border-color: var(--accent); background: var(--accent-soft); }\n.wz-urg-opt strong { font-size: 0.98rem; }\n.wz-urg-opt span { font-size: 0.8rem; color: var(--muted); }\n\n.wz-live, .wz-summary { margin-top: 22px; padding: 18px 20px; background: var(--paper-2); border: 1px dashed var(--line-2); border-radius: var(--r); }\n.wz-summary-row { display: flex; justify-content: space-between; font-weight: 700; font-size: 0.95rem; margin-bottom: 12px; color: var(--ink-2); }\n\n.wz-inputs { display: grid; gap: 12px; }\n.inp { width: 100%; padding: 14px 16px; border-radius: var(--r-sm); border: 1.5px solid var(--line); background: var(--paper); font-family: inherit; font-size: 1rem; color: var(--ink); transition: border-color .18s, box-shadow .18s; resize: vertical; }\n.inp:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }\n.inp::placeholder { color: var(--muted); }\n\n.est-readout { text-align: center; }\n.est-readout.compact { text-align: left; }\n.est-label { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 800; color: var(--muted); }\n.est-range { display: flex; align-items: baseline; gap: 8px; justify-content: center; margin: 4px 0 2px; }\n.est-readout.compact .est-range { justify-content: flex-start; }\n.est-amt { font-family: var(--font-display); font-weight: 800; font-size: 2rem; letter-spacing: -0.02em; color: var(--ink); }\n.est-dash { color: var(--muted); }\n.est-note { font-size: 0.82rem; color: var(--muted); }\n\n.wz-done { text-align: center; display: flex; flex-direction: column; align-items: center; padding-top: 10px; }\n.wz-done-ico { width: 76px; height: 76px; border-radius: 50%; background: var(--accent-soft); color: var(--accent-strong); display: grid; place-items: center; margin-bottom: 22px; animation: pop .5s cubic-bezier(.2,1.5,.4,1); }\n@keyframes pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }\n.wz-done-est { margin: 26px 0 20px; padding: 20px 32px; background: var(--paper-2); border-radius: var(--r); border: 1px solid var(--line); }\n.wz-done-meta { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }\n\n.wizard-foot { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 20px 32px; border-top: 1px solid var(--line); background: var(--paper); }\n.wizard-foot.center-foot { justify-content: center; }\n.wizard-foot-est { font-size: 0.95rem; color: var(--muted); }\n.wizard-foot-est strong { color: var(--ink); font-size: 1.05rem; }\n@media (max-width: 520px) { .wizard-foot-est { display: none; } .wizard-body { padding: 28px 20px; } .wizard-head, .wizard-foot { padding-inline: 20px; } }\n\n/* estimator */\n.estimator { display: grid; grid-template-columns: 1.5fr 1fr; overflow: hidden; }\n.est-left { padding: 34px; display: flex; flex-direction: column; gap: 24px; }\n.est-block { display: flex; flex-direction: column; gap: 12px; }\n.est-blabel { font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 800; color: var(--muted); }\n.est-svc-chips { display: flex; flex-wrap: wrap; gap: 8px; }\n.est-chip { display: inline-flex; align-items: center; gap: 7px; padding: 10px 14px; border-radius: 999px; border: 1.5px solid var(--line); background: var(--paper); font-weight: 600; font-size: 0.9rem; color: var(--ink-2); transition: all .18s; }\n.est-chip:hover { border-color: var(--line-2); }\n.est-chip.sel { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); }\n.est-row2 { display: grid; grid-template-columns: auto 1fr; gap: 28px; align-items: start; }\n.est-right { background: var(--ink); color: var(--paper); padding: 34px; display: flex; flex-direction: column; gap: 20px; }\n.est-right-top { display: flex; flex-direction: column; gap: 6px; }\n.est-svc-pill { display: inline-flex; align-items: center; gap: 7px; align-self: flex-start; padding: 7px 13px; border-radius: 999px; background: oklch(1 0 0 / 0.1); font-weight: 600; font-size: 0.85rem; color: var(--accent); }\n.est-big { font-family: var(--font-display); font-weight: 800; font-size: 3rem; letter-spacing: -0.03em; line-height: 1; margin-top: 6px; }\n.est-big span { font-size: 1.6rem; color: oklch(0.78 0.01 80); }\n.est-sub { font-size: 0.9rem; color: oklch(0.78 0.01 80); margin: 0; }\n.est-incl { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 11px; border-top: 1px solid oklch(1 0 0 / 0.12); padding-top: 20px; }\n.est-incl li { display: flex; align-items: center; gap: 10px; font-size: 0.95rem; color: oklch(0.88 0.006 85); }\n.est-incl svg { color: var(--accent); flex: 0 0 auto; }\n.est-fine { font-size: 0.8rem; color: oklch(0.7 0.01 80); text-align: center; }\n@media (max-width: 820px) { .estimator { grid-template-columns: 1fr; } .est-row2 { grid-template-columns: 1fr; gap: 24px; } }\n\n/* ---------------- AREAS ---------------- */\n.areas-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; align-items: stretch; }\n.areas-map { position: relative; border-radius: var(--r-lg); overflow: hidden; min-height: 460px; }\n.map-pins { position: absolute; inset: 0; }\n.map-pin { position: absolute; display: inline-flex; align-items: center; gap: 5px; padding: 7px 12px 7px 9px; border-radius: 999px; background: var(--paper); border: 1px solid var(--line); box-shadow: var(--shadow); font-weight: 800; font-size: 0.82rem; color: var(--ink); cursor: pointer; transition: all .22s cubic-bezier(.2,.7,.3,1); }\n.map-pin svg { color: var(--accent-strong); }\n.map-pin.on { background: var(--accent); color: var(--on-accent); transform: scale(1.12); box-shadow: 0 8px 22px rgba(29, 111, 242, 0.4); }\n.map-pin.on svg { color: var(--on-accent); }\n.pin-DC { left: 46%; top: 58%; } .pin-MD { left: 58%; top: 40%; } .pin-VA { left: 30%; top: 70%; }\n.pin-PA { left: 64%; top: 18%; } .pin-DE { left: 76%; top: 46%; }\n.map-tag { position: absolute; left: 18px; bottom: 18px; display: inline-flex; align-items: center; gap: 8px; padding: 10px 15px; border-radius: 999px; background: var(--ink); color: var(--paper); font-weight: 600; font-size: 0.85rem; }\n.map-tag svg { color: var(--accent); }\n\n.areas-list { display: flex; flex-direction: column; gap: 10px; }\n.area-row { display: flex; align-items: center; gap: 16px; padding: 18px 20px; border-radius: var(--r); border: 1px solid var(--line); background: var(--paper); transition: all .22s; cursor: default; }\n.area-row.on { border-color: var(--accent); background: var(--accent-soft); box-shadow: var(--shadow-sm); }\n.area-abbr { width: 46px; height: 46px; border-radius: 12px; background: var(--ink); color: var(--paper); display: grid; place-items: center; font-family: var(--font-display); font-weight: 800; font-size: 1rem; flex: 0 0 auto; transition: background .22s; }\n.area-row.on .area-abbr { background: var(--accent); color: var(--on-accent); }\n.area-state { font-weight: 800; font-size: 1.08rem; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }\n.area-note { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent-strong); background: var(--paper); padding: 3px 8px; border-radius: 999px; }\n.area-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }\n.area-chip { font-size: 0.82rem; color: var(--ink-2); background: var(--paper-2); border: 1px solid var(--line); padding: 4px 10px; border-radius: 999px; }\n.area-row.on .area-chip { background: var(--paper); }\n.area-body { flex: 1; }\n.area-chev { color: var(--muted); flex: 0 0 auto; transition: transform .22s; transform: rotate(-90deg); }\n.area-cta { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 18px 20px; margin-top: 6px; border-radius: var(--r); background: var(--paper-2); border: 1px dashed var(--line-2); font-weight: 600; color: var(--ink-2); }\n@media (max-width: 820px) { .areas-grid { grid-template-columns: 1fr; } .areas-map { min-height: 320px; } }\n\n/* ---------------- GALLERY ---------------- */\n.gallery { background: var(--paper-2); }\n.gallery-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 20px; }\n.gallery-ba { border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow); }\n/* width+height:100% fill .gallery-ba (a stretched grid item) so its\n overflow:hidden + border-radius clip all four corners and no bottom gap remains.\n Both dimensions definite => aspect-ratio is ignored here (avoiding it computing\n an over-wide width from the height); aspect-ratio is the fallback only when the\n wrapper height is auto (mobile single-column). */\n.ba { position: relative; aspect-ratio: 16/11; width: 100%; height: 100%; user-select: none; cursor: ew-resize; touch-action: pan-y; --ba-w: 100%; }\n.ba > .ph:first-child { width: 100%; }\n.ba-before { position: absolute; inset: 0; overflow: hidden; }\n.ba-before .ph { width: max(100%, 60vw); height: 100%; }\n.ba-handle { position: absolute; top: 0; bottom: 0; width: 3px; background: var(--paper); transform: translateX(-50%); box-shadow: 0 0 0 1px oklch(0.2 0.02 70 / 0.1); }\n.ba-knob { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 46px; height: 46px; border-radius: 50%; background: var(--paper); color: var(--ink); display: grid; grid-auto-flow: column; place-items: center; gap: 0; box-shadow: var(--shadow); }\n.ba-tag { position: absolute; top: 14px; padding: 6px 12px; border-radius: 999px; font-size: 0.78rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; background: oklch(0.2 0.02 70 / 0.6); color: var(--paper); backdrop-filter: blur(4px); }\n.ba-tag-l { left: 14px; } .ba-tag-r { right: 14px; background: var(--accent); color: var(--on-accent); }\n.gallery-thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }\n.gallery-thumb { position: relative; aspect-ratio: 1; border-radius: var(--r); overflow: hidden; box-shadow: var(--shadow-sm); transition: transform .25s; }\n.gallery-thumb:hover { transform: scale(1.02); }\n@media (max-width: 820px) { .gallery-grid { grid-template-columns: 1fr; } }\n\n/* ---------------- REVIEWS ---------------- */\n.reviews-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 30px; flex-wrap: wrap; margin-bottom: 50px; }\n.reviews-badges { display: flex; gap: 14px; }\n.rev-badge { display: flex; flex-direction: column; gap: 4px; padding: 14px 20px; border-radius: var(--r); background: var(--paper); border: 1px solid var(--line); box-shadow: var(--shadow-sm); }\n.rev-badge strong { font-family: var(--font-display); font-size: 1.2rem; }\n.rev-badge span { font-size: 0.8rem; color: var(--muted); }\n.reviews-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; align-items: stretch; }\n.rev-card { margin-bottom: 0; display: flex; flex-direction: column; padding: 26px 24px; position: relative; }\n.rev-quote { position: absolute; top: 22px; right: 22px; color: var(--line-2); }\n.rev-text { font-size: 1rem; line-height: 1.55; color: var(--ink-2); margin: 14px 0 20px; flex: 1; }\n.rev-foot { display: flex; align-items: center; gap: 12px; }\n.rev-ava { width: 44px; height: 44px; border-radius: 50%; overflow: hidden; position: relative; flex: 0 0 auto; }\n.rev-name { font-weight: 800; font-size: 0.96rem; }\n.rev-loc { font-size: 0.84rem; color: var(--muted); }\n.rev-svc { margin-left: auto; font-size: 0.74rem; font-weight: 700; color: var(--accent-strong); background: var(--accent-soft); padding: 5px 10px; border-radius: 999px; text-align: center; }\n@media (max-width: 960px) { .reviews-grid { grid-template-columns: repeat(2, 1fr); } }\n@media (max-width: 600px) { .reviews-grid { grid-template-columns: 1fr; } }\n\n/* ---------------- FAQ ---------------- */\n.faq-grid { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: 48px; align-items: start; }\n.faq-aside { position: sticky; top: 100px; }\n.faq-call { display: inline-flex; align-items: center; gap: 10px; margin-top: 24px; font-family: var(--font-display); font-weight: 800; font-size: 1.5rem; color: var(--ink); }\n.faq-call svg { color: var(--accent-strong); }\n.faq-list { display: flex; flex-direction: column; gap: 12px; }\n.faq-item { border: 1px solid var(--line); border-radius: var(--r); background: var(--paper); overflow: hidden; transition: border-color .2s, box-shadow .2s; }\n.faq-item.open { border-color: var(--line-2); box-shadow: var(--shadow-sm); }\n.faq-q { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 22px 24px; background: none; border: none; text-align: left; font-family: var(--font-display); font-weight: 700; font-size: 1.12rem; letter-spacing: -0.01em; color: var(--ink); }\n.faq-icon { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; background: var(--paper-3); display: grid; place-items: center; transition: background .2s, color .2s; }\n.faq-item.open .faq-icon { background: var(--accent); color: var(--on-accent); }\n.faq-a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .32s cubic-bezier(.4,0,.2,1); }\n.faq-a-inner { overflow: hidden; }\n.faq-a-inner p { margin: 0; padding: 0 24px 24px; color: var(--muted); line-height: 1.6; max-width: 60ch; }\n@media (max-width: 760px) { .faq-grid { grid-template-columns: 1fr; gap: 28px; } .faq-aside { position: static; } }\n\n/* ---------------- FINAL CTA ---------------- */\n.final-cta { padding-block: clamp(40px, 6vw, 80px); }\n.final-card { position: relative; overflow: hidden; background: var(--night); border-radius: var(--r-xl); padding: clamp(48px, 7vw, 88px) 32px; text-align: center; }\n.final-glow { position: absolute; inset: 0; background: radial-gradient(60% 80% at 50% 120%, rgba(29, 111, 242, 0.4), transparent 70%); pointer-events: none; }\n.final-card > * { position: relative; }\n\n/* ---------------- FOOTER ---------------- */\n.footer { background: var(--night); color: var(--on-night); padding-block: 64px 28px; }\n.footer-top { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 40px; padding-bottom: 44px; border-bottom: 1px solid var(--night-line); }\n.footer-blurb { color: var(--on-night-muted); font-size: 0.95rem; max-width: 32ch; margin: 20px 0; line-height: 1.6; }\n.footer-contact { display: flex; flex-direction: column; gap: 10px; }\n.footer-contact a, .footer-contact span { display: inline-flex; align-items: center; gap: 10px; color: var(--on-night-muted); font-size: 0.94rem; transition: color .2s; }\n.footer-contact a:hover { color: var(--on-night); }\n.footer-contact svg { color: var(--accent); }\n.footer-col h4 { font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--on-night); margin-bottom: 16px; }\n.footer-col ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }\n.footer-col a { color: var(--on-night-muted); font-size: 0.92rem; transition: color .2s; }\n.footer-col a:hover { color: var(--accent); }\n.footer-bottom { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; padding-top: 24px; }\n.footer-bottom > span { color: var(--on-night-muted); font-size: 0.85rem; }\n.footer-legal { display: flex; align-items: center; gap: 22px; }\n.footer-legal a { color: var(--on-night-muted); font-size: 0.85rem; }\n.footer-legal a:hover { color: var(--on-night); }\n@media (max-width: 860px) { .footer-top { grid-template-columns: 1fr 1fr; gap: 32px; } }\n@media (max-width: 520px) { .footer-top { grid-template-columns: 1fr; } }\n\n/* ============================================================\n SERVICE PAGE (service-page.jsx) — tokens only, no new colors\n ============================================================ */\n\n/* breadcrumb — small muted text, amber separator */\n.crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; font-size: 0.85rem; font-weight: 600; color: var(--muted); }\n.crumbs a { color: var(--muted); transition: color .15s; }\n.crumbs a:hover { color: var(--accent-strong); }\n.crumbs .sep { color: var(--accent); }\n.crumbs .current { color: var(--ink-2); }\n\n/* pricing cards — reuse .card; layout + amber feature accent via tokens */\n.price-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; align-items: stretch; }\n.price-card { position: relative; padding: 30px 28px; display: flex; flex-direction: column; gap: 6px; }\n.price-card.feat { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow); }\n.price-feat-tag { position: absolute; top: 16px; right: 16px; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent-strong); background: var(--accent-soft); padding: 5px 10px; border-radius: 999px; }\n.price-tag { font-size: 0.8rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-strong); }\n.price-amt { font-family: var(--font-display); font-weight: 800; font-size: 2.4rem; letter-spacing: -0.02em; line-height: 1; color: var(--ink); margin-top: 2px; }\n.price-from { font-size: 0.92rem; font-weight: 700; color: var(--muted); }\n.price-desc { font-size: 0.95rem; color: var(--muted); margin: 6px 0 0; line-height: 1.5; }\n@media (max-width: 780px) { .price-grid { grid-template-columns: 1fr; } }\n\n/* detail hero — simple text hero, divider into the page */\n.svc-detail-hero { padding-bottom: clamp(40px, 5vw, 60px); border-bottom: 1px solid var(--line); }\n\n/* every room/space — 3-column category grid (icon + title + item list) */\n.room-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px 36px; }\n.room-ico { width: 40px; height: 40px; border-radius: 11px; background: var(--accent-soft); color: var(--accent-strong); display: grid; place-items: center; margin-bottom: 13px; }\n.room-name { font-family: var(--font-display); font-weight: 800; font-size: 1.06rem; letter-spacing: -0.01em; color: var(--ink); margin-bottom: 6px; }\n.room-items { color: var(--muted); font-size: 0.92rem; line-height: 1.55; }\n@media (max-width: 900px) { .room-grid { grid-template-columns: 1fr 1fr; gap: 30px 32px; } }\n@media (max-width: 600px) { .room-grid { grid-template-columns: 1fr; } }\n\n/* later-content placeholders — image frames (landing .ph style) + text boxes */\n.media-frame { position: relative; aspect-ratio: 4 / 3; border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-sm); }\n.ph-text { border: 1.5px dashed var(--line-2); border-radius: var(--r); background: var(--paper-2); padding: 20px 22px; color: var(--muted); }\n.ph-text-label { display: block; font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent-strong); margin-bottom: 8px; }\n.ph-text p { margin: 0; line-height: 1.6; }\n.svc-shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }\n@media (max-width: 820px) { .svc-shots { grid-template-columns: 1fr 1fr; } }\n@media (max-width: 520px) { .svc-shots { grid-template-columns: 1fr; } }\n\n/* pricing table (support page) */\n.price-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); }\n.price-table { width: 100%; min-width: 520px; border-collapse: collapse; background: var(--paper); }\n.price-table th, .price-table td { text-align: left; padding: 18px 22px; border-bottom: 1px solid var(--line); font-size: 0.96rem; }\n.price-table thead th { background: var(--paper-3); font-weight: 800; font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }\n.price-table tbody tr { transition: background .15s; }\n.price-table tbody tr:hover { background: var(--paper-2); }\n.price-table tbody tr:last-child td { border-bottom: none; }\n.price-table .pt-svc { font-weight: 700; color: var(--ink); }\n.price-table .pt-price { font-family: var(--font-display); font-weight: 800; color: var(--accent-strong); white-space: nowrap; }\n.price-table .pt-note { color: var(--muted); }\n/* stronger divider above the Commercial table */\n.price-block-2 { margin-top: 56px; padding-top: 44px; border-top: 2px solid var(--accent); }\n\n/* amber-bordered info / note box */\n.note-box { display: flex; align-items: flex-start; gap: 13px; border: 1.5px solid var(--accent); background: var(--accent-soft); border-radius: var(--r); padding: 20px 24px; color: var(--ink-2); }\n.note-box svg { color: var(--accent-strong); flex: 0 0 auto; margin-top: 2px; }\n.note-box p { margin: 0; line-height: 1.6; font-weight: 500; }\n\n/* ============================================================\n Section background alternation (token-only)\n ============================================================ */\n.bg-2 { background: var(--paper-2); }\n.bg-night { background: var(--night); }\n.bg-night .eyebrow { color: var(--accent); }\n.bg-night h1, .bg-night h2, .bg-night h3, .bg-night h4 { color: var(--on-night); }\n.bg-night .lead, .bg-night p, .bg-night .step-desc, .bg-night .sec-head-sub { color: var(--on-night-muted); }\n.bg-night .step-title, .bg-night .room-name { color: var(--on-night); }\n.bg-night .step-num { box-shadow: 0 0 0 6px var(--night); }\n.bg-night .step-ico { background: var(--night-2); border-color: var(--night-line); color: var(--on-night); }\n.bg-night .steps-line { background: repeating-linear-gradient(90deg, var(--night-line) 0 8px, transparent 8px 16px); }\n.bg-night .why-item { color: var(--on-night-muted); }\n.bg-night .why-item svg { color: var(--accent); }\n\n/* local \"Why [City] residents choose us\" — dark feature row */\n.local-why { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; }\n.local-why-item { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 8px; }\n.local-why-ico { width: 60px; height: 60px; border-radius: 16px; background: oklch(1 0 0 / 0.08); color: var(--accent); display: grid; place-items: center; margin-bottom: 4px; }\n.local-why-item h3 { font-size: 1.2rem; color: var(--on-night); }\n.local-why-item p { color: var(--on-night-muted); font-size: 0.95rem; max-width: 30ch; margin: 0 auto; }\n@media (max-width: 760px) { .local-why { grid-template-columns: 1fr; gap: 26px; } }\n\n/* even-fill chip grid — chips grow to fill each row (no orphaned row) */\n.tag-grid.even .loc-chip { flex: 1 1 180px; text-align: center; justify-content: center; }\n\n/* full-width amber divider line */\n.amber-rule { height: 1px; background: var(--accent); border: 0; margin: 0; }\n\n/* site map */\n.sitemap-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 40px; }\n.sitemap-col .eyebrow { margin-bottom: 16px; }\n.sitemap-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }\n.sitemap-list a { color: var(--ink-2); text-decoration: none; font-weight: 600; font-size: 0.98rem; transition: color .15s; }\n.sitemap-list a:hover { color: var(--accent-strong); }\n@media (max-width: 820px) { .sitemap-grid { grid-template-columns: 1fr 1fr; } }\n@media (max-width: 520px) { .sitemap-grid { grid-template-columns: 1fr; } }\n\n/* chip/tag grid — reuses .loc-chip, slightly larger */\n.tag-grid { display: flex; flex-wrap: wrap; gap: 10px; }\n.tag-grid .loc-chip { font-size: 0.92rem; padding: 9px 15px; }\n\n/* why choose us — 2-column icon + text list */\n.why-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 40px; }\n.why-item { display: flex; align-items: flex-start; gap: 12px; padding: 8px 0; font-size: 1.02rem; font-weight: 600; color: var(--ink-2); }\n.why-item svg { color: var(--accent-strong); flex: 0 0 auto; margin-top: 1px; }\n@media (max-width: 680px) { .why-grid { grid-template-columns: 1fr; } }\n\n/* related services — horizontal scroll row of .svc-card */\n.rel-row { display: grid; grid-template-columns: repeat(4, minmax(240px, 1fr)); gap: 18px; overflow-x: auto; padding-bottom: 8px; scroll-snap-type: x proximity; }\n.rel-row .svc-card { scroll-snap-align: start; }\n.rel-row .svc-foot { justify-content: flex-start; }\n\n/* service area + contact split */\n.contact-split { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 48px; align-items: start; }\n.contact-card { padding: 30px 30px 26px; }\n.contact-form { display: grid; gap: 12px; margin-top: 18px; }\n.contact-note { display: flex; align-items: center; gap: 8px; margin: 14px 0 0; font-size: 0.85rem; font-weight: 600; color: var(--muted); }\n.contact-note svg { color: var(--accent-strong); }\n@media (max-width: 860px) { .contact-split { grid-template-columns: 1fr; gap: 30px; } }\n\n/* ============================================================\n TEMPLATE REDESIGN additions (token-only, no new colors/fonts)\n ============================================================ */\n\n/* hero \"from $X\" price badge */\n.price-badge { display: inline-flex; align-items: baseline; gap: 8px; margin-top: 22px; padding: 10px 18px; border-radius: 999px; background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent-strong); font-weight: 700; font-size: 0.92rem; }\n.price-badge strong { font-family: var(--font-display); font-size: 1.4rem; line-height: 1; color: var(--ink); }\n\n/* service — pricing breakdown card */\n.price-breakdown { max-width: 560px; padding: 0; overflow: hidden; }\n.pb-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 28px 30px; background: var(--paper-2); border-bottom: 1px solid var(--line); }\n.pb-head-label { font-size: 0.8rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-strong); }\n.pb-head-sub { font-size: 0.92rem; color: var(--muted); margin: 4px 0 0; }\n.pb-head-amt { font-family: var(--font-display); font-weight: 800; font-size: 2.6rem; line-height: 0.95; letter-spacing: -0.02em; color: var(--ink); text-align: right; }\n.pb-head-amt span { display: block; font-size: 0.78rem; font-weight: 700; color: var(--muted); letter-spacing: 0.04em; }\n.pb-rows { list-style: none; margin: 0; padding: 6px 30px; }\n.pb-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--line); font-size: 0.97rem; color: var(--ink-2); font-weight: 600; }\n.pb-row:last-child { border-bottom: none; }\n.pb-price { font-family: var(--font-display); font-weight: 800; color: var(--accent-strong); white-space: nowrap; }\n.pb-foot { padding: 4px 30px 28px; }\n.pb-note { display: flex; align-items: flex-start; gap: 10px; font-size: 0.88rem; color: var(--muted); margin: 4px 0 20px; line-height: 1.5; }\n.pb-note svg { color: var(--accent-strong); flex: 0 0 auto; margin-top: 2px; }\n\n/* category — \"Why All Pro Assemble\" trust strip (on dark) */\n.trust-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }\n.trust-cell { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 10px; }\n.trust-cell-ico { width: 58px; height: 58px; border-radius: 16px; background: oklch(1 0 0 / 0.08); color: var(--accent); display: grid; place-items: center; }\n.trust-cell h4 { font-size: 1.05rem; color: var(--on-night); }\n.trust-cell p { font-size: 0.9rem; color: var(--on-night-muted); margin: 0; max-width: 24ch; }\n@media (max-width: 760px) { .trust-strip { grid-template-columns: 1fr 1fr; gap: 30px 18px; } }\n\n/* category — featured service highlight (large split card) */\n.feature-card { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 0; overflow: hidden; padding: 0; align-items: stretch; }\n.feature-card-body { padding: clamp(30px, 4vw, 54px); display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 14px; }\n.feature-card-media { position: relative; min-height: 300px; }\n.feature-card-media .ph { position: absolute; inset: 0; }\n@media (max-width: 820px) { .feature-card { grid-template-columns: 1fr; } .feature-card-media { min-height: 220px; order: -1; } }\n\n/* support — guide step list (resources pages) */\n.guide-list { display: flex; flex-direction: column; gap: 14px; max-width: 760px; }\n.guide-step { display: flex; gap: 18px; padding: 24px 26px; }\n.guide-step-n { width: 42px; height: 42px; border-radius: 12px; background: var(--accent-soft); color: var(--accent-strong); font-family: var(--font-display); font-weight: 800; font-size: 1.2rem; display: grid; place-items: center; flex: 0 0 auto; }\n.guide-step h3 { font-size: 1.15rem; margin-bottom: 5px; }\n.guide-step p { margin: 0; color: var(--muted); font-size: 0.96rem; line-height: 1.55; }\n\n/* image placeholders — light-gray container, dashed border, centered camera + label */\n.img-ph { background: #E5E7EB; border: 1px dashed #9CA3AF; border-radius: 12px; min-height: 220px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: var(--muted); }\n.img-ph svg { color: var(--muted); }\n.img-ph-label { font-size: 0.8rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }\n\n/* hero with image placeholder on the right */\n.svc-hero-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 48px; align-items: center; }\n.svc-hero-grid .img-ph { min-height: 420px; height: 100%; }\n@media (max-width: 900px) { .svc-hero-grid { grid-template-columns: 1fr; gap: 32px; } .svc-hero-grid .img-ph { min-height: 280px; } }\n\n/* before / during / after photo strip */\n.photo-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }\n.photo-strip .img-ph { min-height: 240px; }\n@media (max-width: 700px) { .photo-strip { grid-template-columns: 1fr; } }\n\n/* service-specific prose blocks */\n.svc-prose { max-width: 760px; }\n.svc-prose h3 { font-size: 1.3rem; margin: 28px 0 10px; }\n.svc-prose p { color: var(--ink-2); line-height: 1.7; margin: 0 0 14px; }\n.svc-prose p.lead { color: var(--ink-2); }\n\n/* support — card grid (blog / customer-center pages) */\n.post-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }\n.post-card { padding: 0; overflow: hidden; display: flex; flex-direction: column; transition: transform .25s cubic-bezier(.2,.7,.3,1), box-shadow .25s; }\n.post-card:hover { transform: translateY(-5px); box-shadow: var(--shadow); }\n.post-card-media { position: relative; aspect-ratio: 16/10; }\n.post-card-media .ph { position: absolute; inset: 0; }\n.post-card-body { padding: 22px 22px 24px; display: flex; flex-direction: column; gap: 8px; flex: 1; }\n.post-card-tag { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-strong); }\n.post-card h3 { font-size: 1.12rem; letter-spacing: -0.01em; }\n.post-card p { margin: 0; font-size: 0.92rem; color: var(--muted); line-height: 1.5; flex: 1; }\n.post-card-link { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; font-size: 0.92rem; color: var(--ink); margin-top: 4px; }\n.post-card:hover .post-card-link { color: var(--accent-strong); }\n@media (max-width: 880px) { .post-grid { grid-template-columns: 1fr 1fr; } }\n@media (max-width: 560px) { .post-grid { grid-template-columns: 1fr; } }\n"; document.head.appendChild(s); })(); /* ===== tweaks-panel.jsx ===== */ // @ds-adherence-ignore -- omelette starter scaffold (raw elements/hex/px by design) /* BEGIN USAGE */ // tweaks-panel.jsx // Reusable Tweaks shell + form-control helpers. // Exports (to window): useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider, // TweakToggle, TweakRadio, TweakSelect, TweakText, TweakNumber, TweakColor, TweakButton. // // Owns the host protocol (listens for __activate_edit_mode / __deactivate_edit_mode, // posts __edit_mode_available / __edit_mode_set_keys / __edit_mode_dismissed) so // individual prototypes don't re-roll it. Ships a consistent set of controls so you // don't hand-draw , segmented radios, steppers, etc. // // Usage (in an HTML file that loads React + Babel): // // const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ // "primaryColor": "#D97757", // "palette": ["#D97757", "#29261b", "#f6f4ef"], // "fontSize": 16, // "density": "regular", // "dark": false // }/*EDITMODE-END*/; // // function App() { // const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // return ( //
// Hello // // // setTweak('fontSize', v)} /> // setTweak('density', v)} /> // // setTweak('primaryColor', v)} /> // setTweak('palette', v)} /> // setTweak('dark', v)} /> // //
// ); // } // // TweakRadio is the segmented control for 2–3 short options (auto-falls-back to // TweakSelect past ~16/~10 chars per label); reach for TweakSelect directly when // options are many or long. For color tweaks always curate 3-4 options rather than // a free picker; an option can also be a whole 2–5 color palette (the stored value // is the array). The Tweak* controls are a floor, not a ceiling — build custom // controls inside the panel if a tweak calls for UI they don't cover. /* END USAGE */ // ───────────────────────────────────────────────────────────────────────────── const __TWEAKS_STYLE = ` .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px; max-height:calc(100vh - 32px);display:flex;flex-direction:column; transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right; background:rgba(250,249,247,.78);color:#29261b; -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%); border:.5px solid rgba(255,255,255,.6);border-radius:14px; box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18); font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden} .twk-hd{display:flex;align-items:center;justify-content:space-between; padding:10px 8px 10px 14px;cursor:move;user-select:none} .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em} .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55); width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1} .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b} .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px; overflow-y:auto;overflow-x:hidden;min-height:0; scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent} .twk-body::-webkit-scrollbar{width:8px} .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px} .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px; border:2px solid transparent;background-clip:content-box} .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25); border:2px solid transparent;background-clip:content-box} .twk-row{display:flex;flex-direction:column;gap:5px} .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px} .twk-lbl{display:flex;justify-content:space-between;align-items:baseline; color:rgba(41,38,27,.72)} .twk-lbl>span:first-child{font-weight:500} .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums} .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase; color:rgba(41,38,27,.45);padding:10px 0 0} .twk-sect:first-child{padding-top:0} .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px; border:.5px solid rgba(0,0,0,.1);border-radius:7px; background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none} .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)} select.twk-field{padding-right:22px; background-image:url("data:image/svg+xml;utf8,"); background-repeat:no-repeat;background-position:right 8px center} .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0; border-radius:999px;background:rgba(0,0,0,.12);outline:none} .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none; width:14px;height:14px;border-radius:50%;background:#fff; border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default} .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%; background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default} .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px; background:rgba(0,0,0,.06);user-select:none} .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px; background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12); transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s} .twk-seg.dragging .twk-seg-thumb{transition:none} .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0; background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px; border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2; overflow-wrap:anywhere} .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px; background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0} .twk-toggle[data-on="1"]{background:#34c759} .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%; background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s} .twk-toggle[data-on="1"] i{transform:translateX(14px)} .twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:26px;padding:0 0 0 8px; border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)} .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize; user-select:none;padding-right:8px} .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent; font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0; outline:none;color:inherit;-moz-appearance:textfield} .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{ -webkit-appearance:none;margin:0} .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)} .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px; background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default} .twk-btn:hover{background:rgba(0,0,0,.88)} .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit} .twk-btn.secondary:hover{background:rgba(0,0,0,.1)} .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px; border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default; background:transparent;flex-shrink:0} .twk-swatch::-webkit-color-swatch-wrapper{padding:0} .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px} .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px} .twk-chips{display:flex;gap:6px} .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px; padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default; box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06); transition:transform .12s cubic-bezier(.3,.7,.4,1),box-shadow .12s} .twk-chip:hover{transform:translateY(-1px); box-shadow:0 0 0 .5px rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.12)} .twk-chip[data-on="1"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85), 0 2px 6px rgba(0,0,0,.15)} .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%; display:flex;flex-direction:column;box-shadow:-1px 0 0 rgba(0,0,0,.1)} .twk-chip>span>i{flex:1;box-shadow:0 -1px 0 rgba(0,0,0,.1)} .twk-chip>span>i:first-child{box-shadow:none} .twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px; filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))} `; // ── useTweaks ─────────────────────────────────────────────────────────────── // Single source of truth for tweak values. setTweak persists via the host // (__edit_mode_set_keys → host rewrites the EDITMODE block on disk). function useTweaks(defaults) { const [values, setValues] = React.useState(defaults); // Accepts either setTweak('key', value) or setTweak({ key: value, ... }) so a // useState-style call doesn't write a "[object Object]" key into the persisted // JSON block. const setTweak = React.useCallback((keyOrEdits, val) => { const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null ? keyOrEdits : { [keyOrEdits]: val }; setValues((prev) => ({ ...prev, ...edits })); window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*'); // Same-window signal so in-page listeners (deck-stage rail thumbnails) // can react — the parent message only reaches the host, not peers. window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits })); }, []); return [values, setTweak]; } // ── TweaksPanel ───────────────────────────────────────────────────────────── // Floating shell. Registers the protocol listener BEFORE announcing // availability — if the announce ran first, the host's activate could land // before our handler exists and the toolbar toggle would silently no-op. // The close button posts __edit_mode_dismissed so the host's toolbar toggle // flips off in lockstep; the host echoes __deactivate_edit_mode back which // is what actually hides the panel. function TweaksPanel({ title = 'Tweaks', children }) { const [open, setOpen] = React.useState(false); const dragRef = React.useRef(null); const offsetRef = React.useRef({ x: 16, y: 16 }); const PAD = 16; const clampToViewport = React.useCallback(() => { const panel = dragRef.current; if (!panel) return; const w = panel.offsetWidth, h = panel.offsetHeight; const maxRight = Math.max(PAD, window.innerWidth - w - PAD); const maxBottom = Math.max(PAD, window.innerHeight - h - PAD); offsetRef.current = { x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)), y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)), }; panel.style.right = offsetRef.current.x + 'px'; panel.style.bottom = offsetRef.current.y + 'px'; }, []); React.useEffect(() => { if (!open) return; clampToViewport(); if (typeof ResizeObserver === 'undefined') { window.addEventListener('resize', clampToViewport); return () => window.removeEventListener('resize', clampToViewport); } const ro = new ResizeObserver(clampToViewport); ro.observe(document.documentElement); return () => ro.disconnect(); }, [open, clampToViewport]); React.useEffect(() => { const onMsg = (e) => { const t = e?.data?.type; if (t === '__activate_edit_mode') setOpen(true); else if (t === '__deactivate_edit_mode') setOpen(false); }; window.addEventListener('message', onMsg); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', onMsg); }, []); const dismiss = () => { setOpen(false); window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); }; const onDragStart = (e) => { const panel = dragRef.current; if (!panel) return; const r = panel.getBoundingClientRect(); const sx = e.clientX, sy = e.clientY; const startRight = window.innerWidth - r.right; const startBottom = window.innerHeight - r.bottom; const move = (ev) => { offsetRef.current = { x: startRight - (ev.clientX - sx), y: startBottom - (ev.clientY - sy), }; clampToViewport(); }; const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); }; window.addEventListener('mousemove', move); window.addEventListener('mouseup', up); }; if (!open) return null; return ( <>
{title}
{children}
); } // ── Layout helpers ────────────────────────────────────────────────────────── function TweakSection({ label, children }) { return ( <>
{label}
{children} ); } function TweakRow({ label, value, children, inline = false }) { return (
{label} {value != null && {value}}
{children}
); } // ── Controls ──────────────────────────────────────────────────────────────── function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) { return ( onChange(Number(e.target.value))} /> ); } function TweakToggle({ label, value, onChange }) { return (
{label}
); } function TweakRadio({ label, value, options, onChange }) { const trackRef = React.useRef(null); const [dragging, setDragging] = React.useState(false); // The active value is read by pointer-move handlers attached for the lifetime // of a drag — ref it so a stale closure doesn't fire onChange for every move. const valueRef = React.useRef(value); valueRef.current = value; // Segments wrap mid-word once per-segment width runs out. The track is // ~248px (280 panel − 28 body pad − 4 seg pad), each button loses 12px // to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2 // options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall // back to a dropdown rather than wrap. const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length; const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0); const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] ?? 0); if (!fitsAsSegments) { // onChange(e.target.value)}> {options.map((o) => { const v = typeof o === 'object' ? o.value : o; const l = typeof o === 'object' ? o.label : o; return ; })} ); } function TweakText({ label, value, placeholder, onChange }) { return ( onChange(e.target.value)} /> ); } function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) { const clamp = (n) => { if (min != null && n < min) return min; if (max != null && n > max) return max; return n; }; const startRef = React.useRef({ x: 0, val: 0 }); const onScrubStart = (e) => { e.preventDefault(); startRef.current = { x: e.clientX, val: value }; const decimals = (String(step).split('.')[1] || '').length; const move = (ev) => { const dx = ev.clientX - startRef.current.x; const raw = startRef.current.val + dx * step; const snapped = Math.round(raw / step) * step; onChange(clamp(Number(snapped.toFixed(decimals)))); }; const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); }; window.addEventListener('pointermove', move); window.addEventListener('pointerup', up); }; return (
{label} onChange(clamp(Number(e.target.value)))} /> {unit && {unit}}
); } // Relative-luminance contrast pick — checkmarks drawn over a swatch need to // read on both #111 and #fafafa without per-option configuration. Hex input // only (#rgb / #rrggbb); named or rgb()/hsl() colors fall through to "light". function __twkIsLight(hex) { const h = String(hex).replace('#', ''); const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0'); const n = parseInt(x.slice(0, 6), 16); if (Number.isNaN(n)) return true; const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255; return r * 299 + g * 587 + b * 114 > 148000; } const __TwkCheck = ({ light }) => ( ); // TweakColor — curated color/palette picker. Each option is either a single // hex string or an array of 1-5 hex strings; the card adapts — a lone color // renders solid, a palette renders colors[0] as the hero (left ~2/3) with the // rest stacked in a sharp column on the right. onChange emits the // option in the shape it was passed (string stays string, array stays array). // Without options it falls back to the native color input for back-compat. function TweakColor({ label, value, options, onChange }) { if (!options || !options.length) { return (
{label}
onChange(e.target.value)} />
); } // Native emits lowercase hex per the HTML spec, so // compare case-insensitively. String() guards JSON.stringify(undefined), // which returns the primitive undefined (no .toLowerCase). const key = (o) => String(JSON.stringify(o)).toLowerCase(); const cur = key(value); return (
{options.map((o, i) => { const colors = Array.isArray(o) ? o : [o]; const [hero, ...rest] = colors; const sup = rest.slice(0, 4); const on = key(o) === cur; return ( ); })}
); } function TweakButton({ label, onClick, secondary = false }) { return ( ); } Object.assign(window, { useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider, TweakToggle, TweakRadio, TweakSelect, TweakText, TweakNumber, TweakColor, TweakButton, }); /* ===== js/lib.jsx ===== */ /* lib.jsx — shared primitives: Placeholder, Icons, Reveal, hooks */ const { useState, useEffect, useRef, useCallback } = React; /* ---- Placeholder image ---- */ function Ph({ label = "photo", dark = false, style, className = "" }) { return (
); } /* ---- Shared scroll-based visibility manager (reliable inside iframes) ---- */ const _watchers = new Set(); let _rafQueued = false; function _check() { _rafQueued = false; const vh = window.innerHeight || document.documentElement.clientHeight; _watchers.forEach((w) => { const el = w.el; if (!el) return; const r = el.getBoundingClientRect(); if (r.top < vh * 0.9 && r.bottom > 0) { w.cb(); _watchers.delete(w); } }); } function _queue() { if (!_rafQueued) { _rafQueued = true; requestAnimationFrame(_check); } } if (typeof window !== "undefined" && !window.__revealInit) { window.__revealInit = true; window.addEventListener("scroll", _queue, { passive: true }); window.addEventListener("resize", _queue, { passive: true }); // Arm the hidden state only if the render loop is actually running. // (In a frozen/non-painting iframe, rAF never fires → content stays visible.) requestAnimationFrame(() => { document.documentElement.classList.add("anim"); _check(); }); } function watchVisible(el, cb) { const w = { el, cb }; _watchers.add(w); _queue(); // failsafe: never leave content hidden const t = setTimeout(() => { if (_watchers.has(w)) { cb(); _watchers.delete(w); } }, 2600); return () => { _watchers.delete(w); clearTimeout(t); }; } /* ---- Scroll reveal wrapper ---- */ function Reveal({ children, delay = 0, as = "div", className = "", style, ...rest }) { const ref = useRef(null); const [seen, setSeen] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; return watchVisible(el, () => setSeen(true)); }, []); const Tag = as; return ( {children} ); } /* ---- Count up when visible ---- */ function useCountUp(target, { duration = 1500, decimals = 0 } = {}) { const ref = useRef(null); const [val, setVal] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; let raf; const cleanup = watchVisible(el, () => { const t0 = performance.now(); const tick = (t) => { const p = Math.min(1, (t - t0) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(target * eased); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); // Guarantee the final value even if rAF is throttled/frozen. setTimeout(() => setVal(target), duration + 250); }); return () => { cleanup(); cancelAnimationFrame(raf); }; }, [target, duration]); return [ref, val.toFixed(decimals)]; } /* ---- Icons (simple stroke set) ---- */ const I = { pipe: "M3 7h6a3 3 0 0 1 3 3v4a3 3 0 0 0 3 3h6M7 3v8M3 7l4-4 4 4", bolt: "M13 2 4 14h7l-1 8 9-12h-7l1-8z", saw: "M3 17 17 3l4 4L7 21l-4-4zM3 17v4h4M9 9l2 2", brush: "M9 11l-5 5a2.5 2.5 0 1 0 4 4l5-5M14 6l4 4M21 3c-1.5-.5-4 0-6 2l-5 5 4 4 5-5c2-2 2.5-4.5 2-6z", chair: "M6 4v8h12V4M6 12l-1 8M18 12l1 8M5 12h14", tv: "M3 4h18v12H3zM8 20h8M12 16v4", wall: "M3 5h18v14H3zM3 10h18M3 15h18M9 5v5M15 10v5M6 15v4M18 5v5", home: "M3 11 12 3l9 8M5 10v10h14V10", shield: "M12 2 4 5v6c0 5 3.5 8 8 11 4.5-3 8-6 8-11V5l-8-3z M9 12l2 2 4-4", clock: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 7v5l3 2", badge: "M12 15a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM8.5 13 7 22l5-3 5 3-1.5-9", star: "M12 2l2.9 6.3 6.8.8-5 4.7 1.3 6.8L12 18.6 5.7 21.4 7 14.6l-5-4.7 6.8-.8z", check: "M5 12.5 10 17.5 19.5 7", arrow: "M5 12h14M13 6l6 6-6 6", arrowUR: "M7 17 17 7M9 7h8v8", phone: "M5 3h4l2 5-3 2a12 12 0 0 0 5 5l2-3 5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 5a2 2 0 0 1 2-2z", pin: "M12 22s7-6 7-12a7 7 0 1 0-14 0c0 6 7 12 7 12zM12 12a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z", calendar: "M5 5h14v15H5zM5 9h14M9 3v4M15 3v4", user: "M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM4 21c0-4 4-6 8-6s8 2 8 6", plus: "M12 5v14M5 12h14", minus: "M5 12h14", quote: "M9 7H5a2 2 0 0 0-2 2v4h6V7zM21 7h-4a2 2 0 0 0-2 2v4h6V7zM3 13c0 3 1 4 3 4M15 13c0 3 1 4 3 4", spark: "M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5 18 18M18 6l-2.5 2.5M8.5 15.5 6 18", tool: "M14.5 5.5a3.5 3.5 0 0 0 4.9 4.4L21 11l-9 9-2-2 7-7-1.6-1.6a3.5 3.5 0 0 1-1-3.9zM6 14l4 4-3 3-4-4 3-3z", smile: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01", card: "M3 6h18v12H3zM3 10h18M7 15h4", truck: "M3 6h11v9H3zM14 9h4l3 3v3h-7zM7 18a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM18 18a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z", leaf: "M5 21c0-9 5-14 14-14 0 9-5 14-14 14zM5 21c3-3 6-5 9-6", menu: "M4 7h16M4 12h16M4 17h16", x: "M6 6l12 12M18 6 6 18", chevron: "M6 9l6 6 6-6", camera: "M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z M12 17a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", }; function Icon({ name, size = 22, stroke = 1.7, fill = false, className = "", style }) { return ( ); } function Stars({ n = 5, size = 16 }) { return ( {Array.from({ length: n }).map((_, i) => ( ))} ); } Object.assign(window, { Ph, Reveal, Icon, Icons: I, Stars, useCountUp }); /* ===== js/nav-hero.jsx ===== */ /* nav-hero.jsx — sticky mega-menu nav + 3 hero variations */ /* Convert a legacy path href to a hash route (slug = last path segment). Pricing children map to "-pricing" to match the page data slugs. */ function toHash(href) { if (!href || href.charAt(0) === "#") return href || "#top"; var parts = href.split("/").filter(Boolean); if (!parts.length) return "#top"; var last = parts[parts.length - 1]; if (parts[0] === "pricing") { if (last === "residential") return "#residential-pricing"; if (last === "commercial") return "#commercial-pricing"; } return "#" + last; } window.toHash = toHash; /* ---- Services mega: 3 main columns + an Add-Ons strip across the bottom ---- */ const SERVICE_COLUMNS = [ { heading: "Residential", links: [ { label: "Home Furniture Assembly", href: "/services/residential/home-furniture-assembly" }, { label: "Recreational Equipment", href: "/services/residential/recreational-equipment" }, { label: "Outdoor Furniture & Equipment", href: "/services/residential/outdoor-furniture-equipment" }, { label: "Fitness Equipment Assembly", href: "/services/residential/fitness-equipment-assembly" }, ]}, { heading: "Commercial", links: [ { label: "Office Furniture Assembly", href: "/services/commercial/office-furniture-assembly" }, { label: "Retail Furniture Installation", href: "/services/commercial/retail-furniture-installation" }, { label: "Restaurant Equipment Assembly", href: "/services/commercial/restaurant-equipment-assembly" }, { label: "Medical Office Setup", href: "/services/commercial/medical-office-setup" }, { label: "Educational Institution Furniture", href: "/services/commercial/educational-institution-furniture" }, ]}, { heading: "Specialized", links: [ { label: "Furniture Disassembly", href: "/services/specialized/furniture-disassembly" }, { label: "White Glove Delivery", href: "/services/specialized/white-glove-delivery" }, { label: "Furniture Relocation", href: "/services/specialized/furniture-relocation" }, { label: "Installation Services", href: "/services/specialized/installation-services" }, { label: "Removal & Recycling", href: "/services/specialized/removal-recycling" }, ]}, ]; const SERVICE_ADDONS = { heading: "Add-Ons", links: [ { label: "Packaging & Debris Removal", href: "/services/add-ons/packaging-debris-removal" }, { label: "Furniture Protection", href: "/services/add-ons/furniture-protection" }, { label: "Same-Day Service", href: "/services/add-ons/same-day-service" }, { label: "Emergency Assembly", href: "/services/add-ons/emergency-assembly" }, ], }; /* ---- Locations: states (2 rows of 3) each with sub-area chips ---- */ const LOCATION_GROUPS = [ { label: "Washington DC", href: "/areas/washington-dc", subs: [ { label: "Northwest DC", href: "/areas/washington-dc/northwest-dc" }, { label: "Northeast DC", href: "/areas/washington-dc/northeast-dc" }, { label: "Southeast DC", href: "/areas/washington-dc/southeast-dc" }, { label: "Southwest DC", href: "/areas/washington-dc/southwest-dc" }, ]}, { label: "Maryland", href: "/areas/maryland", subs: [ { label: "Montgomery County", href: "/areas/maryland/montgomery-county" }, { label: "Prince George's County", href: "/areas/maryland/prince-georges-county" }, { label: "Anne Arundel County", href: "/areas/maryland/anne-arundel-county" }, ]}, { label: "Virginia", href: "/areas/virginia", subs: [ { label: "Northern Virginia", href: "/areas/virginia/northern-virginia" }, { label: "Alexandria", href: "/areas/virginia/alexandria" }, { label: "Arlington", href: "/areas/virginia/arlington" }, { label: "Fairfax County", href: "/areas/virginia/fairfax-county" }, ]}, { label: "Pennsylvania", href: "/areas/pennsylvania", subs: [ { label: "Philadelphia Area", href: "/areas/pennsylvania/philadelphia-area" }, ]}, { label: "Delaware", href: "/areas/delaware", subs: [ { label: "New Castle County", href: "/areas/delaware/new-castle-county" }, ]}, ]; /* ---- simple dropdown menus ---- */ const PRICING_LINKS = [ { label: "Residential Pricing", href: "/pricing/residential" }, { label: "Commercial Pricing", href: "/pricing/commercial" }, { label: "Service Packages", href: "/pricing/service-packages" }, { label: "Free Estimate", href: "/pricing/free-estimate" }, ]; const RESOURCES_LINKS = [ { label: "Assembly Guides", href: "/resources/assembly-guides" }, { label: "Moving & Storage Tips", href: "/resources/moving-storage-tips" }, { label: "Maintenance Guides", href: "/resources/maintenance-guides" }, { label: "Product Manuals Library", href: "/resources/product-manuals-library" }, { label: "Video Tutorials", href: "/resources/video-tutorials" }, ]; const BLOG_LINKS = [ { label: "Assembly Tips & Tricks", href: "/blog/assembly-tips-tricks" }, { label: "Product Reviews", href: "/blog/product-reviews" }, { label: "Industry News", href: "/blog/industry-news" }, { label: "Case Studies", href: "/blog/case-studies" }, { label: "Seasonal Content", href: "/blog/seasonal-content" }, ]; const CUSTOMER_LINKS = [ { label: "Reviews & Testimonials", href: "/customer-center/reviews-testimonials" }, { label: "Before & After Gallery", href: "/customer-center/before-after-gallery" }, { label: "FAQ", href: "/customer-center/faq" }, { label: "Service Guarantee", href: "/customer-center/service-guarantee" }, { label: "Customer Support", href: "/customer-center/customer-support" }, ]; /* Top-level nav. type: "mega" (Services) | "locations" | "menu" (simple list) */ const NAV_ITEMS = [ { label: "Services", type: "mega" }, { label: "Locations", type: "locations" }, { label: "Pricing", type: "menu", links: PRICING_LINKS }, { label: "Resources", type: "menu", links: RESOURCES_LINKS }, { label: "Blog", type: "menu", links: BLOG_LINKS }, { label: "Customer Center", type: "menu", links: CUSTOMER_LINKS }, ]; /* ---- desktop dropdown panels ---- */ function ServicesMega({ activeSlug, showAll, onShowAll }) { const map = (typeof window !== "undefined" && window.SUBSERVICE_MAP) || {}; const cat = activeSlug ? map[activeSlug] : null; /* focused view: only this category's sub-services + a back link */ if (cat && !showAll) { return (
{cat.label}
); } return (
{SERVICE_COLUMNS.map((col) => (
{col.heading}
))}
{SERVICE_ADDONS.heading}
{SERVICE_ADDONS.links.map((l) => ( {l.label} ))}
); } function LocationsPanel() { return (
{LOCATION_GROUPS.map((g) => (
{g.label}
{g.subs.map((s) => ( {s.label} ))}
))}
); } function SimpleMenu({ links }) { return (
); } /* ---- mobile nested accordion (level 2) ---- */ function MAcc2({ id, title, openKey, onToggle, children }) { const isOpen = openKey === id; return (
{children}
); } function Logo({ on = "ink" }) { const c1 = on === "light" ? "var(--on-night)" : "var(--ink)"; return ( All Pro Assemble DMV · PA · DE ); } function Nav({ onQuote, activeSlug }) { const [scrolled, setScrolled] = useState(false); const [open, setOpen] = useState(false); // mobile drawer const [openMenu, setOpenMenu] = useState(null); // desktop: which panel is open const [mobileSub, setMobileSub] = useState(null); // mobile level-1 section const [mobileSub2, setMobileSub2] = useState(null); // mobile level-2 (nested group) const [svcShowAll, setSvcShowAll] = useState(false); // Services: show full mega even on a category page const closeTimer = useRef(null); const subMap = (typeof window !== "undefined" && window.SUBSERVICE_MAP) || {}; const focusedCat = activeSlug ? subMap[activeSlug] : null; // returning to a different category resets the dropdown back to its focused view useEffect(() => { setSvcShowAll(false); }, [activeSlug]); useEffect(() => { const f = () => setScrolled(window.scrollY > 24); f(); window.addEventListener("scroll", f, { passive: true }); return () => window.removeEventListener("scroll", f); }, []); // hover intent — small delay on leave so the gap to the panel doesn't flicker const enterMenu = (label) => { clearTimeout(closeTimer.current); setOpenMenu(label); }; const leaveMenu = () => { closeTimer.current = setTimeout(() => setOpenMenu(null), 140); }; const closeAll = () => { setOpen(false); setMobileSub(null); setMobileSub2(null); }; const toggleSub = (label) => setMobileSub((s) => { setMobileSub2(null); return s === label ? null : label; }); const toggleSub2 = (id) => setMobileSub2((s) => (s === id ? null : id)); const panelFor = (item) => item.type === "mega" ? setSvcShowAll(true)} /> : item.type === "locations" ? : ; return (
(301) 615-8388
{open && (
{NAV_ITEMS.map((item) => { const isOpen = mobileSub === item.label; return (
{item.type === "mega" && focusedCat && !svcShowAll && (
{focusedCat.label}
{focusedCat.subs.map((s) => ( {s.label} ))}
)} {item.type === "mega" && !(focusedCat && !svcShowAll) && ( [...SERVICE_COLUMNS, SERVICE_ADDONS].map((g) => ( {g.links.map((l) => ( {l.label} ))} )) )} {item.type === "locations" && ( LOCATION_GROUPS.map((g) => ( All of {g.label} {g.subs.map((s) => ( {s.label} ))} )) )} {item.type === "menu" && (
{item.links.map((l) => ( {l.label} ))}
)}
); })} (301) 615-8388
)}
); } /* small trust pills shown under hero CTAs */ function HeroProof() { const items = [ { icon: "shield", t: "Licensed & insured" }, { icon: "clock", t: "Same-week booking" }, { icon: "badge", t: "90-day workmanship warranty" }, ]; return (
{items.map((x) => ( {x.t} ))}
); } function RatingPill({ light = false }) { return (
4.9 · 2,300+ jobs across the DMV
); } /* ============ HERO VARIANT A — Split ============ */ function HeroSplit({ onQuote }) { return (
Your DMV handyman, on call Done right.
Done today.
Furniture assembly, TV mounting, plumbing, electrical and more — one trusted crew for every job around the house. Background-checked pros, upfront pricing, spotless cleanup. Browse services
All Pro Assemble pro mounting a TV on a living-room wall
2,300+ 5-star reviews
); } /* ============ HERO VARIANT B — Full bleed ============ */ function HeroFull({ onQuote }) { return (
Serving the DMV, Philadelphia & Delaware One call fixes it all. Vetted handymen for assembly, mounting, repairs and installs — booked in minutes, done with care. Browse services
); } /* ============ HERO VARIANT C — Bold type / collage ============ */ function HeroBold({ onQuote }) { return (
All Pro Assemble · Est. 2016 FIX. MOUNT.
ASSEMBLE.

The DMV's all-in-one handyman crew. Background-checked, fully insured, and obsessive about clean work.

Services
); } function Hero({ variant, onQuote }) { if (variant === "fullbleed") return ; if (variant === "bold") return ; return ; } Object.assign(window, { Nav, Hero, Logo, RatingPill }); /* ===== js/services.jsx ===== */ /* services.jsx — stats strip, services grid, how-it-works */ const SERVICES = [ { icon: "chair", name: "Furniture Assembly", desc: "IKEA, Wayfair, cribs, desks, beds — built solid and squared up, packaging hauled away.", tag: "Most booked", from: 79 }, { icon: "tv", name: "TV & Mounting", desc: "TVs, shelves, mirrors, art and curtain rods hung level on any wall type, cables tidy.", from: 99 }, { icon: "pipe", name: "Plumbing", desc: "Faucets, garbage disposals, toilets, leaks and shut-off valves — no surprises.", from: 119 }, { icon: "bolt", name: "Electrical", desc: "Fixtures, ceiling fans, switches, outlets and smart switches by licensed pros.", from: 129 }, { icon: "saw", name: "Carpentry", desc: "Trim, doors, shelving, cabinets and custom builds — measured twice, cut once.", from: 109 }, { icon: "brush", name: "Painting", desc: "Accent walls, rooms, touch-ups and trim with crisp lines and clean edges.", from: 149 }, { icon: "wall", name: "Drywall & Repair", desc: "Holes, cracks, water damage and texture matching — patched like it never happened.", from: 99 }, { icon: "home", name: "Smart Home", desc: "Thermostats, video doorbells, locks, cameras and hubs installed and configured.", from: 89 }, ]; function Stats() { const data = [ { n: 2300, suffix: "+", label: "Jobs completed", icon: "check" }, { n: 4.9, dec: 1, label: "Average rating", icon: "star" }, { n: 9, suffix: " yrs", label: "Serving the DMV", icon: "badge" }, { n: 24, suffix: " hr", label: "Typical response", icon: "clock" }, ]; return (
{data.map((d, i) => )}
); } function StatItem({ n, suffix = "", dec = 0, label, icon }) { const [ref, val] = useCountUp(n, { decimals: dec, duration: 1600 }); return (
{val}{suffix}
{label}
); } function ServiceCard({ s, i, onQuote }) { return ( {s.tag && {s.tag}}

{s.name}

{s.desc}

from ${s.from}
); } function Services({ onQuote }) { return (
What we do Every job around the house,
one dependable crew.
No job too small. If it needs fixing, mounting, building or installing — we handle it, and we clean up after.
{SERVICES.map((s, i) => )}

Don't see your project?

Odd jobs, punch lists, move-in fixes — tell us what you need and we'll quote it free.

); } /* ---- How it works ---- */ const STEPS = [ { icon: "calendar", t: "Tell us the job", d: "Pick a service and share a few details or photos. Get a clear, upfront estimate — no walk-through fee." }, { icon: "user", t: "Get matched", d: "We assign a background-checked pro who specializes in your task and confirm a time that works." }, { icon: "tool", t: "We get it done", d: "Your pro arrives on time, works clean, and double-checks everything before they pack up." }, { icon: "smile", t: "Love it, guaranteed", d: "Pay securely after the work's done. Every job is backed by our 90-day workmanship warranty." }, ]; function Process() { return (
How it works Booked in minutes.
Handled with care.
{STEPS.map((s, i) => (
{i + 1}

{s.t}

{s.d}

))}
); } Object.assign(window, { Stats, Services, Process, SERVICES }); /* ===== js/quote.jsx ===== */ /* quote.jsx — Quote wizard + Instant estimator (two variants) */ const QUOTE_SERVICES = [ { id: "assembly", name: "Furniture Assembly", icon: "chair", base: 79, unit: "item" }, { id: "mounting", name: "TV & Mounting", icon: "tv", base: 99, unit: "mount" }, { id: "plumbing", name: "Plumbing", icon: "pipe", base: 119, unit: "fixture" }, { id: "electrical", name: "Electrical", icon: "bolt", base: 129, unit: "fixture" }, { id: "carpentry", name: "Carpentry", icon: "saw", base: 109, unit: "project" }, { id: "painting", name: "Painting", icon: "brush", base: 149, unit: "room" }, { id: "drywall", name: "Drywall & Repair", icon: "wall", base: 99, unit: "patch" }, { id: "smart", name: "Smart Home", icon: "home", base: 89, unit: "device" }, ]; const ADDONS = [ { id: "haul", name: "Haul away packaging / debris", price: 25 }, { id: "heavy", name: "Heavy / oversized items", price: 40 }, { id: "materials", name: "We supply small materials", price: 35 }, ]; const URGENCY = [ { id: "flex", name: "I'm flexible", note: "Best price", mult: 1 }, { id: "week", name: "This week", note: "Standard", mult: 1.1 }, { id: "today", name: "ASAP / same-day", note: "+ priority", mult: 1.35 }, ]; function calcEstimate({ serviceId, qty, addons, urgencyId }) { const svc = QUOTE_SERVICES.find((s) => s.id === serviceId) || QUOTE_SERVICES[0]; const urg = URGENCY.find((u) => u.id === urgencyId) || URGENCY[0]; let sub = svc.base * qty; // gentle volume curve if (qty > 1) sub = svc.base + svc.base * 0.82 * (qty - 1); const addTotal = addons.reduce((a, id) => a + (ADDONS.find((x) => x.id === id)?.price || 0), 0); const total = (sub + addTotal) * urg.mult; const low = Math.round((total * 0.92) / 5) * 5; const high = Math.round((total * 1.12) / 5) * 5; return { low, high, mid: Math.round(total) }; } function Stepper({ value, min = 1, max = 12, onChange }) { return (
{value}
); } function EstimateReadout({ est, compact = false }) { return (
Estimated total
${est.low} ${est.high}
Free, no-obligation estimate · final price confirmed before any work
); } /* ============ VARIANT 1: WIZARD ============ */ function QuoteWizard() { const [step, setStep] = useState(0); const [serviceId, setServiceId] = useState("assembly"); const [qty, setQty] = useState(2); const [addons, setAddons] = useState(["haul"]); const [urgencyId, setUrgencyId] = useState("week"); const [contact, setContact] = useState({ name: "", phone: "", zip: "", notes: "" }); const est = calcEstimate({ serviceId, qty, addons, urgencyId }); const svc = QUOTE_SERVICES.find((s) => s.id === serviceId); const toggleAddon = (id) => setAddons((a) => a.includes(id) ? a.filter((x) => x !== id) : [...a, id]); const steps = ["Service", "Details", "Your info", "Done"]; return (
{steps.map((s, i) => (
{i < step ? : i + 1} {s}
))}
{step === 0 && (

What do you need done?

{QUOTE_SERVICES.map((s) => ( ))}
)} {step === 1 && (

A few details on your {svc.name.toLowerCase()}.

{ADDONS.map((a) => ( ))}
{URGENCY.map((u) => ( ))}
)} {step === 2 && (

Where should we send your quote?

setContact({ ...contact, name: e.target.value })} /> setContact({ ...contact, phone: e.target.value })} /> setContact({ ...contact, zip: e.target.value })} />