/* ===== 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}
e.stopPropagation()}
onClick={dismiss}>✕
{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}
onChange(!value)}>
);
}
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) {
// emits strings — map back to the original option value so the
// fallback stays type-preserving (numbers, booleans) like the segment path.
const resolve = (s) => {
const m = options.find((o) => String(typeof o === 'object' ? o.value : o) === s);
return m === undefined ? s : typeof m === 'object' ? m.value : m;
};
return onChange(resolve(s))} />;
}
const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o }));
const idx = Math.max(0, opts.findIndex((o) => o.value === value));
const n = opts.length;
const segAt = (clientX) => {
const r = trackRef.current.getBoundingClientRect();
const inner = r.width - 4;
const i = Math.floor(((clientX - r.left - 2) / inner) * n);
return opts[Math.max(0, Math.min(n - 1, i))].value;
};
const onPointerDown = (e) => {
setDragging(true);
const v0 = segAt(e.clientX);
if (v0 !== valueRef.current) onChange(v0);
const move = (ev) => {
if (!trackRef.current) return;
const v = segAt(ev.clientX);
if (v !== valueRef.current) onChange(v);
};
const up = () => {
setDragging(false);
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
};
return (
{opts.map((o) => (
{o.label}
))}
);
}
function TweakSelect({ label, value, options, onChange }) {
return (
onChange(e.target.value)}>
{options.map((o) => {
const v = typeof o === 'object' ? o.value : o;
const l = typeof o === 'object' ? o.label : o;
return {l} ;
})}
);
}
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 (
);
}
// 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 (
onChange(o)}>
{sup.length > 0 && (
{sup.map((c, j) => )}
)}
{on && <__TwkCheck light={__twkIsLight(hero)} />}
);
})}
);
}
function TweakButton({ label, onClick, secondary = false }) {
return (
{label}
);
}
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 (
← Back to all services
{cat.label}
);
}
return (
{SERVICE_COLUMNS.map((col) => (
))}
{SERVICE_ADDONS.heading}
{SERVICE_ADDONS.links.map((l) => (
{l.label}
))}
);
}
function LocationsPanel() {
return (
{LOCATION_GROUPS.map((g) => (
))}
);
}
function SimpleMenu({ links }) {
return (
);
}
/* ---- mobile nested accordion (level 2) ---- */
function MAcc2({ id, title, openKey, onToggle, children }) {
const isOpen = openKey === id;
return (
onToggle(id)} aria-expanded={isOpen}>
{title}
);
}
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 (
);
}
/* 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.
Get a free quote
Browse services
);
}
/* ============ 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.
Get a free quote
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.
);
}
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 (
);
}
function StatItem({ n, suffix = "", dec = 0, label, icon }) {
const [ref, val] = useCountUp(n, { decimals: dec, duration: 1600 });
return (
);
}
function ServiceCard({ s, i, onQuote }) {
return (
{s.tag && {s.tag} }
{s.name}
{s.desc}
from ${s.from}
Book
);
}
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.
Describe your job
);
}
/* ---- 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 (
onChange(Math.max(min, value - 1))} aria-label="less">
{value}
onChange(Math.min(max, value + 1))} aria-label="more">
);
}
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) => (
setServiceId(s.id)}>
{s.name}
))}
)}
{step === 1 && (
A few details on your {svc.name.toLowerCase()}.
How many {svc.unit}s?
Add-ons
{ADDONS.map((a) => (
toggleAddon(a.id)}>
{addons.includes(a.id) && }
{a.name}
+${a.price}
))}
How soon?
{URGENCY.map((u) => (
setUrgencyId(u.id)}>
{u.name}
{u.note}
))}
)}
{step === 2 && (
)}
{step === 3 && (
You're all set{contact.name ? ", " + contact.name.split(" ")[0] : ""}!
A coordinator will text you within 1 business hour to confirm your {svc.name.toLowerCase()} and lock in a time.
Licensed & insured
90-day warranty
)}
{step < 3 && (
setStep((s) => Math.max(0, s - 1))} style={{ visibility: step === 0 ? "hidden" : "visible" }}>Back
Est. ${est.low}–${est.high}
setStep((s) => s + 1)}>
{step === 2 ? "Get my quote" : "Continue"}
)}
{step === 3 && (
{ setStep(0); }}>Start another quote
)}
);
}
/* ============ VARIANT 2: INSTANT ESTIMATOR ============ */
function QuoteEstimator() {
const [serviceId, setServiceId] = useState("assembly");
const [qty, setQty] = useState(2);
const [addons, setAddons] = useState(["haul"]);
const [urgencyId, setUrgencyId] = useState("week");
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]);
return (
Service
{QUOTE_SERVICES.map((s) => (
setServiceId(s.id)}>
{s.name}
))}
How many {svc.unit}s?
Timing
{URGENCY.map((u) => (
setUrgencyId(u.id)}>
{u.name} {u.note}
))}
Add-ons
{ADDONS.map((a) => (
toggleAddon(a.id)}>
{addons.includes(a.id) && }
{a.name}
+${a.price}
))}
{svc.name}
${est.low}–${est.high}
Estimated total · {qty} {svc.unit}{qty > 1 ? "s" : ""}
Background-checked pro
Upfront, no-surprise pricing
90-day workmanship warranty
Book this job
No charge until the work is done.
);
}
function QuoteSection({ variant, anchorRef }) {
return (
Free instant quote
Know your price before we knock.
Build your job and see a transparent estimate in seconds — then book the time that suits you. No walk-through fees, ever.
{variant === "estimator" ? : }
);
}
Object.assign(window, { QuoteSection, QuoteWizard, QuoteEstimator });
/* ===== js/areas-gallery.jsx ===== */
/* areas-gallery.jsx — Areas we serve + before/after gallery */
const REGIONS = [
{ state: "Washington, DC", abbr: "DC", areas: ["Northwest", "Northeast", "Southwest", "Southeast"], note: "All four quadrants" },
{ state: "Maryland", abbr: "MD", areas: ["Montgomery County", "Prince George's", "Anne Arundel"] },
{ state: "Virginia", abbr: "VA", areas: ["Northern Virginia", "Alexandria", "Fairfax"] },
{ state: "Pennsylvania", abbr: "PA", areas: ["Greater Philadelphia"] },
{ state: "Delaware", abbr: "DE", areas: ["Statewide"] },
];
function Areas({ onQuote }) {
const [active, setActive] = useState(0);
return (
Where we work
Proudly serving the DMV, Philly & Delaware.
Local crews in every market mean faster arrivals and pros who know your area's homes — from DC rowhouses to Maryland new-builds.
{REGIONS.map((r, i) => (
setActive(i)}
onFocus={() => setActive(i)}
aria-label={r.state}
>
{r.abbr}
))}
Local crews · same-week availability
{REGIONS.map((r, i) => (
setActive(i)}
>
{r.abbr}
{r.state}{r.note && {r.note} }
{r.areas.map((a) => {a} )}
))}
Not sure if we reach you?
Check your ZIP
);
}
/* ---- Before / after comparison ---- */
const BA_BEFORE = "/wp-content/uploads/2026/06/before.jpg";
const BA_AFTER = "/wp-content/uploads/2026/06/after.jpg";
function BeforeAfter() {
const [pos, setPos] = useState(50);
const wrapRef = useRef(null);
const dragging = useRef(false);
const userMoved = useRef(false);
const move = useCallback((clientX) => {
const el = wrapRef.current;
if (!el) return;
const r = el.getBoundingClientRect();
const p = ((clientX - r.left) / r.width) * 100;
userMoved.current = true;
setPos(Math.max(2, Math.min(98, p)));
}, []);
// Keep --ba-w synced to the container's pixel width so the BEFORE image
// stays full size and is revealed (not squished) as the clip narrows.
useEffect(() => {
const el = wrapRef.current;
if (!el) return;
const setW = () => el.style.setProperty("--ba-w", Math.round(el.getBoundingClientRect().width) + "px");
setW();
let ro = null;
if (window.ResizeObserver) { ro = new ResizeObserver(setW); ro.observe(el); }
else { window.addEventListener("resize", setW); }
return () => { if (ro) ro.disconnect(); else window.removeEventListener("resize", setW); };
}, []);
useEffect(() => {
const onMove = (e) => { if (dragging.current) move(e.touches ? e.touches[0].clientX : e.clientX); };
const onUp = () => { dragging.current = false; };
window.addEventListener("mousemove", onMove);
window.addEventListener("touchmove", onMove, { passive: true });
window.addEventListener("mouseup", onUp);
window.addEventListener("touchend", onUp);
return () => {
window.removeEventListener("mousemove", onMove);
window.removeEventListener("touchmove", onMove);
window.removeEventListener("mouseup", onUp);
window.removeEventListener("touchend", onUp);
};
}, [move]);
// One-time intro sweep when it scrolls into view — yields the moment the user drags.
useEffect(() => {
const el = wrapRef.current;
if (!el) return;
const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduce || !window.IntersectionObserver) return;
let raf = 0, t0 = 0, started = false;
const DUR = 2600, AMP = 34;
const step = (ts) => {
if (dragging.current || userMoved.current) return;
if (!t0) t0 = ts;
const u = (ts - t0) / DUR;
if (u >= 1) { setPos(50); return; }
setPos(50 + AMP * Math.sin(u * Math.PI * 2));
raf = requestAnimationFrame(step);
};
const io = new IntersectionObserver((ents) => {
ents.forEach((e) => {
if (e.isIntersecting && !started && !userMoved.current) {
started = true; raf = requestAnimationFrame(step); io.disconnect();
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => { io.disconnect(); if (raf) cancelAnimationFrame(raf); };
}, []);
return (
{ dragging.current = true; move(e.clientX); }}
onTouchStart={(e) => { dragging.current = true; move(e.touches[0].clientX); }}
>
Before
After
);
}
function Gallery() {
const shots = [
{ label: "Floating Shelves · DC", src: "/wp-content/uploads/2026/06/floating_shelves.jpg" },
{ label: "Gallery Wall · Arlington", src: "/wp-content/uploads/2026/06/u7171748668_create_me_realistic_photo_Specialty_Installations_decc4796-222e-4e98-85e8-3304ca46db24_0.png" },
{ label: "Home Gym · Bethesda", src: "/wp-content/uploads/2026/06/home_gym.jpg" },
{ label: "Playset Assembly · Philly", src: "/wp-content/uploads/2026/06/playset.jpg" },
];
return (
Our work
The difference is in the details.
Drag the slider to see a recent project go from box-fresh to buttoned-up. Clean lines, level mounts, zero mess left behind.
{shots.map((s, i) => (
{s.src
?
: }
))}
);
}
Object.assign(window, { Areas, Gallery, REGIONS });
/* ===== js/social-proof.jsx ===== */
/* social-proof.jsx — testimonials, FAQ, final CTA, footer */
const REVIEWS = [
{ name: "Marcus T.", loc: "Capitol Hill, DC", text: "Assembled two wardrobes and a bed frame in under two hours and took every scrap of cardboard with them. Booked Monday, done Wednesday.", svc: "Furniture Assembly" },
{ name: "Priya N.", loc: "Bethesda, MD", text: "Mounted a 75\" TV with the cables completely hidden in the wall. Looks like it came with the house. Fair price, zero mess.", svc: "TV Mounting" },
{ name: "Dani R.", loc: "Alexandria, VA", text: "Fixed a leaking disposal and swapped a faucet same week. The pro was on time, friendly and explained everything.", svc: "Plumbing" },
{ name: "Greg & Sam", loc: "Fairfax, VA", text: "Whole-condo paint touch-ups and a new ceiling fan. Crisp lines, no drips on the floors. Already re-booked them.", svc: "Painting + Electrical" },
{ name: "Lena K.", loc: "Center City, Philadelphia", text: "Installed a video doorbell, smart lock and thermostat and set the whole thing up on my phone before leaving.", svc: "Smart Home" },
{ name: "Omar H.", loc: "Silver Spring, MD", text: "Patched three drywall holes from a move-out and you genuinely cannot tell where they were. Lifesaver for my deposit.", svc: "Drywall Repair" },
];
function Reviews() {
return (
Loved by neighbors
4.9 stars across 2,300+ jobs.
4.9 / 5 Google
Top Pro 2023–2026
{REVIEWS.map((r, i) => (
{r.text}
))}
);
}
const FAQS = [
{ q: "How does pricing work?", a: "Every quote is upfront and flat-rate — you see the estimate before we start and the price is confirmed before any work begins. There are no hourly surprises and no walk-through or estimate fees." },
{ q: "Are your pros licensed and insured?", a: "Yes. Every All Pro Assemble technician is background-checked, fully insured, and our plumbing and electrical work is performed by appropriately licensed professionals. We're happy to share documentation on request." },
{ q: "How soon can you come out?", a: "Most jobs across the DMV are booked the same week, and same-day priority slots are available for urgent fixes. You'll pick a confirmed time window when you book." },
{ q: "What's covered by the warranty?", a: "All workmanship is backed by our 90-day guarantee. If anything we installed or repaired isn't right, we'll come back and make it right at no charge." },
{ q: "Do I need to buy materials first?", a: "For most jobs you provide the fixtures or furniture and we bring the tools and hardware. Need us to grab small materials? Add it to your quote and we'll handle the supply run." },
{ q: "Which areas do you serve?", a: "Washington DC (all quadrants), Maryland (Montgomery, Prince George's, Anne Arundel), Northern Virginia (Alexandria, Fairfax), the Greater Philadelphia area, and Delaware." },
];
function FAQ() {
const [open, setOpen] = useState(0);
return (
Good to know
Questions, answered.
Still wondering about something? Our team replies fast.
(301) 615-8388
{FAQS.map((f, i) => (
setOpen(open === i ? -1 : i)}>
{f.q}
))}
);
}
function FinalCTA({ onQuote }) {
return (
Ready when you are
Got a to-do list? Consider it done.
Free estimates, same-week booking, and a crew that treats your home like their own.
Get a free quote
Call now
);
}
function Footer({ onQuote }) {
const cols = [
{ h: "Services", links: ["Furniture Assembly", "TV & Mounting", "Plumbing", "Electrical", "Carpentry", "Painting", "Drywall", "Smart Home"] },
{ h: "Areas", links: ["Washington, DC", "Montgomery, MD", "Prince George's, MD", "Northern Virginia", "Alexandria & Fairfax", "Philadelphia, PA", "Delaware"] },
{ h: "Company", links: ["About us", "Our guarantee", "Careers", "Reviews", "Contact"] },
];
return (
The DMV's all-in-one handyman crew — assembly, mounting, repairs and installs, done right the first time.
{cols.map((c) => (
{c.h}
{c.links.map((l) => {l} )}
))}
© 2026 All Pro Assemble LLC · Licensed & insured
);
}
Object.assign(window, { Reviews, FAQ, FinalCTA, Footer });
/* ===== js/service-page.jsx ===== */
/* service-page.jsx — SERVICE detail template, data-driven via `page` prop.
Sections: hero (+ price badge) · What's Included · How It Works (3-step) ·
Pricing breakdown · [rooms/chips if present] · Related (3) · FAQ · CTA.
Reuses landing tokens/classes + globals (Icon, Reveal, Ph, toHash, useState). */
const SP_STEPS = [
{ icon: "calendar", t: "Book in minutes", d: "Tell us what you need assembled and pick a time — get a clear, upfront estimate with no walk-through fee." },
{ icon: "user", t: "A pro arrives", d: "A background-checked specialist shows up on time with every tool the job needs." },
{ icon: "check", t: "Done & guaranteed", d: "We build it, level it, tidy up, haul the packaging — all backed by a 90-day warranty." },
];
/* related: use page data if given, else 3 sibling services from PAGES_DATA */
function spRelated(page) {
const r = page.content.related;
if (r && r.length) return r.slice(0, 3);
const data = window.PAGES_DATA || [];
return data
.filter((p) => p.template === "service" && p.parent === page.parent && p.slug !== page.slug)
.slice(0, 3)
.map((p) => ({ title: p.title, desc: (p.hero && p.hero.description) || "", link: "#" + p.slug }));
}
function SPFaq({ faqs }) {
const [open, setOpen] = useState(0);
return (
Good to know
Questions, answered.
(301) 615-8388
{faqs.map((f, i) => (
setOpen(open === i ? -1 : i)}>
{f.q}
))}
);
}
/* styled empty image placeholder — light-gray box, dashed border, camera + label */
function ImgPh({ label, style }) {
return (
{label}
);
}
function ServicePage({ page, onQuote = () => {} }) {
const c = page.content;
const parentLabel = page.parent.charAt(0).toUpperCase() + page.parent.slice(1);
const related = spRelated(page);
const pr = c.pricing || {
labor: "$" + (page.price || 89), laborLabel: page.title,
addons: [
{ name: "Each additional item (same visit)", price: "+$39" },
{ name: "Packaging & debris haul-away", price: "+$20" },
{ name: "Same-day priority scheduling", price: "+$40" },
],
note: "Starting rate. You get an exact, flat quote before any work begins — never by the hour, no surprise fees.",
};
return (
{/* HERO + breadcrumb + price badge (--paper) */}
Services
›
{parentLabel}
›
{page.title}
{page.hero.eyebrow}
{page.hero.h1}
{page.hero.description}
{page.price && (
starting ${page.price} / job
)}
Get a Free Quote
Call (301) 615-8388
{page.trust.map((t) => (
{t}
))}
{page.hero.image
?
: }
{/* SERVICE-SPECIFIC PROSE (--paper) */}
{c.article && c.article.length > 0 && (
About this service
{c.article.map((blk, i) => (
{blk.heading && {blk.heading} }
{blk.body}
))}
)}
{/* WHAT'S INCLUDED — checklist (--paper-2) */}
{c.included && c.included.length > 0 && (
What's included
Every job, the full package.
One flat price covers the build, the cleanup and the guarantee — no upsells at the door.
{c.included.map((it, i) => (
{it}
))}
)}
{/* PHOTO STRIP — before / during / after (--paper) */}
{["Before", "During", "After"].map((lbl, i) => (
{c.photos && c.photos[i]
?
: }
))}
{/* HOW IT WORKS — 3-step timeline + service photo (--night) */}
How it works
Three steps, start to finish.
{SP_STEPS.map((s, i) => (
{i + 1}
{s.t}
{s.d}
))}
{c.servicePhoto
?
: }
{/* PRICING BREAKDOWN — labor + add-ons card (--paper) */}
Upfront pricing
What it costs.
Transparent starting rates. Bundle items in one visit and the per-piece price drops.
Base labor
{pr.laborLabel || page.title}
{pr.labor}starting
{pr.addons.map((a) => (
{a.name} {a.price}
))}
{pr.note}
Get my exact price
{/* ROOMS — optional rich-page coverage grid (--paper-2) */}
{c.rooms && c.rooms.length > 0 && (
Coverage
Every room. Every space. Inside and out.
{c.rooms.map((r, i) => (
{r.title}
{r.items}
))}
)}
{/* CHIPS — optional "what we assemble" grid (--paper) */}
{c.chips && c.chips.length > 0 && (
What we assemble
Every piece, every brand.
From flat-pack basics to custom builds — if it comes in a box, we put it together.
{c.chips.map((it) => {it} )}
)}
{/* RELATED SERVICES — 3 cards (--paper-2) */}
{related.length > 0 && (
)}
{/* FAQ (--paper) */}
{/* BOTTOM CTA (--night) with phone */}
Ready to get it done today?
Book online in two minutes, or call and talk to a real person now.
Get a Free Quote
(301) 615-8388
);
}
Object.assign(window, { ServicePage });
/* ===== js/category-page.jsx ===== */
/* category-page.jsx — CATEGORY template, data-driven via `page` prop.
Sections: full-width hero · services grid (icon/name/desc/price/Book) ·
"Why All Pro Assemble" trust strip · featured service highlight · CTA.
Reuses landing tokens/classes + globals (Icon, Reveal, Ph, toHash). */
const CAT_TRUST = [
{ icon: "shield", t: "Licensed & insured", d: "Every pro is vetted, background-checked and fully covered." },
{ icon: "clock", t: "Same-day available", d: "Most jobs are booked and completed within 24 hours." },
{ icon: "badge", t: "9 years in the DMV", d: "2,500+ assemblies completed across the region." },
{ icon: "star", t: "4.9-star rated", d: "Backed by our 90-day workmanship warranty." },
];
/* price for a service card: real service-page price by slug, else category default */
function catPrice(link, fallback) {
const slug = (link || "").split("/").filter(Boolean).pop();
const data = window.PAGES_DATA || [];
const p = data.find((x) => x.slug === slug && x.template === "service");
return (p && p.price) || fallback;
}
function CategoryPage({ page, onQuote = () => {} }) {
const c = page.content;
const fb = ({ residential: 79, commercial: 149, specialized: 99, "add-ons": 49 })[page.slug] || 89;
const featured = c.services && c.services[0];
return (
{/* FULL-WIDTH HERO + breadcrumb (--paper) */}
Services
›
{page.title}
{page.hero.eyebrow}
{page.hero.h1}
{page.hero.description}
Get a Free Quote
Call (301) 615-8388
{page.trust.map((t) => (
{t}
))}
{/* SERVICES GRID — card w/ icon, name, desc, price, Book (--paper) */}
What we offer
Pick your service.
One trusted crew — choose what you need assembled or installed and book it in minutes.
{c.services.map((s, i) => (
{s.title}
{s.desc}
from ${catPrice(s.link, fb)}
Book
))}
{/* WHY ALL PRO ASSEMBLE — trust strip, 4 icons (--night) */}
Why All Pro Assemble
The all-in-one assembly crew.
{CAT_TRUST.map((t, i) => (
{t.t}
{t.d}
))}
{/* FEATURED SERVICE HIGHLIGHT — large split card (--paper-2) */}
{featured && (
Most booked
Our most-requested service.
{featured.title}
{featured.desc}
from ${catPrice(featured.link, fb)}
View this service
)}
{/* BOTTOM CTA (--night) */}
Tell us what you need built.
Free upfront quote, same-day availability, and a crew that cleans up after.
Get a Free Quote
(301) 615-8388
);
}
Object.assign(window, { CategoryPage });
/* ===== js/location-page.jsx ===== */
/* location-page.jsx — LOCATION template, data-driven via `page` prop.
Sections: hero (city) · services we offer in [city] · local stats strip ·
neighborhood chips · local FAQ · CTA with phone.
Reuses landing tokens/classes + globals (Icon, Reveal, toHash, statIcon, svcIcon). */
function LocFaq({ faqs, city }) {
const [open, setOpen] = useState(0);
return (
Good to know
{city}, answered.
(301) 615-8388
{faqs.map((f, i) => (
setOpen(open === i ? -1 : i)}>
{f.q}
))}
);
}
function LocationPage({ page, onQuote = () => {} }) {
const c = page.content;
const city = page.title;
return (
{/* HERO + breadcrumb (--paper) */}
Locations
›
{city}
{page.hero.eyebrow}
{page.hero.h1}
{page.hero.description}
Get a Free Quote
Call (301) 615-8388
{page.trust.map((t) => (
{t}
))}
{/* SERVICES WE OFFER IN [CITY] (--paper) */}
Our services
Services we offer in {city}.
Residential and commercial assembly, all handled by a local {city} crew.
{c.services.map((s, i) => (
{s.title}
{s.desc}
View Service
))}
{/* LOCAL STATS STRIP — jobs / response / rating (--ink) */}
{c.stats.map((s, i) => (
))}
{/* NEIGHBORHOODS WE SERVE — chips (--paper-2) */}
Areas we cover
Neighborhoods we serve in {city}.
Local crews across the area mean faster arrivals and same-week availability.
{c.cities.map((n) => {n} )}
{/* LOCAL FAQ (--paper) */}
{/* BOTTOM CTA (--night) with phone */}
Serving {city} & the entire region.
Book online or call a local pro now.
Get a Free Quote
(301) 615-8388
);
}
Object.assign(window, { LocationPage });
/* ===== js/support-page.jsx ===== */
/* support-page.jsx — SUPPORT template, data-driven via `page` prop.
Clean hero + a content section that ADAPTS to the page type:
· pricing → pricing table(s) + packages + note
· resources → numbered guide step list
· blog → card grid of posts
· customer-* → card grid of help resources
Then FAQ + CTA. Reuses landing tokens/classes + globals. */
const SUP_PRICE_STEPS = [
{ title: "Request", desc: "Call or fill the form — we respond within 2 hours." },
{ title: "We assess", desc: "We review your items and send an exact, flat quote." },
{ title: "You approve", desc: "No work begins until you confirm the price." },
];
const SUP_GUIDE = [
{ title: "Read the basics", desc: "Start with our quick-reference guides covering tools, prep and safety." },
{ title: "Follow along", desc: "Step-by-step instructions written in plain language — no jargon." },
{ title: "Avoid common mistakes", desc: "Pro tips on the snags that trip most people up." },
{ title: "Know when to call a pro", desc: "Some jobs are faster and safer with a specialist — we'll tell you which." },
];
const SUP_POSTS = [
{ tag: "Guide", title: "Assemble flat-pack furniture like a pro", desc: "The order of operations that saves time and prevents wobble." },
{ tag: "Tips", title: "5 tools that make assembly twice as fast", desc: "What's actually worth keeping in your kit." },
{ tag: "How-to", title: "Anchoring tall furniture safely", desc: "Tip-over prevention every household should know." },
{ tag: "Moving", title: "Disassembly checklist before a move", desc: "What to take apart, label and protect." },
{ tag: "Care", title: "Keeping joints tight over time", desc: "A two-minute seasonal check that extends furniture life." },
{ tag: "Buying", title: "Flat-pack brands worth the money", desc: "How build quality stacks up across popular lines." },
];
const SUP_CUSTOMER = [
{ tag: "Reviews", title: "What our customers say", desc: "Read verified reviews from 2,500+ jobs across the DMV." },
{ tag: "Gallery", title: "Before & after", desc: "See finished assemblies and installs from real homes." },
{ tag: "Support", title: "Get help fast", desc: "Reach a real person at (301) 615-8388, seven days a week." },
];
const SUP_PARENT_LABEL = { pricing: "Pricing", resources: "Resources", blog: "Blog", "customer-center": "Customer Center" };
function PriceTable({ rows }) {
return (
Service Starting Price Notes
{rows.map((r) => (
{r.service}
{r.price}
{r.notes}
))}
);
}
function GuideSteps({ steps, eyebrow, heading }) {
return (
{eyebrow}
{heading}
{steps.map((s, i) => (
{i + 1}
))}
);
}
function PostGrid({ posts, eyebrow, heading, onQuote }) {
return (
{eyebrow}
{heading}
Fresh content is on the way — here's what's coming to this section.
{posts.map((p, i) => (
{p.tag}
{p.title}
{p.desc}
Read more
))}
);
}
function SupFaq({ faqs }) {
const [open, setOpen] = useState(0);
return (
Good to know
Questions, answered.
(301) 615-8388
{faqs.map((f, i) => (
setOpen(open === i ? -1 : i)}>
{f.q}
))}
);
}
function SupportPage({ page, onQuote = () => {} }) {
const c = page.content;
const isPricing = page.parent === "pricing";
const isGuide = page.parent === "resources";
const isBlog = page.parent === "blog";
const isCustomer = page.parent === "customer-center";
const plabel = SUP_PARENT_LABEL[page.parent] || "Pricing";
const hasTables = c.tables && c.tables.length > 0;
const hasPackages = c.packages && c.packages.length > 0;
return (
{/* CLEAN HERO + breadcrumb (--paper) */}
{plabel}
›
{page.title}
{page.hero.eyebrow}
{page.hero.h1}
{page.hero.description}
Get a Free Quote
Call (301) 615-8388
{page.trust.map((t) => (
{t}
))}
{/* ADAPTIVE CONTENT ------------------------------------------------ */}
{/* PRICING — tables (or process steps) + packages + note */}
{isPricing && (
{hasTables ? (
{c.tables.map((tbl, ti) => (
0 ? "price-block-2" : ""}>
{tbl.label}
{tbl.heading}
))}
{c.note && (
{c.note}
)}
) : (
)}
{hasPackages && (
Save more
Bundle & save.
{c.packages.map((p, i) => (
{p.featured && Most popular }
{p.label}
from {p.price}
{p.desc}
))}
Get a custom quote for your job
)}
)}
{/* RESOURCES — numbered guide steps */}
{isGuide && (
)}
{/* BLOG — card grid */}
{isBlog && (
)}
{/* CUSTOMER CENTER — card grid */}
{isCustomer && (
)}
{/* FAQ (--paper-2) */}
{/* BOTTOM CTA (--night) */}
Still have questions?
Get a free, no-obligation quote — or call and talk it through with a real person.
Get a Free Quote
(301) 615-8388
);
}
Object.assign(window, { SupportPage });
/* ===== js/pages-data.js ===== */
/* pages-data.js — single source of truth for all redesigned WordPress pages.
Plain data + small icon-mapping helpers (data supplies labels, not icons).
Exposed on window so the (in-browser Babel) templates + app.jsx can read it. */
/* ---- icon mappers (keyword → existing Icon name) ---- */
function trustIcon(t) {
var s = String(t).toLowerCase();
if (s.indexOf("insur") >= 0) return "shield";
if (s.indexOf("same-day") >= 0 || s.indexOf("same day") >= 0) return "clock";
if (s.indexOf("warranty") >= 0) return "badge";
if (s.indexOf("rated") >= 0 || s.indexOf("star") >= 0) return "star";
if (s.indexOf("local") >= 0 || s.indexOf("team") >= 0) return "pin";
if (s.indexOf("fee") >= 0) return "card";
if (s.indexOf("quote") >= 0) return "check";
return "check";
}
function svcIcon(t) {
var s = String(t).toLowerCase();
if (s.indexOf("ikea") >= 0) return "tool";
if (s.indexOf("recreational") >= 0) return "star";
if (s.indexOf("outdoor") >= 0) return "leaf";
if (s.indexOf("fitness") >= 0) return "badge";
if (s.indexOf("kids") >= 0) return "smile";
if (s.indexOf("sports") >= 0) return "bolt";
if (s.indexOf("garage") >= 0 || s.indexOf("workshop") >= 0) return "tool";
if (s.indexOf("shed") >= 0) return "truck";
if (s.indexOf("office") >= 0) return "user";
if (s.indexOf("retail") >= 0) return "tv";
if (s.indexOf("restaurant") >= 0) return "chair";
if (s.indexOf("medical") >= 0) return "shield";
if (s.indexOf("educational") >= 0) return "badge";
if (s.indexOf("disassembly") >= 0) return "tool";
if (s.indexOf("white glove") >= 0 || s.indexOf("delivery") >= 0 || s.indexOf("relocation") >= 0) return "truck";
if (s.indexOf("installation") >= 0) return "wall";
if (s.indexOf("removal") >= 0 || s.indexOf("recycling") >= 0) return "leaf";
if (s.indexOf("packaging") >= 0) return "truck";
if (s.indexOf("protection") >= 0) return "shield";
if (s.indexOf("emergency") >= 0) return "bolt";
if (s.indexOf("commercial") >= 0) return "card";
if (s.indexOf("furniture") >= 0 || s.indexOf("home") >= 0) return "home";
return "tool";
}
function roomIcon(t) {
var s = String(t).toLowerCase();
if (s.indexOf("living") >= 0) return "tv";
if (s.indexOf("bedroom") >= 0) return "home";
if (s.indexOf("dining") >= 0) return "chair";
if (s.indexOf("office") >= 0) return "user";
if ((s.indexOf("kids") >= 0 || s.indexOf("nursery") >= 0)) return s.indexOf("outdoor") >= 0 ? "spark" : "smile";
if (s.indexOf("playroom") >= 0 || s.indexOf("game") >= 0) return "star";
if (s.indexOf("basement") >= 0 || s.indexOf("man cave") >= 0) return "card";
if (s.indexOf("kitchen") >= 0) return "wall";
if (s.indexOf("bathroom") >= 0) return "pipe";
if (s.indexOf("entryway") >= 0 || s.indexOf("mudroom") >= 0) return "pin";
if (s.indexOf("laundry") >= 0) return "badge";
if (s.indexOf("backyard") >= 0 || s.indexOf("patio") >= 0) return "leaf";
if (s.indexOf("deck") >= 0 || s.indexOf("porch") >= 0) return "home";
if (s.indexOf("sports") >= 0 || s.indexOf("recreation") >= 0) return "bolt";
if (s.indexOf("garage") >= 0 || s.indexOf("workshop") >= 0) return "tool";
if (s.indexOf("shed") >= 0) return "truck";
if (s.indexOf("pool") >= 0) return "shield";
return "home";
}
function statIcon(t) {
var s = String(t).toLowerCase();
if (s.indexOf("job") >= 0) return "check";
if (s.indexOf("same-day") >= 0 || s.indexOf("available") >= 0) return "clock";
if (s.indexOf("rated") >= 0 || s.indexOf("star") >= 0) return "star";
if (s.indexOf("serving") >= 0 || s.indexOf("since") >= 0) return "badge";
return "check";
}
function localIcon(t) {
var s = String(t).toLowerCase();
if (s.indexOf("local") >= 0) return "pin";
if (s.indexOf("same-day") >= 0) return "bolt";
if (s.indexOf("trusted") >= 0) return "star";
return "check";
}
/* states shown in contact-split / branding chips (static) */
var DMV_AREAS = [
{ name: "Washington DC", href: "/areas/washington-dc" },
{ name: "Maryland", href: "/areas/maryland" },
{ name: "Virginia", href: "/areas/virginia" },
{ name: "Pennsylvania", href: "/areas/pennsylvania" },
{ name: "Delaware", href: "/areas/delaware" },
];
/* ---- shared defaults + builders for the placeholder/minimal pages ---- */
var STD_TRUST = ["Licensed & Insured", "Same-Day Available", "2,500+ Jobs", "90-Day Warranty"];
var MIN_FAQ = [
{ q: "How do I book this service?", a: "Call (301) 615-8388 or fill out the quote form." },
{ q: "Do you serve my area?", a: "We serve MD, DC, VA, PA and DE." },
{ q: "Are you licensed and insured?", a: "Yes. Fully licensed and insured." }
];
var STD_WHY = [
"Skilled and experienced technicians",
"Fully licensed and insured",
"Same-day and weekend availability",
"Over 2,500 successful projects completed",
"Transparent upfront pricing, no surprise fees",
"Serving MD, PA, DC, DE, and Northern Virginia"
];
var STD_LOCAL_WHY = [
"Local team you can trust",
"Same-day and weekend availability",
"Background-checked technicians",
"Fully licensed and insured",
"Transparent upfront pricing",
"Serving the entire region"
];
var STD_LOC_SERVICES = [
{ title: "Furniture Assembly", desc: "Beds, dressers, sofas and tv stands, built solid.", link: "/services/residential/home-furniture-assembly" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor & Recreation", desc: "Patio sets, playsets, trampolines and more.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Commercial Assembly", desc: "Office furniture, cubicles and retail fixtures.", link: "/services/commercial/office-furniture-assembly" }
];
var STD_INCLUDED = [
"Full assembly by a background-checked, insured pro",
"All tools, hardware sorting and fittings handled",
"Packaging, boxes and debris hauled away",
"Furniture leveled, squared and wall-anchored if needed",
"Backed by our 90-day workmanship warranty"
];
var PRICE_BY_PARENT = { residential: 79, commercial: 149, specialized: 99, "add-ons": 49 };
function mkService(id, slug, parent, eyebrow, h1, desc) {
return { id: id, title: h1, slug: slug, template: "service", parent: parent,
price: PRICE_BY_PARENT[parent] || 89,
hero: { eyebrow: eyebrow, h1: h1, description: desc },
trust: STD_TRUST.slice(),
content: { overview: { heading: h1, body: desc }, included: STD_INCLUDED.slice(), why: STD_WHY.slice(), faq: MIN_FAQ.slice() } };
}
function mkLocationSub(id, slug, parent, h1, cities) {
var title = h1.replace("Assembly Services in ", "");
return { id: id, title: title, slug: slug, template: "location", parent: parent,
hero: { eyebrow: "SERVING " + title.toUpperCase(), h1: h1, description: "Professional assembly and installation services across " + title + ". Same-day availability, upfront pricing, licensed and insured." },
trust: STD_TRUST.slice(),
content: {
stats: ["2,500+ Jobs", "Same-Day Service", "5-Star Rated", "Since 2019"],
cities: cities,
services: STD_LOC_SERVICES.slice(),
why: STD_LOCAL_WHY.slice(),
local: { heading: "Why " + title + " residents choose us.", items: [
{ title: "Local Team", desc: "Technicians based nearby — no long drives, faster arrival" },
{ title: "Same-Day", desc: "Most jobs booked and completed same day or next day" },
{ title: "Trusted Locally", desc: "Thousands of local families and businesses served" }
] },
faq: MIN_FAQ.slice()
} };
}
function mkSupport(id, slug, parent, eyebrow, h1) {
return { id: id, title: h1, slug: slug, template: "support", parent: parent,
hero: { eyebrow: eyebrow, h1: h1, description: "Get an instant estimate or request a custom quote. No hidden fees, no surprises." },
trust: STD_TRUST.slice(),
content: { tables: [], note: "All prices are starting rates. Final price depends on item complexity, quantity, and location. We provide exact quotes before any work begins — no surprise fees ever.", steps: [], packages: [], faq: MIN_FAQ.slice() } };
}
/* context-aware nav: each service category → its sub-services (also used to
generate the sub-service pages below). Keyed by the category page slug. */
var SUBSERVICE_MAP = {
"home-furniture-assembly": { label: "Home Furniture Assembly", parent: "residential", subs: [
{ label: "Bedroom Furniture", slug: "bedroom-furniture" },
{ label: "Living Room Assembly", slug: "living-room-assembly" },
{ label: "Dining Room Sets", slug: "dining-room-sets" },
{ label: "Home Office Furniture", slug: "home-office-furniture" },
{ label: "IKEA Furniture Assembly", slug: "ikea-furniture-assembly" },
{ label: "Crib & Baby Furniture", slug: "crib-baby-furniture" },
{ label: "Bookcase & Shelving", slug: "bookcase-shelving" },
{ label: "TV Stand Assembly", slug: "tv-stand-assembly" }
]},
"recreational-equipment": { label: "Recreational Equipment", parent: "residential", subs: [
{ label: "Playground & Playset", slug: "playground-playset" },
{ label: "Trampoline Assembly", slug: "trampoline-assembly" },
{ label: "Above Ground Pool", slug: "above-ground-pool" },
{ label: "Gazebo & Pergola", slug: "gazebo-pergola" },
{ label: "Storage Shed", slug: "storage-shed" },
{ label: "Basketball Hoop", slug: "basketball-hoop" }
]},
"outdoor-furniture-equipment": { label: "Outdoor Furniture & Equipment", parent: "residential", subs: [
{ label: "Patio Furniture", slug: "patio-furniture" },
{ label: "Grill Assembly", slug: "grill-assembly" },
{ label: "Outdoor Storage Solutions", slug: "outdoor-storage-solutions" }
]},
"fitness-equipment-assembly": { label: "Fitness Equipment", parent: "residential", subs: [
{ label: "Treadmill Assembly", slug: "treadmill-assembly" },
{ label: "Exercise Bike", slug: "exercise-bike" },
{ label: "Home Gym Equipment", slug: "home-gym-equipment" },
{ label: "Weight Bench", slug: "weight-bench" }
]},
"office-furniture-assembly": { label: "Office Furniture", parent: "commercial", subs: [
{ label: "Cubicle Installation", slug: "cubicle-installation" },
{ label: "Conference Room Furniture", slug: "conference-room-furniture" },
{ label: "Reception Area Setup", slug: "reception-area-setup" },
{ label: "Executive Office Assembly", slug: "executive-office-assembly" }
]},
"retail-furniture-installation": { label: "Retail Furniture", parent: "commercial", subs: [
{ label: "Display Shelving Installation", slug: "display-shelving-installation" },
{ label: "Checkout Counter Assembly", slug: "checkout-counter-assembly" },
{ label: "Mannequin & Fixture Setup", slug: "mannequin-fixture-setup" },
{ label: "Stockroom Shelving", slug: "stockroom-shelving" }
]},
"restaurant-equipment-assembly": { label: "Restaurant Equipment", parent: "commercial", subs: [
{ label: "Dining Table & Booth Assembly", slug: "dining-booth-assembly" },
{ label: "Bar Stool & Counter Setup", slug: "bar-stool-counter-setup" },
{ label: "Commercial Shelving", slug: "commercial-shelving" },
{ label: "Patio & Outdoor Seating", slug: "restaurant-patio-seating" }
]},
"medical-office-setup": { label: "Medical Office", parent: "commercial", subs: [
{ label: "Exam Table Assembly", slug: "exam-table-assembly" },
{ label: "Medical Storage Cabinets", slug: "medical-storage-cabinets" },
{ label: "Waiting Room Seating", slug: "waiting-room-seating" },
{ label: "Reception Desk Installation", slug: "medical-reception-desk" }
]},
"educational-institution-furniture": { label: "Educational Furniture", parent: "commercial", subs: [
{ label: "Classroom Desk & Chair Assembly", slug: "classroom-desk-chair" },
{ label: "Library Shelving Installation", slug: "library-shelving" },
{ label: "Lab Furniture Setup", slug: "lab-furniture-setup" },
{ label: "Auditorium Seating", slug: "auditorium-seating" }
]}
};
var PAGES_DATA = [
/* ===================== CATEGORY ===================== */
{
id: 13, title: "Residential Services", slug: "residential", template: "category", parent: "services",
hero: { eyebrow: "RESIDENTIAL SERVICES", h1: "Home Assembly & Installation Services", description: "From bedroom furniture to backyard playsets — our residential team handles every room, inside and out. Licensed, insured, and available same-day across the DMV." },
trust: ["Licensed & Insured", "Same-Day Available", "2,500+ Jobs", "90-Day Warranty"],
content: {
intro: { heading: "Professional assembly for every home", body: "Our residential team handles everything from single-item assembly to whole-home move-in setups. Background-checked pros, upfront pricing, and a 90-day workmanship warranty on every job." },
services: [
{ title: "Home Furniture Assembly", desc: "Beds, dressers, sofas and more — built solid in every room.", link: "/services/residential/home-furniture-assembly" },
{ title: "Recreational Equipment", desc: "Playsets, trampolines, game tables and more.", link: "/services/residential/recreational-equipment" },
{ title: "Outdoor Furniture & Equipment", desc: "Patio sets, grills and outdoor storage solutions.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Fitness Equipment Assembly", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Kids Outdoor Play", desc: "Swing sets, playsets, zip lines, trampolines and more.", link: "/services/residential/kids-outdoor-play" },
{ title: "Sports & Recreation", desc: "Basketball hoops, batting cages, soccer goals and more.", link: "/services/residential/sports-recreation" },
{ title: "Garage & Workshop", desc: "Shelving, workbenches, overhead storage and tool cabinets.", link: "/services/residential/garage-workshop" },
{ title: "Shed & Outdoor Storage", desc: "Storage sheds, garden sheds and outdoor storage boxes.", link: "/services/residential/shed-outdoor-storage" }
],
why: ["Skilled and experienced technicians", "Fully licensed and insured", "Same-day and weekend availability", "Over 2,500 successful projects completed", "Transparent upfront pricing, no surprise fees", "Serving MD, PA, DC, DE, and Northern Virginia"],
faq: [
{ q: "What residential services do you offer?", a: "Furniture assembly, fitness equipment, outdoor and recreational assembly, garage storage and more — for every room, inside and out." },
{ q: "Do you serve my area?", a: "Yes. We serve Maryland, Washington DC, Virginia, Pennsylvania, and Delaware." },
{ q: "How do I book a service?", a: "Call (301) 615-8388 or fill out the quote form. We respond within 2 hours." },
{ q: "Are you licensed and insured?", a: "Yes. All technicians are fully licensed and insured." },
{ q: "Do you offer same-day service?", a: "Yes. Same-day availability across the DMV area." }
]
}
},
{
id: 14, title: "Commercial Services", slug: "commercial", template: "category", parent: "services",
hero: { eyebrow: "COMMERCIAL SERVICES", h1: "Commercial Assembly & Installation Services", description: "Office furniture, retail fixtures, restaurant equipment and more. Our commercial team handles large-scale projects with minimal disruption to your business." },
trust: ["Licensed & Insured", "Same-Day Available", "2,500+ Jobs", "90-Day Warranty"],
content: {
intro: { heading: "Professional assembly for every business", body: "From single workstations to full office buildouts — we handle commercial assembly projects of any size. Background-checked pros, upfront pricing, and flexible scheduling around your business hours." },
services: [
{ title: "Office Furniture Assembly", desc: "Desks, chairs, cubicles and conference room furniture.", link: "/services/commercial/office-furniture-assembly" },
{ title: "Retail Furniture Installation", desc: "Display fixtures, shelving and retail setup.", link: "/services/commercial/retail-furniture-installation" },
{ title: "Restaurant Equipment Assembly", desc: "Tables, chairs, bar stools and restaurant fixtures.", link: "/services/commercial/restaurant-equipment-assembly" },
{ title: "Medical Office Setup", desc: "Exam tables, storage and medical office furniture.", link: "/services/commercial/medical-office-setup" },
{ title: "Educational Institution Furniture", desc: "Classroom desks, chairs, shelving and storage.", link: "/services/commercial/educational-institution-furniture" }
],
why: ["Experienced commercial assembly team", "Fully licensed and insured", "Flexible scheduling around business hours", "Over 2,500 successful projects completed", "Transparent upfront pricing, no surprise fees", "Serving MD, PA, DC, DE, and Northern Virginia"],
faq: [
{ q: "What commercial services do you offer?", a: "Office furniture, retail fixtures, restaurant equipment, medical office setup and educational institution furniture." },
{ q: "Can you work outside business hours?", a: "Yes. We offer flexible scheduling including evenings and weekends." },
{ q: "Do you handle large office buildouts?", a: "Yes. We handle projects of any size from single workstations to full office floors." },
{ q: "Are you licensed and insured?", a: "Yes. All technicians are fully licensed and insured." },
{ q: "Do you offer same-day service?", a: "Yes. Same-day availability across the DMV area." }
]
}
},
{
id: 15, title: "Specialized Services", slug: "specialized", template: "category", parent: "services",
hero: { eyebrow: "SPECIALIZED SERVICES", h1: "Specialized Assembly & Installation Services", description: "Disassembly, white glove delivery, relocation and removal — we handle the complex jobs other services won't touch." },
trust: ["Licensed & Insured", "Same-Day Available", "2,500+ Jobs", "90-Day Warranty"],
content: {
intro: { heading: "The jobs others won't do", body: "From careful furniture disassembly for moving to white glove delivery setup — our specialized team handles every complex assembly and removal project with care and precision." },
services: [
{ title: "Furniture Disassembly", desc: "Careful disassembly for moving, storage or donation.", link: "/services/specialized/furniture-disassembly" },
{ title: "White Glove Delivery", desc: "Premium delivery, unpacking and full setup service.", link: "/services/specialized/white-glove-delivery" },
{ title: "Furniture Relocation", desc: "Move furniture within your home or to a new location.", link: "/services/specialized/furniture-relocation" },
{ title: "Installation Services", desc: "TV mounting, shelving, mirrors and wall installations.", link: "/services/specialized/installation-services" },
{ title: "Removal & Recycling", desc: "Responsible furniture removal and recycling service.", link: "/services/specialized/removal-recycling" }
],
why: ["Careful and experienced specialized team", "Fully licensed and insured", "Same-day and weekend availability", "Over 2,500 successful projects completed", "Transparent upfront pricing, no surprise fees", "Serving MD, PA, DC, DE, and Northern Virginia"],
faq: [
{ q: "What specialized services do you offer?", a: "Disassembly, white glove delivery, relocation, installation and removal and recycling." },
{ q: "Do you disassemble furniture for moving?", a: "Yes. We carefully disassemble and prepare furniture for moving or storage." },
{ q: "What is white glove delivery?", a: "We receive your delivery, unpack it, assemble it and set it up exactly where you want it." },
{ q: "Do you haul away old furniture?", a: "Yes. We offer responsible removal and recycling of old furniture." },
{ q: "Do you offer same-day service?", a: "Yes. Same-day availability across the DMV area." }
]
}
},
{
id: 16, title: "Service Add-Ons", slug: "add-ons", template: "category", parent: "services",
hero: { eyebrow: "SERVICE ADD-ONS", h1: "Assembly Service Add-Ons", description: "Enhance your assembly service with packaging removal, furniture protection, same-day service and emergency assembly." },
trust: ["Licensed & Insured", "Same-Day Available", "2,500+ Jobs", "90-Day Warranty"],
content: {
intro: { heading: "More value on every job", body: "Our service add-ons let you customize your assembly experience. Add packaging removal, furniture protection or upgrade to same-day or emergency service." },
services: [
{ title: "Packaging & Debris Removal", desc: "We haul away all boxes and packaging materials.", link: "/services/add-ons/packaging-debris-removal" },
{ title: "Furniture Protection", desc: "Floor and wall protection during assembly.", link: "/services/add-ons/furniture-protection" },
{ title: "Same-Day Service", desc: "Priority same-day scheduling for urgent jobs.", link: "/services/add-ons/same-day-service" },
{ title: "Emergency Assembly", desc: "Emergency assembly service available 24/7.", link: "/services/add-ons/emergency-assembly" }
],
why: ["Flexible add-on options for every job", "Fully licensed and insured", "Same-day and emergency availability", "Over 2,500 successful projects completed", "Transparent upfront pricing, no surprise fees", "Serving MD, PA, DC, DE, and Northern Virginia"],
faq: [
{ q: "What add-ons are available?", a: "Packaging removal, furniture protection, same-day service and emergency assembly." },
{ q: "How do I add these to my service?", a: "Just mention it when you book. We will include it in your quote." },
{ q: "Is same-day service always available?", a: "Subject to availability. Call (301) 615-8388 for same-day requests." },
{ q: "What does furniture protection include?", a: "Floor padding, wall protection and careful handling during assembly." },
{ q: "Do you offer 24/7 emergency assembly?", a: "Yes. Call (301) 615-8388 for emergency assembly requests." }
]
}
},
/* ===================== LOCATION ===================== */
{
id: 17, title: "Washington DC", slug: "washington-dc", template: "location", parent: "areas",
hero: { eyebrow: "SERVING WASHINGTON DC", h1: "Assembly Services in Washington DC", description: "Professional assembly and installation services across Northwest, Northeast, Southeast and Southwest DC. Same-day availability, upfront pricing, licensed and insured." },
trust: ["Licensed & Insured", "Same-Day Available", "Local DC Team", "90-Day Warranty"],
content: {
stats: ["500+ Jobs in DC", "Same-Day Available", "5-Star Rated", "Serving DC Since 2019"],
cities: ["Northwest DC", "Northeast DC", "Southeast DC", "Southwest DC", "Capitol Hill", "Dupont Circle", "Columbia Heights", "Downtown DC", "Adams Morgan", "Georgetown", "Anacostia", "Brookland"],
services: [
{ title: "Furniture Assembly", desc: "Beds, dressers, sofas and tv stands, built solid.", link: "/services/residential/home-furniture-assembly" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor & Recreation", desc: "Patio sets, playsets, trampolines and more.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Commercial Assembly", desc: "Office furniture, cubicles and retail fixtures.", link: "/services/commercial/office-furniture-assembly" }
],
why: ["Local DC-based team", "Same-day and weekend availability", "Serving DC for over 5 years", "Fully licensed and insured in DC", "Background-checked technicians", "Transparent upfront pricing"],
local: { heading: "Why DC residents choose us.", items: [
{ title: "Local Team", desc: "Technicians based in DC — no long drives, faster arrival" },
{ title: "Same-Day", desc: "Most DC jobs booked and completed same day or next day" },
{ title: "Trusted Locally", desc: "500+ DC families and businesses served" }
] },
faq: [
{ q: "Do you serve all of Washington DC?", a: "Yes — all quadrants including Northwest, Northeast, Southeast and Southwest DC." },
{ q: "How quickly can you come to my area?", a: "Most DC jobs are booked same day or next day." },
{ q: "Are your technicians local to DC?", a: "Yes. Our team is based in the DC area." },
{ q: "Do you offer same-day service in DC?", a: "Yes. Same-day availability across Washington DC." },
{ q: "What areas of DC do you cover?", a: "All DC neighborhoods including Capitol Hill, Dupont Circle, Columbia Heights, Georgetown and more." }
]
}
},
{
id: 18, title: "Maryland", slug: "maryland", template: "location", parent: "areas",
hero: { eyebrow: "SERVING MARYLAND", h1: "Assembly Services in Maryland", description: "Professional assembly and installation services across Montgomery County, Prince George's County, Anne Arundel County and beyond. Same-day availability, upfront pricing, licensed and insured." },
trust: ["Licensed & Insured", "Same-Day Available", "Local Maryland Team", "90-Day Warranty"],
content: {
stats: ["500+ Jobs in Maryland", "Same-Day Available", "5-Star Rated", "Serving MD Since 2019"],
cities: ["Montgomery County", "Prince George's County", "Anne Arundel County", "Baltimore County", "Howard County", "Frederick County", "Silver Spring", "Rockville", "Bethesda", "Gaithersburg", "Germantown", "Columbia", "Annapolis", "Baltimore", "Waldorf", "Frederick"],
services: [
{ title: "Furniture Assembly", desc: "Beds, dressers, sofas and tv stands, built solid.", link: "/services/residential/home-furniture-assembly" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor & Recreation", desc: "Patio sets, playsets, trampolines and more.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Commercial Assembly", desc: "Office furniture, cubicles and retail fixtures.", link: "/services/commercial/office-furniture-assembly" }
],
why: ["Local Maryland-based team", "Same-day and weekend availability", "Serving MD for over 5 years", "Fully licensed and insured in Maryland", "Background-checked technicians", "Transparent upfront pricing"],
local: { heading: "Why Maryland residents choose us.", items: [
{ title: "Local Team", desc: "Technicians based in Maryland — no long drives, faster arrival" },
{ title: "Same-Day", desc: "Most Maryland jobs booked and completed same day or next day" },
{ title: "Trusted Locally", desc: "500+ Maryland families and businesses served" }
] },
faq: [
{ q: "Do you serve all of Maryland?", a: "Yes — from Montgomery and Prince George's County to Baltimore, Howard, Frederick and the surrounding areas." },
{ q: "How quickly can you come to my area?", a: "Most Maryland jobs are booked same day or next day." },
{ q: "Are your technicians local to Maryland?", a: "Yes. Our team is based in the Maryland area." },
{ q: "Do you offer same-day service in Maryland?", a: "Yes. Same-day availability across Maryland." },
{ q: "What areas of Maryland do you cover?", a: "All major Maryland counties and cities including Montgomery, Prince George's, Anne Arundel, Baltimore, Silver Spring, Rockville, Bethesda and more." }
]
}
},
{
id: 19, title: "Virginia", slug: "virginia", template: "location", parent: "areas",
hero: { eyebrow: "SERVING VIRGINIA", h1: "Assembly Services in Virginia", description: "Professional assembly and installation across Northern Virginia, Alexandria, Arlington, Fairfax County and beyond. Same-day availability, upfront pricing, licensed and insured." },
trust: ["Licensed & Insured", "Same-Day Available", "Local Virginia Team", "90-Day Warranty"],
content: {
stats: ["500+ Jobs in Virginia", "Same-Day Available", "5-Star Rated", "Serving VA Since 2019"],
cities: ["Northern Virginia", "Alexandria", "Arlington", "Fairfax County", "Reston", "Herndon", "Ashburn", "Tysons", "McLean", "Vienna", "Springfield", "Manassas", "Woodbridge", "Chantilly"],
services: [
{ title: "Furniture Assembly", desc: "Beds, dressers, sofas and tv stands, built solid.", link: "/services/residential/home-furniture-assembly" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor & Recreation", desc: "Patio sets, playsets, trampolines and more.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Commercial Assembly", desc: "Office furniture, cubicles and retail fixtures.", link: "/services/commercial/office-furniture-assembly" }
],
why: ["Local Virginia-based team", "Same-day and weekend availability", "Serving VA for over 5 years", "Fully licensed and insured in Virginia", "Background-checked technicians", "Transparent upfront pricing"],
local: { heading: "Why Virginia residents choose us.", items: [
{ title: "Local Team", desc: "Technicians based in Northern Virginia — no long drives, faster arrival" },
{ title: "Same-Day", desc: "Most Virginia jobs booked and completed same day or next day" },
{ title: "Trusted Locally", desc: "500+ Virginia families and businesses served" }
] },
faq: [
{ q: "Do you serve all of Virginia?", a: "We focus on Northern Virginia including Alexandria, Arlington, Fairfax County, Reston, Ashburn and surrounding areas." },
{ q: "How quickly can you come to my area?", a: "Most Virginia jobs are booked same day or next day." },
{ q: "Are your technicians local to Virginia?", a: "Yes. Our team is based in Northern Virginia." },
{ q: "Do you offer same-day service in Virginia?", a: "Yes. Same-day availability across Northern Virginia." },
{ q: "What areas of Virginia do you cover?", a: "Alexandria, Arlington, Fairfax County, Reston, Herndon, Ashburn, Tysons, McLean, Vienna, Springfield and more." }
]
}
},
{
id: 20, title: "Pennsylvania", slug: "pennsylvania", template: "location", parent: "areas",
hero: { eyebrow: "SERVING PENNSYLVANIA", h1: "Assembly Services in Pennsylvania", description: "Professional assembly and installation across Philadelphia, Levittown, Phoenixville and the greater PA area. Same-day availability, upfront pricing, licensed and insured." },
trust: ["Licensed & Insured", "Same-Day Available", "Local PA Team", "90-Day Warranty"],
content: {
stats: ["200+ Jobs in PA", "Same-Day Available", "5-Star Rated", "Serving PA Since 2020"],
cities: ["Philadelphia", "Levittown", "Phoenixville", "Chester County", "Delaware County", "Montgomery County PA", "Bucks County", "King of Prussia", "Norristown", "Lansdale", "Doylestown", "Pottstown", "Exton", "Wayne"],
services: [
{ title: "Furniture Assembly", desc: "Beds, dressers, sofas and tv stands, built solid.", link: "/services/residential/home-furniture-assembly" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor & Recreation", desc: "Patio sets, playsets, trampolines and more.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Commercial Assembly", desc: "Office furniture, cubicles and retail fixtures.", link: "/services/commercial/office-furniture-assembly" }
],
why: ["Local Pennsylvania-based team", "Same-day and weekend availability", "Serving PA for over 4 years", "Fully licensed and insured in Pennsylvania", "Background-checked technicians", "Transparent upfront pricing"],
local: { heading: "Why Pennsylvania residents choose us.", items: [
{ title: "Local Team", desc: "Technicians based in the Philadelphia area — no long drives, faster arrival" },
{ title: "Same-Day", desc: "Most PA jobs booked and completed same day or next day" },
{ title: "Trusted Locally", desc: "200+ Pennsylvania families and businesses served" }
] },
faq: [
{ q: "Do you serve all of Pennsylvania?", a: "We focus on the Philadelphia area including Levittown, Phoenixville, Chester County, Delaware County and surrounding areas." },
{ q: "How quickly can you come to my area?", a: "Most PA jobs are booked same day or next day." },
{ q: "Are your technicians local to Pennsylvania?", a: "Yes. Our team is based in the Philadelphia area." },
{ q: "Do you offer same-day service in Pennsylvania?", a: "Yes. Same-day availability across the Philadelphia area." },
{ q: "What areas of Pennsylvania do you cover?", a: "Philadelphia, Levittown, Phoenixville, King of Prussia, Norristown, Bucks County, Chester County and more." }
]
}
},
{
id: 21, title: "Delaware", slug: "delaware", template: "location", parent: "areas",
hero: { eyebrow: "SERVING DELAWARE", h1: "Assembly Services in Delaware", description: "Professional assembly and installation across New Castle County, Wilmington, Newark and the greater Delaware area. Same-day availability, upfront pricing, licensed and insured." },
trust: ["Licensed & Insured", "Same-Day Available", "Local DE Team", "90-Day Warranty"],
content: {
stats: ["150+ Jobs in Delaware", "Same-Day Available", "5-Star Rated", "Serving DE Since 2020"],
cities: ["Wilmington", "Newark", "New Castle County", "Dover", "Middletown", "Bear", "Hockessin", "Pike Creek", "Claymont", "Glasgow", "Smyrna", "Milford"],
services: [
{ title: "Furniture Assembly", desc: "Beds, dressers, sofas and tv stands, built solid.", link: "/services/residential/home-furniture-assembly" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor & Recreation", desc: "Patio sets, playsets, trampolines and more.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "Commercial Assembly", desc: "Office furniture, cubicles and retail fixtures.", link: "/services/commercial/office-furniture-assembly" }
],
why: ["Local Delaware-based team", "Same-day and weekend availability", "Serving DE for over 4 years", "Fully licensed and insured in Delaware", "Background-checked technicians", "Transparent upfront pricing"],
local: { heading: "Why Delaware residents choose us.", items: [
{ title: "Local Team", desc: "Technicians based in Delaware — no long drives, faster arrival" },
{ title: "Same-Day", desc: "Most Delaware jobs booked and completed same day or next day" },
{ title: "Trusted Locally", desc: "150+ Delaware families and businesses served" }
] },
faq: [
{ q: "Do you serve all of Delaware?", a: "We focus on New Castle County including Wilmington, Newark, Middletown, Bear and surrounding areas." },
{ q: "How quickly can you come to my area?", a: "Most Delaware jobs are booked same day or next day." },
{ q: "Are your technicians local to Delaware?", a: "Yes. Our team is based in Delaware." },
{ q: "Do you offer same-day service in Delaware?", a: "Yes. Same-day availability across Delaware." },
{ q: "What areas of Delaware do you cover?", a: "Wilmington, Newark, New Castle County, Dover, Middletown, Bear, Hockessin and more." }
]
}
},
/* ===================== SUPPORT ===================== */
{
id: 22, title: "Residential Pricing", slug: "residential-pricing", template: "support", parent: "pricing",
hero: { eyebrow: "TRANSPARENT PRICING", h1: "Simple, Upfront Assembly Pricing", description: "No hidden fees. No surprises. Get an instant estimate or request a custom quote for larger jobs." },
trust: ["No Hidden Fees", "Upfront Quotes", "Licensed & Insured", "Same-Day Available"],
content: {
tables: [
{ label: "RESIDENTIAL PRICING", heading: "Furniture Assembly", rows: [
{ service: "Single Item Assembly", price: "from $79", notes: "Any brand, any room" },
{ service: "3-5 Items Same Visit", price: "from $149", notes: "Save with bundling" },
{ service: "Full Room Package", price: "from $249", notes: "Priority scheduling" },
{ service: "IKEA Full Room", price: "from $299", notes: "Flat-pack specialists" },
{ service: "Disassembly", price: "from $59", notes: "Per item" },
{ service: "White Glove Delivery", price: "from $129", notes: "Setup included" }
] },
{ label: "COMMERCIAL PRICING", heading: "Office & Commercial Assembly", rows: [
{ service: "Single Workstation", price: "from $99", notes: "Desk + chair" },
{ service: "Office Suite (5-10 items)", price: "from $399", notes: "Full office setup" },
{ service: "Cubicle Installation", price: "Custom quote", notes: "Per unit" },
{ service: "Retail Fixture Setup", price: "Custom quote", notes: "Per project" }
] }
],
note: "All prices are starting rates. Final price depends on item complexity, quantity, and location. We provide exact quotes before any work begins — no surprise fees ever.",
packages: [
{ label: "STARTER PACK", price: "$79", desc: "1-2 items. Best for single purchases.", featured: false },
{ label: "HOME PACK", price: "$299", desc: "5-8 items. Full room setup.", featured: true },
{ label: "MOVE-IN PACK", price: "$599", desc: "10+ items. Whole home assembly.", featured: false }
],
steps: [
{ title: "Request", desc: "Call or fill the form — we respond in 2 hours." },
{ title: "We Assess", desc: "We review your items and send an exact quote." },
{ title: "You Approve", desc: "No work begins until you confirm the price." }
],
faq: [
{ q: "Do you charge by the hour or per item?", a: "Per item or per package — never by the hour. You get a flat, upfront price before any work begins — no surprises." },
{ q: "Are there any hidden fees?", a: "No. We provide exact quotes before work begins. No surprise fees ever." },
{ q: "Do you offer discounts for multiple items?", a: "Yes. Our 3-5 item and room packages offer significant savings over single-item pricing." },
{ q: "How do I get an exact quote?", a: "Call (301) 615-8388 or fill out the form. We respond within 2 hours." },
{ q: "What payment methods do you accept?", a: "We accept all major credit cards, cash, Venmo and Zelle." },
{ q: "Do you offer financing?", a: "Contact us to discuss financing options for larger commercial projects." }
]
}
},
/* ===================== SERVICE ===================== */
{
id: 41, title: "Home Furniture Assembly", slug: "home-furniture-assembly", template: "service", parent: "residential",
price: 79,
hero: { eyebrow: "RESIDENTIAL ASSEMBLY", h1: "Professional Home Furniture Assembly", description: "IKEA, Wayfair, Ashley, Amazon — any brand, any room. Built solid, squared up, packaging hauled away.", image: "/wp-content/uploads/2026/06/home_furniture_hero_section.jpg" },
trust: ["Licensed & Insured", "Same-Day Available", "2,500+ Jobs Completed", "90-Day Warranty"],
content: {
photos: ["/wp-content/uploads/2026/06/home_furniture_stepone.jpg", "/wp-content/uploads/2026/06/home_furniture_steptwo.jpg", "/wp-content/uploads/2026/06/home_furniture_stepthree.jpg"],
servicePhoto: "/wp-content/uploads/2026/06/home_furniture_stepfour.jpg",
overview: { heading: "Built right, in any room.", body: "Placeholder text. Drop the service overview here — what's included, the brands we cover, floor protection and cleanup, and why customers choose All Pro Assemble." },
article: [
{ body: "Flat-pack furniture promises a simple afternoon project — until you're surrounded by forty numbered parts, a bag of spare dowels, and instructions that skip step seven. All Pro Assemble takes that off your plate. Our crews assemble furniture from every major brand — IKEA, Wayfair, Ashley, Pottery Barn, Amazon and more — quickly, correctly, and with every piece left squared, stable and ready to use." },
{ heading: "Why professional assembly matters", body: "A wobbly bookshelf or a dresser that leans isn't just annoying — it's a safety risk, especially with children in the home. Professional assembly means cam locks are fully seated, joints are torqued evenly, and tall pieces are anchored to the wall to prevent tip-overs. We catch the small mistakes that cause big problems later: stripped screws, mismatched panels, and drawers that bind. The result is furniture that looks right and lasts the way the manufacturer intended." },
{ heading: "What to expect", body: "Booking takes about two minutes, and you'll get an upfront, flat price before we arrive — never an hourly meter. Your assembler shows up on time, lays down floor protection, and works clean from box to finished piece. When the build is done we level and test everything, anchor what needs anchoring, and haul away every box and scrap of packaging. All that's left for you to do is enjoy the room — backed by our 90-day workmanship warranty." }
],
included: [
"Any brand — IKEA, Wayfair, Ashley, Amazon and more",
"Beds anchored, drawers aligned, doors leveled",
"All packaging, boxes and debris hauled away",
"Floors and walls protected while we work",
"90-day workmanship warranty on every build"
],
pricing: {
labor: "$79", laborLabel: "Single-item assembly",
addons: [
{ name: "Each additional item (same visit)", price: "+$39" },
{ name: "Wall-anchoring & safety tip-over kit", price: "+$25" },
{ name: "Packaging & debris haul-away", price: "+$20" },
{ name: "Same-day priority scheduling", price: "+$40" }
],
note: "Starting rates. You get an exact, flat quote before any work begins — never by the hour, no surprise fees."
},
rooms: [
{ title: "Living Room Assembly", items: "Sofas, sectionals, loveseats, entertainment centers, TV stands, coffee tables, side tables, bookshelves" },
{ title: "Bedroom Furniture Assembly", items: "Beds, dressers, wardrobes, nightstands, vanity tables, mirrors, headboards, platform beds, bunk beds" },
{ title: "Dining Room Assembly", items: "Dining tables, chairs, bar stools, buffets, sideboards, china cabinets, bar carts, wine racks" },
{ title: "Home Office Assembly", items: "Desks, standing desks, L-shaped desks, office chairs, bookshelves, filing cabinets, monitor stands" },
{ title: "Kids & Nursery Assembly", items: "Cribs, toddler beds, bunk beds, loft beds, trundle beds, changing tables, toy storage, activity tables" },
{ title: "Playroom & Game Room Assembly", items: "Ping pong tables, pool tables, foosball tables, air hockey tables, gaming chairs, bean bags, play kitchens" },
{ title: "Basement & Man Cave Assembly", items: "Bar furniture, bar stools, media room seating, gaming setups, home theater seating, shelving" },
{ title: "Kitchen Furniture Assembly", items: "Kitchen islands, bar carts, kitchen carts, butcher blocks, pantry shelving, baker's racks" },
{ title: "Bathroom Furniture Assembly", items: "Vanities, bathroom shelving, over-toilet storage, towel racks, medicine cabinets, linen towers" },
{ title: "Entryway & Mudroom Assembly", items: "Entryway benches, coat racks, shoe storage, hall tables, umbrella stands, mudroom lockers" },
{ title: "Laundry Room Assembly", items: "Laundry shelving units, folding tables, hamper storage, drying racks, laundry cabinets" },
{ title: "Backyard & Patio Assembly", items: "Patio furniture sets, outdoor sofas, loungers, Adirondack chairs, outdoor dining sets, fire pits" },
{ title: "Deck & Porch Assembly", items: "Deck furniture, porch swings, pergolas, gazebos, shade sails, deck storage boxes" },
{ title: "Kids Outdoor Play Assembly", items: "Swing sets, playsets, climbing walls, zip lines, monkey bars, sandbox assemblies, water tables" },
{ title: "Sports & Recreation Assembly", items: "Trampolines, basketball hoops, batting cages, pitching machines, soccer goals, volleyball nets" },
{ title: "Garage & Workshop Installation", items: "Garage shelving, overhead storage racks, workbenches, tool cabinets, pegboard systems, tire racks" }
],
chips: ["Beds", "Murphy Beds", "Trundle Beds", "Bunk Beds", "Canopy Beds", "Sleigh Beds", "Platform Beds", "Dressers & Wardrobes", "Sofas & Sectionals", "Dining Tables & Chairs", "Entertainment Centers", "Coffee Tables", "Bookcases & Shelving", "Nightstands", "Bedroom Sets", "Cribs & Toddler Beds", "Custom Beds", "Kids Bunk Beds with Slides"],
why: ["Skilled and experienced technicians", "Fully licensed and insured", "Same-day and weekend availability", "Over 2,500 successful projects completed", "Transparent upfront pricing, no surprise fees", "Serving MD, PA, DC, DE, and Northern Virginia"],
related: [
{ title: "Recreational Equipment", desc: "Playsets, trampolines, game tables and more.", link: "/services/residential/recreational-equipment" },
{ title: "Fitness Equipment", desc: "Home gyms, treadmills and exercise bikes.", link: "/services/residential/fitness-equipment-assembly" },
{ title: "Outdoor Furniture", desc: "Patio sets, loungers and outdoor storage.", link: "/services/residential/outdoor-furniture-equipment" },
{ title: "IKEA Furniture Assembly", desc: "Flat-pack specialists for every IKEA line.", link: "/services/residential/home-furniture-assembly/ikea-furniture-assembly" }
],
faq: [
{ q: "How much does furniture assembly cost?", a: "Most single-item assemblies start at $79, with full room packages priced lower per piece. You get an upfront, exact price before any work begins — no surprises." },
{ q: "Can you disassemble furniture for moving?", a: "Yes. We disassemble, transport support and reassemble furniture at your new location." },
{ q: "Do you anchor tall furniture like wardrobes?", a: "Yes. We recommend and provide wall anchoring for safety and stability." },
{ q: "Do you assemble cribs and kids furniture?", a: "Yes. We install cribs, toddler beds and kids furniture with child safety best practices." },
{ q: "How long does assembly take?", a: "Small items under an hour. Larger projects vary. Estimates provided after item review." },
{ q: "Do you service all DMV areas?", a: "Yes. We serve Maryland, Washington DC, Virginia, Pennsylvania and Delaware." }
]
}
}
];
/* ===================== GENERATED PLACEHOLDER PAGES ===================== */
var EXTRA_PAGES = [
/* --- SERVICE pages (42–58) --- */
mkService(42, "recreational-equipment", "residential", "RESIDENTIAL ASSEMBLY", "Recreational Equipment Assembly", "Playsets, trampolines, game tables, basketball hoops and more — assembled safely in your backyard."),
mkService(43, "outdoor-furniture-equipment", "residential", "RESIDENTIAL ASSEMBLY", "Outdoor Furniture & Equipment Assembly", "Patio sets, grills, pergolas, fire pits and outdoor storage — your outdoor space, ready to enjoy."),
mkService(44, "fitness-equipment-assembly", "residential", "RESIDENTIAL ASSEMBLY", "Fitness Equipment Assembly", "Treadmills, home gyms, exercise bikes and weight equipment — assembled safely and correctly."),
mkService(45, "office-furniture-assembly", "commercial", "COMMERCIAL ASSEMBLY", "Office Furniture Assembly", "Desks, chairs, cubicles and conference room furniture — your office, set up and ready to work."),
mkService(46, "retail-furniture-installation", "commercial", "COMMERCIAL ASSEMBLY", "Retail Furniture Installation", "Display fixtures, shelving and retail furniture — installed quickly with minimal disruption."),
mkService(47, "restaurant-equipment-assembly", "commercial", "COMMERCIAL ASSEMBLY", "Restaurant Equipment Assembly", "Tables, chairs, bar stools and restaurant fixtures — assembled and ready for opening day."),
mkService(48, "medical-office-setup", "commercial", "COMMERCIAL ASSEMBLY", "Medical Office Setup", "Exam tables, storage units and medical office furniture — set up precisely and safely."),
mkService(49, "educational-institution-furniture", "commercial", "COMMERCIAL ASSEMBLY", "Educational Institution Furniture Assembly", "Classroom desks, chairs, shelving and storage — assembled efficiently for schools and universities."),
mkService(50, "furniture-disassembly", "specialized", "SPECIALIZED SERVICES", "Furniture Disassembly", "Careful disassembly for moving, storage or donation — we take it apart so you don't have to."),
mkService(51, "white-glove-delivery", "specialized", "SPECIALIZED SERVICES", "White Glove Delivery & Setup", "Premium delivery, unpacking and full setup — we receive your delivery and set everything up perfectly."),
mkService(52, "furniture-relocation", "specialized", "SPECIALIZED SERVICES", "Furniture Relocation", "Move furniture within your home or to a new location — carefully and without damage."),
mkService(53, "installation-services", "specialized", "SPECIALIZED SERVICES", "Installation Services", "TV mounting, shelving, mirrors and wall installations — done right the first time."),
mkService(54, "removal-recycling", "specialized", "SPECIALIZED SERVICES", "Removal & Recycling", "Responsible furniture removal and recycling — we haul it away and dispose of it properly."),
mkService(55, "packaging-debris-removal", "add-ons", "SERVICE ADD-ONS", "Packaging & Debris Removal", "We haul away all boxes, packaging and debris after assembly — leave it spotless."),
mkService(56, "furniture-protection", "add-ons", "SERVICE ADD-ONS", "Furniture Protection", "Floor and wall protection during assembly — your home stays safe while we work."),
mkService(57, "same-day-service", "add-ons", "SERVICE ADD-ONS", "Same-Day Assembly Service", "Priority same-day scheduling for urgent jobs — call before noon for same-day availability."),
mkService(58, "emergency-assembly", "add-ons", "SERVICE ADD-ONS", "Emergency Assembly Service", "Emergency assembly available 24/7 — call (301) 615-8388 for immediate assistance."),
/* --- LOCATION sub-pages (59–74) --- */
mkLocationSub(59, "northwest-dc", "washington-dc", "Assembly Services in Northwest DC", ["Dupont Circle", "Georgetown", "Adams Morgan", "Cleveland Park", "Tenleytown", "Woodley Park", "Chevy Chase DC", "Friendship Heights", "Columbia Heights", "Mount Pleasant", "Petworth", "Brightwood"]),
mkLocationSub(60, "northeast-dc", "washington-dc", "Assembly Services in Northeast DC", ["Brookland", "Eckington", "Edgewood", "Fort Lincoln", "Gateway", "Michigan Park", "Woodridge", "Brentwood", "Ivy City", "Trinidad", "Langdon", "Deanwood"]),
mkLocationSub(61, "southeast-dc", "washington-dc", "Assembly Services in Southeast DC", ["Capitol Hill", "Eastern Market", "Anacostia", "Congress Heights", "Shipley Terrace", "Navy Yard", "Barracks Row", "Hillcrest", "Fort Dupont", "Fairlawn"]),
mkLocationSub(62, "southwest-dc", "washington-dc", "Assembly Services in Southwest DC", ["The Wharf", "Waterfront", "Fort McNair", "Buzzard Point", "Lansburgh", "Greenleaf", "Carrollsburg"]),
mkLocationSub(63, "montgomery-county", "maryland", "Assembly Services in Montgomery County MD", ["Rockville", "Gaithersburg", "Germantown", "Silver Spring", "Bethesda", "Chevy Chase", "Potomac", "Wheaton", "Aspen Hill", "Olney", "Damascus", "Clarksburg"]),
mkLocationSub(64, "prince-georges-county", "maryland", "Assembly Services in Prince George's County MD", ["Bowie", "Laurel", "Greenbelt", "College Park", "Hyattsville", "Landover", "Oxon Hill", "Temple Hills", "Clinton", "Fort Washington", "Upper Marlboro", "Suitland"]),
mkLocationSub(65, "anne-arundel-county", "maryland", "Assembly Services in Anne Arundel County MD", ["Annapolis", "Glen Burnie", "Severn", "Odenton", "Crofton", "Pasadena", "Arnold", "Millersville", "Severna Park", "Linthicum", "Hanover", "Jessup"]),
mkLocationSub(66, "other-md-counties", "maryland", "Assembly Services in Maryland", ["Baltimore City", "Howard County", "Frederick County", "Carroll County", "Harford County", "Charles County", "Calvert County", "St. Mary's County", "Columbia", "Ellicott City", "Towson", "Frederick"]),
mkLocationSub(67, "northern-virginia", "virginia", "Assembly Services in Northern Virginia", ["Fairfax", "Reston", "Herndon", "Ashburn", "Sterling", "Chantilly", "Centreville", "Manassas", "Woodbridge", "Dale City", "Lorton", "Burke"]),
mkLocationSub(68, "alexandria", "virginia", "Assembly Services in Alexandria VA", ["Old Town Alexandria", "Del Ray", "Arlandria", "West End", "Cameron Station", "Eisenhower Valley", "Potomac Yard", "Seminary Hill", "Rosemont", "Beverly Hills"]),
mkLocationSub(69, "arlington", "virginia", "Assembly Services in Arlington VA", ["Rosslyn", "Ballston", "Clarendon", "Pentagon City", "Crystal City", "Courthouse", "Lyon Village", "Cherrydale", "Shirlington", "Aurora Highlands", "Nauck", "Westover"]),
mkLocationSub(70, "fairfax-county", "virginia", "Assembly Services in Fairfax County VA", ["Tysons", "McLean", "Vienna", "Oakton", "Reston", "Herndon", "Centreville", "Chantilly", "Springfield", "Burke", "Annandale", "Falls Church"]),
mkLocationSub(71, "other-va-areas", "virginia", "Assembly Services in Virginia", ["Prince William County", "Loudoun County", "Stafford County", "Fredericksburg", "Leesburg", "Purcellville", "Haymarket", "Gainesville", "Warrenton", "Culpeper", "Winchester", "Front Royal"]),
mkLocationSub(72, "philadelphia-area", "pennsylvania", "Assembly Services in Philadelphia PA", ["Center City", "South Philadelphia", "North Philadelphia", "West Philadelphia", "Northeast Philadelphia", "Roxborough", "Manayunk", "Chestnut Hill", "Mount Airy", "Germantown PA", "Fishtown", "Northern Liberties"]),
mkLocationSub(73, "other-pa-regions", "pennsylvania", "Assembly Services in Pennsylvania", ["Levittown", "Phoenixville", "King of Prussia", "Norristown", "Lansdale", "Doylestown", "Pottstown", "Exton", "Wayne", "Bucks County", "Chester County", "Delaware County PA"]),
mkLocationSub(74, "new-castle-county", "delaware", "Assembly Services in New Castle County DE", ["Wilmington", "Newark", "Bear", "Hockessin", "Pike Creek", "Claymont", "Glasgow", "Middletown", "Smyrna", "New Castle", "Elsmere", "Brookside"]),
/* --- SUPPORT pages (23–40); id 23 slug "commercial-pricing" to avoid colliding with category "commercial" --- */
mkSupport(23, "commercial-pricing", "pricing", "TRANSPARENT PRICING", "Commercial Assembly Pricing"),
mkSupport(24, "service-packages", "pricing", "SAVE MORE", "Assembly Service Packages"),
mkSupport(25, "free-estimate", "pricing", "NO OBLIGATION QUOTE", "Get a Free Estimate"),
mkSupport(26, "assembly-guides", "resources", "RESOURCES", "Assembly Guides & Tips"),
mkSupport(27, "moving-storage-tips", "resources", "RESOURCES", "Moving & Storage Tips"),
mkSupport(28, "maintenance-guides", "resources", "RESOURCES", "Furniture Maintenance Guides"),
mkSupport(29, "product-manuals-library", "resources", "RESOURCES", "Product Manuals Library"),
mkSupport(30, "video-tutorials", "resources", "RESOURCES", "Assembly Video Tutorials"),
mkSupport(31, "assembly-tips-tricks", "blog", "BLOG", "Assembly Tips & Tricks"),
mkSupport(32, "product-reviews", "blog", "BLOG", "Product Reviews"),
mkSupport(33, "industry-news", "blog", "BLOG", "Industry News"),
mkSupport(34, "case-studies", "blog", "BLOG", "Case Studies"),
mkSupport(35, "seasonal-content", "blog", "BLOG", "Seasonal Assembly Content"),
mkSupport(36, "reviews-testimonials", "customer-center", "CUSTOMER CENTER", "Customer Reviews & Testimonials"),
mkSupport(37, "before-after-gallery", "customer-center", "CUSTOMER CENTER", "Before & After Gallery"),
mkSupport(38, "faq", "customer-center", "CUSTOMER CENTER", "Frequently Asked Questions"),
mkSupport(39, "service-guarantee", "customer-center", "CUSTOMER CENTER", "Our Service Guarantee"),
mkSupport(40, "customer-support", "customer-center", "CUSTOMER CENTER", "Customer Support")
];
PAGES_DATA = PAGES_DATA.concat(EXTRA_PAGES);
/* ===================== SUB-SERVICE PAGES (from SUBSERVICE_MAP) ===================== */
var SUB_PAGES = [];
var __subId = 75;
Object.keys(SUBSERVICE_MAP).forEach(function (catSlug) {
var cat = SUBSERVICE_MAP[catSlug];
var eyebrow = cat.parent === "commercial" ? "COMMERCIAL ASSEMBLY" : "RESIDENTIAL ASSEMBLY";
cat.subs.forEach(function (s) {
SUB_PAGES.push(mkService(
__subId++, s.slug, cat.parent, eyebrow, s.label,
s.label + " specialists serving the DMV — same-day availability, upfront pricing, licensed and insured."
));
});
});
PAGES_DATA = PAGES_DATA.concat(SUB_PAGES);
Object.assign(window, { PAGES_DATA: PAGES_DATA, SUBSERVICE_MAP: SUBSERVICE_MAP, DMV_AREAS: DMV_AREAS, trustIcon: trustIcon, svcIcon: svcIcon, roomIcon: roomIcon, statIcon: statIcon, localIcon: localIcon });
/* ===== js/app.jsx ===== */
/* app.jsx — compose sections + Tweaks */
const { useState: useS, useEffect: useE } = React;
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"accent": "amber",
"font": "default",
"hero": "split",
"quote": "wizard",
"motion": "moderate"
}/*EDITMODE-END*/;
const MOTION_MAP = { subtle: 0.5, moderate: 1, lively: 1.6 };
function scrollToId(id) {
const el = document.getElementById(id);
if (!el) return;
const y = el.getBoundingClientRect().top + window.scrollY - 76;
window.scrollTo({ top: y, behavior: "smooth" });
}
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
useE(() => {
const r = document.documentElement;
r.setAttribute("data-accent", t.accent);
r.setAttribute("data-font", t.font);
r.style.setProperty("--motion", MOTION_MAP[t.motion] ?? 1);
}, [t.accent, t.font, t.motion]);
const onQuote = () => scrollToId("quote");
return (
setTweak("hero", v)}
/>
setTweak("quote", v)}
/>
setTweak("accent", nameForSwatch(v))}
/>
setTweak("font", v)}
/>
setTweak("motion", v)}
/>
);
}
/* map accent names <-> hex for the color swatch control */
const ACCENT_HEX = { amber: "#E08A2B", blue: "#2F6FE0", green: "#1F9A63", red: "#E0533B" };
function swatchFor(name) { return ACCENT_HEX[name]; }
function nameForSwatch(hex) { return Object.keys(ACCENT_HEX).find((k) => ACCENT_HEX[k] === hex) || "amber"; }
/* data-driven route switch (no router in this prototype): read the hash, find the
matching page in PAGES_DATA by slug, and render the template named by page.template
inside the real Nav + Footer. #sitemap → SiteMap. No match → the landing page. */
const TEMPLATES = {
service: ServicePage,
category: CategoryPage,
location: LocationPage,
support: SupportPage,
};
/* group a page into one of the 6 nav sections for the sitemap */
function sectionFor(p) {
const s = p.parent;
if (["services", "residential", "commercial", "specialized", "add-ons"].indexOf(s) >= 0) return "Services";
if (["areas", "washington-dc", "maryland", "virginia", "pennsylvania", "delaware"].indexOf(s) >= 0) return "Locations";
if (s === "pricing") return "Pricing";
if (s === "resources") return "Resources";
if (s === "blog") return "Blog";
if (s === "customer-center") return "Customer Center";
return "Other";
}
function SiteMap() {
const data = window.PAGES_DATA || [];
const order = ["Services", "Locations", "Pricing", "Resources", "Blog", "Customer Center"];
const groups = {};
order.forEach((o) => { groups[o] = []; });
data.forEach((p) => { const sec = sectionFor(p); (groups[sec] = groups[sec] || []).push(p); });
return (
Site map
All Pro Assemble — Site Map
Every page in the redesign. Click any link to preview that page.
{order.map((sec) => (groups[sec] && groups[sec].length > 0) ? (
) : null)}
);
}
function Root() {
const [route, setRoute] = useS(() => {
// A URL hash that names a real page (or the sitemap) is a client-side route —
// honor it even when the server set __ROUTE_SLUG__ (e.g. the homepage linking
// to a service). A non-page hash (#top, #services, #quote) is just an in-page
// anchor, so fall back to __ROUTE_SLUG__ and let the browser scroll to it.
const h = (window.location.hash || "").replace(/^#/, "");
const data = window.PAGES_DATA || [];
if (h && (h === "sitemap" || data.some((p) => p.slug === h))) return "#" + h;
return window.__ROUTE_SLUG__ ? ("#" + window.__ROUTE_SLUG__) : (window.location.hash || "");
});
useE(() => {
const f = () => { setRoute(window.location.hash || ""); window.scrollTo(0, 0); };
window.addEventListener("hashchange", f);
return () => window.removeEventListener("hashchange", f);
}, []);
const slug = route.replace(/^#/, "");
const go = () => { window.location.hash = "#free-estimate"; };
if (slug === "sitemap") {
return (
);
}
const data = window.PAGES_DATA || [];
const page = slug ? data.find((p) => p.slug === slug) : null;
const Template = page && TEMPLATES[page.template];
if (page && Template) {
return (
);
}
return ;
}
ReactDOM.createRoot(document.getElementById("root")).render( );