diff --git a/.gitignore b/.gitignore index 26d9f4f..aafcb34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules/ -_site/ .DS_Store *.log diff --git a/_site/about/index.html b/_site/about/index.html new file mode 100644 index 0000000..fa0c978 --- /dev/null +++ b/_site/about/index.html @@ -0,0 +1,284 @@ + + + + + + About Diana | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

About Diana

+

Built to fix things.
Built to last.

+
+
+ + +
+
+ +
+ Diana on a sailboat with the Golden Gate Bridge in the background. +
+ +
+

My story

+

A lifetime of craft,
sharpened by aerospace.

+ +
+

My passion has always been building and fixing things. My early professional experience includes interior and exterior residential painting. As a tile setter, I realized I could provide better services and began my own tile company, where I provided both commercial and residential installations for over 200 customers.

+

As my passion continued to develop, it led me to an opportunity to work as an airline mechanic and satellite engineer. Aerospace gave me the opportunity to manage large multimillion-dollar projects where I gained project management, engineering design, and material procurement expertise.

+

After a long career in aerospace, I wanted to return to my passion of working with people and help them complete the project of their dreams.

+
+ +
+

Over 200 tile installations. Multimillion-dollar aerospace projects. Now she's working on yours.

+
+ + Request a Quote +
+ +
+
+ + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/assets/Group-433.png b/_site/assets/Group-433.png new file mode 100644 index 0000000..52abe0a Binary files /dev/null and b/_site/assets/Group-433.png differ diff --git a/_site/assets/boat-work-min-scaled.jpg b/_site/assets/boat-work-min-scaled.jpg new file mode 100644 index 0000000..9ad8b18 Binary files /dev/null and b/_site/assets/boat-work-min-scaled.jpg differ diff --git a/_site/assets/diana_photo-min-1-scaled.webp b/_site/assets/diana_photo-min-1-scaled.webp new file mode 100644 index 0000000..400b141 Binary files /dev/null and b/_site/assets/diana_photo-min-1-scaled.webp differ diff --git a/_site/assets/electrical-1-min.jpg b/_site/assets/electrical-1-min.jpg new file mode 100644 index 0000000..9b20714 Binary files /dev/null and b/_site/assets/electrical-1-min.jpg differ diff --git a/_site/assets/electrical-2-min.jpg b/_site/assets/electrical-2-min.jpg new file mode 100644 index 0000000..63b8efa Binary files /dev/null and b/_site/assets/electrical-2-min.jpg differ diff --git a/_site/assets/electrical-3-min.jpg b/_site/assets/electrical-3-min.jpg new file mode 100644 index 0000000..a5eba81 Binary files /dev/null and b/_site/assets/electrical-3-min.jpg differ diff --git a/_site/assets/electrical-4-min.jpg b/_site/assets/electrical-4-min.jpg new file mode 100644 index 0000000..4b1fd89 Binary files /dev/null and b/_site/assets/electrical-4-min.jpg differ diff --git a/_site/assets/electrical-5-min.jpg b/_site/assets/electrical-5-min.jpg new file mode 100644 index 0000000..a7dcd1b Binary files /dev/null and b/_site/assets/electrical-5-min.jpg differ diff --git a/_site/assets/electrical-6-min-1.jpg b/_site/assets/electrical-6-min-1.jpg new file mode 100644 index 0000000..e5455fd Binary files /dev/null and b/_site/assets/electrical-6-min-1.jpg differ diff --git a/_site/assets/electrical-7-min-768x1165.jpg b/_site/assets/electrical-7-min-768x1165.jpg new file mode 100644 index 0000000..85b4f11 Binary files /dev/null and b/_site/assets/electrical-7-min-768x1165.jpg differ diff --git a/_site/assets/electrical-7-min.jpg b/_site/assets/electrical-7-min.jpg new file mode 100644 index 0000000..ac6be7a Binary files /dev/null and b/_site/assets/electrical-7-min.jpg differ diff --git a/_site/assets/electrical-8-min.jpg b/_site/assets/electrical-8-min.jpg new file mode 100644 index 0000000..242aa3d Binary files /dev/null and b/_site/assets/electrical-8-min.jpg differ diff --git a/_site/assets/grab-bar-min.png b/_site/assets/grab-bar-min.png new file mode 100644 index 0000000..dde013e Binary files /dev/null and b/_site/assets/grab-bar-min.png differ diff --git a/_site/assets/painting-1-min-1429x1536.png b/_site/assets/painting-1-min-1429x1536.png new file mode 100644 index 0000000..9d0a891 Binary files /dev/null and b/_site/assets/painting-1-min-1429x1536.png differ diff --git a/_site/assets/painting-1-min.png b/_site/assets/painting-1-min.png new file mode 100644 index 0000000..52bc440 Binary files /dev/null and b/_site/assets/painting-1-min.png differ diff --git a/_site/assets/painting-2-min.png b/_site/assets/painting-2-min.png new file mode 100644 index 0000000..6b0ae20 Binary files /dev/null and b/_site/assets/painting-2-min.png differ diff --git a/_site/assets/painting-3-min.jpg b/_site/assets/painting-3-min.jpg new file mode 100644 index 0000000..fb8c11e Binary files /dev/null and b/_site/assets/painting-3-min.jpg differ diff --git a/_site/assets/painting-4-min.jpg b/_site/assets/painting-4-min.jpg new file mode 100644 index 0000000..aff9ca6 Binary files /dev/null and b/_site/assets/painting-4-min.jpg differ diff --git a/_site/assets/painting-5-min.jpg b/_site/assets/painting-5-min.jpg new file mode 100644 index 0000000..548958d Binary files /dev/null and b/_site/assets/painting-5-min.jpg differ diff --git a/_site/assets/painting-6-min.jpg b/_site/assets/painting-6-min.jpg new file mode 100644 index 0000000..6bb721d Binary files /dev/null and b/_site/assets/painting-6-min.jpg differ diff --git a/_site/assets/painting-7-min.jpg b/_site/assets/painting-7-min.jpg new file mode 100644 index 0000000..e5d1b62 Binary files /dev/null and b/_site/assets/painting-7-min.jpg differ diff --git a/_site/assets/painting-8-min.jpg b/_site/assets/painting-8-min.jpg new file mode 100644 index 0000000..edee1d3 Binary files /dev/null and b/_site/assets/painting-8-min.jpg differ diff --git a/_site/assets/plumbing-2-min.png b/_site/assets/plumbing-2-min.png new file mode 100644 index 0000000..901f1c8 Binary files /dev/null and b/_site/assets/plumbing-2-min.png differ diff --git a/_site/assets/plumbing-3-min.png b/_site/assets/plumbing-3-min.png new file mode 100644 index 0000000..a6171ac Binary files /dev/null and b/_site/assets/plumbing-3-min.png differ diff --git a/_site/assets/plumbing-4-min.png b/_site/assets/plumbing-4-min.png new file mode 100644 index 0000000..ebc1e44 Binary files /dev/null and b/_site/assets/plumbing-4-min.png differ diff --git a/_site/assets/plumbing-5-min.png b/_site/assets/plumbing-5-min.png new file mode 100644 index 0000000..f71a81f Binary files /dev/null and b/_site/assets/plumbing-5-min.png differ diff --git a/_site/assets/plumbing-6-min.png b/_site/assets/plumbing-6-min.png new file mode 100644 index 0000000..a2eab5c Binary files /dev/null and b/_site/assets/plumbing-6-min.png differ diff --git a/_site/assets/plumbing-7-min.png b/_site/assets/plumbing-7-min.png new file mode 100644 index 0000000..f0775df Binary files /dev/null and b/_site/assets/plumbing-7-min.png differ diff --git a/_site/assets/plumbing-8-min.png b/_site/assets/plumbing-8-min.png new file mode 100644 index 0000000..80092c1 Binary files /dev/null and b/_site/assets/plumbing-8-min.png differ diff --git a/_site/assets/security-garage-door-min.png b/_site/assets/security-garage-door-min.png new file mode 100644 index 0000000..0f4c2fc Binary files /dev/null and b/_site/assets/security-garage-door-min.png differ diff --git a/_site/assets/sheetrocking-1-min.png b/_site/assets/sheetrocking-1-min.png new file mode 100644 index 0000000..7d95cfa Binary files /dev/null and b/_site/assets/sheetrocking-1-min.png differ diff --git a/_site/assets/sheetrocking-2-min.png b/_site/assets/sheetrocking-2-min.png new file mode 100644 index 0000000..823ac36 Binary files /dev/null and b/_site/assets/sheetrocking-2-min.png differ diff --git a/_site/assets/sheetrocking-3-min.png b/_site/assets/sheetrocking-3-min.png new file mode 100644 index 0000000..a01e90a Binary files /dev/null and b/_site/assets/sheetrocking-3-min.png differ diff --git a/_site/assets/sheetrocking-4-min.jpg b/_site/assets/sheetrocking-4-min.jpg new file mode 100644 index 0000000..73911ec Binary files /dev/null and b/_site/assets/sheetrocking-4-min.jpg differ diff --git a/_site/assets/sheetrocking-5-min.png b/_site/assets/sheetrocking-5-min.png new file mode 100644 index 0000000..01f7d75 Binary files /dev/null and b/_site/assets/sheetrocking-5-min.png differ diff --git a/_site/assets/tile-setting-1-min.png b/_site/assets/tile-setting-1-min.png new file mode 100644 index 0000000..8428734 Binary files /dev/null and b/_site/assets/tile-setting-1-min.png differ diff --git a/_site/assets/tile-setting-2-min.png b/_site/assets/tile-setting-2-min.png new file mode 100644 index 0000000..8d83ed6 Binary files /dev/null and b/_site/assets/tile-setting-2-min.png differ diff --git a/_site/assets/tile-setting-3-min.png b/_site/assets/tile-setting-3-min.png new file mode 100644 index 0000000..1ad7339 Binary files /dev/null and b/_site/assets/tile-setting-3-min.png differ diff --git a/_site/assets/tile-setting-4-min.png b/_site/assets/tile-setting-4-min.png new file mode 100644 index 0000000..e809754 Binary files /dev/null and b/_site/assets/tile-setting-4-min.png differ diff --git a/_site/assets/tile-setting-5-min.png b/_site/assets/tile-setting-5-min.png new file mode 100644 index 0000000..a7e1877 Binary files /dev/null and b/_site/assets/tile-setting-5-min.png differ diff --git a/_site/assets/tile-setting-6-min.png b/_site/assets/tile-setting-6-min.png new file mode 100644 index 0000000..373ea47 Binary files /dev/null and b/_site/assets/tile-setting-6-min.png differ diff --git a/_site/assets/tile-setting-7-min.png b/_site/assets/tile-setting-7-min.png new file mode 100644 index 0000000..5d80423 Binary files /dev/null and b/_site/assets/tile-setting-7-min.png differ diff --git a/_site/assets/tile-setting-8-min.png b/_site/assets/tile-setting-8-min.png new file mode 100644 index 0000000..6470b08 Binary files /dev/null and b/_site/assets/tile-setting-8-min.png differ diff --git a/_site/assets/wood-refinishing-1-min.png b/_site/assets/wood-refinishing-1-min.png new file mode 100644 index 0000000..63d3b6b Binary files /dev/null and b/_site/assets/wood-refinishing-1-min.png differ diff --git a/_site/assets/wood-refinishing-2-min.png b/_site/assets/wood-refinishing-2-min.png new file mode 100644 index 0000000..91a17b3 Binary files /dev/null and b/_site/assets/wood-refinishing-2-min.png differ diff --git a/_site/assets/wood-refinishing-3-min.png b/_site/assets/wood-refinishing-3-min.png new file mode 100644 index 0000000..5c198bf Binary files /dev/null and b/_site/assets/wood-refinishing-3-min.png differ diff --git a/_site/assets/wood-refinishing-4-min.png b/_site/assets/wood-refinishing-4-min.png new file mode 100644 index 0000000..a2d1903 Binary files /dev/null and b/_site/assets/wood-refinishing-4-min.png differ diff --git a/_site/assets/wood-refinishing-5-min.png b/_site/assets/wood-refinishing-5-min.png new file mode 100644 index 0000000..eff43ac Binary files /dev/null and b/_site/assets/wood-refinishing-5-min.png differ diff --git a/_site/assets/wood-refinishing-6-min.png b/_site/assets/wood-refinishing-6-min.png new file mode 100644 index 0000000..a0a3ff0 Binary files /dev/null and b/_site/assets/wood-refinishing-6-min.png differ diff --git a/_site/assets/wood-refinishing-7-min.png b/_site/assets/wood-refinishing-7-min.png new file mode 100644 index 0000000..00198c6 Binary files /dev/null and b/_site/assets/wood-refinishing-7-min.png differ diff --git a/_site/assets/wood-refinishing-8-min.png b/_site/assets/wood-refinishing-8-min.png new file mode 100644 index 0000000..69bb1a0 Binary files /dev/null and b/_site/assets/wood-refinishing-8-min.png differ diff --git a/_site/assets/wood-refinishing-9-min.png b/_site/assets/wood-refinishing-9-min.png new file mode 100644 index 0000000..2895bef Binary files /dev/null and b/_site/assets/wood-refinishing-9-min.png differ diff --git a/_site/contact/index.html b/_site/contact/index.html new file mode 100644 index 0000000..d84f9d6 --- /dev/null +++ b/_site/contact/index.html @@ -0,0 +1,387 @@ + + + + + + Request a Quote | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Get in touch

+

Request a Quote.

+

Tell Diana about your project. She responds within 1–2 business days.

+
+
+ + +
+
+ + +
+
+ + + + + + + + +
+ + +

+
+ + +
+ + +

Where is the work located? City or full address.

+

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

What needs to be fixed, replaced, or improved?

+

+

+
+ + + + + + +
+
+ + + + +
+
+ +
+ + + + + + + diff --git a/_site/css/main.css b/_site/css/main.css new file mode 100644 index 0000000..6245ec1 --- /dev/null +++ b/_site/css/main.css @@ -0,0 +1,1907 @@ +/* ============================================================================= + HER — Home Enhancement and Renovation + main.css | Mobile-first production stylesheet + ============================================================================= + + TABLE OF CONTENTS + ----------------- + 1. Design Tokens + 2. Reset & Base + 3. Skip Link + 4. Layout Utilities + 5. Typography Helpers + 6. Navigation + 7. Buttons + 8. Hero (Homepage) + 9. Services Section (Homepage) + 10. Why HER Section + 11. Testimonials Strip (Homepage) + 12. Service Area Section (Homepage) + 13. CTA Band + 14. Footer + 15. Inner Page Hero + 16. Breadcrumb + 17. Service Detail Page + 18. Services Hub Page + 19. About Page + 20. Testimonials Page + 21. Contact / Form Page + 22. Quote Form + 23. Visually Hidden / Honeypot + 24. Animation + + ============================================================================= */ + + +/* ============================================================================= + 1. DESIGN TOKENS + ============================================================================= */ + +:root { + /* Colors */ + --col-graphite: #252B30; + --col-linen: #F4F1EB; + --col-copper: #B5622A; + --col-copper-hover: #9A4E20; + --col-slate: #5C7080; + --col-mist: #E8ECF0; + --col-chalk: #EDEDEB; + --col-ink: #1A1A1A; + --col-white: #FFFFFF; + + /* Typography */ + --font-display: 'Fraunces', Georgia, serif; + --font-body: 'DM Sans', system-ui, sans-serif; + + /* Type scale */ + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.375rem; + --text-2xl: 1.75rem; + --text-3xl: 2.25rem; + --text-4xl: 3rem; + --text-5xl: 4rem; + + /* Spacing (8-point grid) */ + --space-1: 0.5rem; + --space-2: 1rem; + --space-3: 1.5rem; + --space-4: 2rem; + --space-6: 3rem; + --space-8: 4rem; + --space-12: 6rem; + --space-16: 8rem; + + /* Other */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 16px; + --shadow-card: 0 2px 8px rgba(37, 43, 48, 0.08); + --max-width: 1160px; + --nav-height: 64px; +} + + +/* ============================================================================= + 2. RESET & BASE + ============================================================================= */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: var(--font-body); + background: var(--col-linen); + color: var(--col-ink); + line-height: 1.6; +} + +img { + max-width: 100%; + display: block; +} + +h1, +h2, +h3, +h4, +h5 { + margin: 0; + line-height: 1.2; +} + +p { + margin: 0; +} + +ul, +ol { + margin: 0; + padding: 0; +} + +a { + color: var(--col-copper); +} + +/* Global focus ring — copper accent on all interactive elements */ +:focus-visible { + outline: 2px solid var(--col-copper); + outline-offset: 2px; +} + + +/* ============================================================================= + 3. SKIP LINK + ============================================================================= */ + +.skip-link { + position: absolute; + left: 50%; + top: -100%; + transform: translateX(-50%) translateY(0); + background: var(--col-copper); + color: var(--col-white); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 600; + padding: 0.5rem 1rem; + border-radius: var(--radius-sm); + text-decoration: none; + white-space: nowrap; + z-index: 999; + transition: top 0.2s ease; +} + +.skip-link:focus { + top: var(--space-2); +} + + +/* ============================================================================= + 4. LAYOUT UTILITIES + ============================================================================= */ + +.container { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .container { + padding-inline: var(--space-4); + } +} + +.section { + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .section { + padding-block: var(--space-8); + } +} + +.bg-linen { + background: var(--col-linen); +} + +.bg-mist { + background: var(--col-mist); +} + +.bg-graphite { + background: var(--col-graphite); + color: var(--col-white); +} + +.bg-copper { + background: var(--col-copper); + color: var(--col-white); +} + + +/* ============================================================================= + 5. TYPOGRAPHY HELPERS + ============================================================================= */ + +.section-eyebrow { + display: block; + font-family: var(--font-body); + font-weight: 500; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--col-copper); + margin: 0; +} + +.display-title { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-4xl); + line-height: 1.1; + margin: 0; +} + +@media (min-width: 1024px) { + .display-title { + font-size: var(--text-5xl); + } +} + +.lead-text { + font-family: var(--font-body); + font-size: var(--text-lg); + line-height: 1.6; + color: var(--col-slate); + margin: 0; +} + +/* Adapt lead-text for dark backgrounds */ +.bg-graphite .lead-text { + color: rgba(255, 255, 255, 0.8); +} + +.bg-copper .lead-text { + color: rgba(255, 255, 255, 0.85); +} + + +/* ============================================================================= + 6. NAVIGATION + ============================================================================= */ + +.nav { + position: sticky; + top: 0; + z-index: 100; + background: var(--col-graphite); + height: var(--nav-height); + display: flex; + align-items: center; + transition: border-bottom 0.25s ease, box-shadow 0.25s ease; +} + +/* Compact state when user has scrolled */ +.nav--compact { + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); +} + +.nav--compact .nav__inner { + /* Slightly tighter feel — height is fixed by .nav so only visual tweaks here */ + padding-block: 0; +} + +/* Inner row — full-height flex container */ +.nav__inner { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; + height: 100%; +} + +@media (min-width: 768px) { + .nav__inner { + padding-inline: var(--space-4); + } +} + +/* Brand / wordmark */ +.nav__brand { + display: flex; + align-items: center; + gap: 0.6rem; + text-decoration: none; + transition: color 0.2s ease; +} + +.nav__brand-her { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-copper); + letter-spacing: -0.01em; + line-height: 1; + flex-shrink: 0; + transition: color 0.2s ease; +} + +.nav__brand-tagline { + font-family: var(--font-body); + font-size: 0.65rem; + font-weight: 500; + color: var(--col-copper); + line-height: 1.38; + border-left: 1px solid rgba(181, 98, 42, 0.35); + padding-left: 0.6rem; + transition: color 0.2s ease; +} + +/* Georgia italic has a far more elegant ampersand than Fraunces at small sizes */ +/* Shared elegant ampersand — applied via the styleAmp filter in headings + and directly in nav/footer brand markup */ +.amp, +.nav__brand-amp, +.footer__brand-amp { + font-family: Georgia, 'Times New Roman', serif; + font-style: italic; +} + +.nav__brand:hover .nav__brand-her, +.nav__brand:hover .nav__brand-tagline { + color: var(--col-copper-hover); +} + +/* Desktop nav links — hidden on mobile */ +.nav__links { + display: none; + list-style: none; + align-items: center; + gap: var(--space-4); + margin: 0; + padding: 0; +} + +@media (min-width: 1024px) { + .nav__links { + display: flex; + } +} + +.nav__links a { + font-family: var(--font-body); + font-weight: 500; + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.85); + text-decoration: none; + transition: color 0.2s ease; +} + +.nav__links a:hover { + color: var(--col-white); +} + +/* Action cluster (phone icon + hamburger) */ +.nav__actions { + display: flex; + align-items: center; + gap: var(--space-2); +} + +/* Phone icon link — visible on mobile as icon-only CTA */ +.nav__phone { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + color: var(--col-copper); + text-decoration: none; + flex-shrink: 0; + border-radius: var(--radius-sm); + transition: color 0.2s ease; +} + +.nav__phone:hover { + color: var(--col-copper-hover); +} + +/* Hamburger toggle — 44×44 touch target */ +.nav__toggle { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 5px; + width: 44px; + height: 44px; + padding: 0; + background: transparent; + border: none; + color: var(--col-white); + cursor: pointer; + flex-shrink: 0; + border-radius: var(--radius-sm); +} + +/* The three bars drawn with child spans */ +.nav__toggle-bar { + display: block; + width: 22px; + height: 2px; + background: currentColor; + border-radius: 2px; + transform-origin: center; + transition: transform 0.25s ease, opacity 0.25s ease, width 0.25s ease; +} + +/* X state when menu is open */ +.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(1) { + transform: translateY(7px) rotate(45deg); +} + +.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(2) { + opacity: 0; + width: 0; +} + +.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(3) { + transform: translateY(-7px) rotate(-45deg); +} + +/* Hide hamburger on desktop */ +@media (min-width: 1024px) { + .nav__toggle { + display: none; + } + + .nav__phone { + display: none; + } +} + +/* Mobile overlay menu — fullscreen, centered links */ +.nav__overlay { + display: none; + position: fixed; + inset: 0; + background: var(--col-graphite); + z-index: 99; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-4); +} + +.nav__overlay.is-open { + display: flex; +} + +.nav__overlay-links { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-3); + list-style: none; + margin: 0; + padding: 0; +} + +.nav__overlay-links a { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-white); + text-decoration: none; + transition: color 0.2s ease; +} + +.nav__overlay-links a:hover { + color: var(--col-copper); +} + +/* Close button — positioned top-right, aligned with nav height */ +.nav__overlay-close { + position: absolute; + top: calc((var(--nav-height) - 44px) / 2); + right: var(--space-2); + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + background: transparent; + border: none; + color: var(--col-white); + cursor: pointer; + font-size: var(--text-xl); + border-radius: var(--radius-sm); + transition: color 0.2s ease; +} + +.nav__overlay-close:hover { + color: var(--col-copper); +} + +@media (min-width: 768px) { + .nav__overlay-close { + right: var(--space-4); + } +} + + +/* ============================================================================= + 7. BUTTONS + ============================================================================= */ + +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-1); + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + line-height: 1; + border-radius: var(--radius-md); + padding: 0.75rem 1.5rem; + border: 2px solid transparent; + cursor: pointer; + text-decoration: none; + white-space: nowrap; + transition: background-color 0.2s ease, + border-color 0.2s ease, + color 0.2s ease, + box-shadow 0.2s ease; +} + +.btn:focus-visible { + outline: 2px solid var(--col-copper); + outline-offset: 2px; +} + +/* Primary — filled copper */ +.btn--primary { + background: var(--col-copper); + color: var(--col-white); + border-color: var(--col-copper); +} + +.btn--primary:hover { + background: var(--col-copper-hover); + border-color: var(--col-copper-hover); +} + +/* Secondary — outlined copper */ +.btn--secondary { + background: transparent; + color: var(--col-copper); + border-color: var(--col-copper); +} + +.btn--secondary:hover { + background: rgba(181, 98, 42, 0.1); +} + +/* Ghost white — for use on dark backgrounds */ +.btn--ghost-white { + background: transparent; + color: var(--col-white); + border-color: rgba(255, 255, 255, 0.6); +} + +.btn--ghost-white:hover { + background: rgba(255, 255, 255, 0.1); + border-color: var(--col-white); +} + +/* Full-width variant */ +.btn--full { + width: 100%; + justify-content: center; +} + + +/* ============================================================================= + 8. HERO (HOMEPAGE) + ============================================================================= */ + +.hero { + background: var(--col-linen); + padding-top: calc(var(--nav-height) + var(--space-8)); + padding-bottom: var(--space-8); +} + +.hero__inner { + display: flex; + flex-direction: column; + gap: var(--space-6); + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .hero__inner { + padding-inline: var(--space-4); + } +} + +@media (min-width: 1024px) { + .hero__inner { + display: grid; + grid-template-columns: 55% 45%; + align-items: stretch; + gap: var(--space-8); + flex-direction: unset; + } +} + +.hero__content { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +@media (min-width: 1024px) { + .hero__content { + /* No extra padding — section handles outer breathing room. + Image will be exactly the natural height of the text stack. */ + justify-content: flex-start; + } +} + +.hero__actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + align-items: center; +} + +.hero__contact { + display: flex; + align-items: center; + gap: var(--space-1); + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); +} + +.hero__contact a { + color: var(--col-slate); + text-decoration: none; + transition: color 0.2s ease; +} + +.hero__contact a:hover { + color: var(--col-copper); + text-decoration: underline; +} + +/* Image container — stacks below content on mobile, right column on desktop */ +.hero__image { + width: 100%; + border-radius: var(--radius-lg); + overflow: hidden; +} + +.hero__image img { + width: 100%; + border-radius: var(--radius-lg); + aspect-ratio: 3 / 4; + object-fit: cover; + object-position: top; + display: block; +} + +@media (min-width: 1024px) { + .hero__image { + border-radius: var(--radius-lg); + } + + .hero__image img { + /* Fill the content column height exactly; crop from the top + so only Diana's face and shoulders are visible. */ + height: 100%; + object-position: center 8%; + aspect-ratio: unset; + border-radius: 0; + } +} + + +/* ============================================================================= + 9. SERVICES SECTION (HOMEPAGE) + ============================================================================= */ + +.services-section { + background: var(--col-linen); + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .services-section { + padding-block: var(--space-8); + } +} + +.services-section__header { + text-align: center; + margin-bottom: var(--space-6); +} + +.services-section__header .section-eyebrow { + margin-bottom: var(--space-2); +} + +/* Grid: 2 cols mobile, 3 cols desktop */ +.services-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); +} + +@media (min-width: 1024px) { + .services-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Service card */ +.service-card { + display: flex; + flex-direction: column; + background: var(--col-white); + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-card); + text-decoration: none; + color: inherit; + transition: transform 0.25s ease, box-shadow 0.25s ease; +} + +.service-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(37, 43, 48, 0.14); +} + +.service-card__image { + aspect-ratio: 4 / 3; + overflow: hidden; +} + +.service-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.35s ease; +} + +.service-card:hover .service-card__image img { + transform: scale(1.03); +} + +.service-card__body { + display: flex; + flex-direction: column; + flex: 1; + gap: var(--space-1); + padding: var(--space-3); +} + +.service-card__name { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-base); + color: var(--col-ink); + margin: 0; +} + +.service-card__desc { + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); + flex: 1; + margin: 0; +} + +.service-card__arrow { + color: var(--col-copper); + margin-top: auto; + align-self: flex-start; + font-size: var(--text-base); + line-height: 1; + padding-top: var(--space-1); +} + + +/* ============================================================================= + 10. WHY HER SECTION + ============================================================================= */ + +.why-her { + background: var(--col-mist); + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .why-her { + padding-block: var(--space-8); + } +} + +.why-her__header { + text-align: center; + margin-bottom: var(--space-6); +} + +.why-her__header .section-eyebrow { + margin-bottom: var(--space-2); +} + +.why-her__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); +} + +@media (min-width: 1024px) { + .why-her__grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Trust pillar — centered on mobile, left-aligned on md+ */ +.trust-pillar { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: var(--space-2); +} + +@media (min-width: 768px) { + .trust-pillar { + align-items: flex-start; + text-align: left; + } +} + +.trust-pillar__icon { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + background: rgba(181, 98, 42, 0.1); + border-radius: 50%; + color: var(--col-copper); + flex-shrink: 0; + font-size: 1.25rem; +} + +.trust-pillar__title { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-xl); + color: var(--col-ink); + margin: 0; +} + +.trust-pillar__body { + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-slate); + line-height: 1.6; + margin: 0; +} + + +/* ============================================================================= + 11. TESTIMONIALS STRIP (HOMEPAGE) + ============================================================================= */ + +.testimonials-strip { + background: var(--col-linen); + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .testimonials-strip { + padding-block: var(--space-8); + } +} + +.testimonials-strip__header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-6); +} + +.testimonials-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +@media (min-width: 768px) { + .testimonials-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.testimonial-card { + background: var(--col-white); + border-radius: var(--radius-md); + padding: var(--space-4); + box-shadow: var(--shadow-card); +} + +/* Decorative large opening quote mark */ +.testimonial-card__quote { + position: relative; + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-lg); + line-height: 1.55; + color: var(--col-ink); + margin: 0 0 var(--space-3); + padding-top: 1.75rem; +} + +.testimonial-card__quote::before { + content: '\201C'; + position: absolute; + top: -0.5rem; + left: -0.125rem; + font-family: var(--font-display); + font-style: italic; + font-size: 5rem; + line-height: 1; + color: rgba(181, 98, 42, 0.18); + pointer-events: none; + user-select: none; +} + +.testimonial-card__author { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + color: var(--col-copper); + margin: 0; +} + +.testimonial-card__city { + font-family: var(--font-body); + font-size: var(--text-xs); + color: var(--col-slate); + margin: 0.125rem 0 0; +} + + +/* ============================================================================= + 12. SERVICE AREA SECTION (HOMEPAGE) + ============================================================================= */ + +.service-area { + background: var(--col-graphite); + color: var(--col-white); + text-align: center; + padding: var(--space-8) 0; +} + +.service-area__title { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-2xl); + color: var(--col-white); + margin: 0 0 var(--space-3); +} + +.service-area__cities { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--space-1) var(--space-2); + margin-top: var(--space-3); +} + +.service-area__city { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.7); + background: rgba(255, 255, 255, 0.08); + border-radius: var(--radius-sm); + padding: 0.25rem 0.75rem; +} + + +/* ============================================================================= + 13. CTA BAND + ============================================================================= */ + +.cta-band { + background: var(--col-copper); + padding: var(--space-8) 0; + text-align: center; +} + +.cta-band__inner { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .cta-band__inner { + padding-inline: var(--space-4); + } +} + +.cta-band__title { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-3xl); + color: var(--col-white); + line-height: 1.15; + margin: 0 0 var(--space-3); +} + +@media (min-width: 1024px) { + .cta-band__title { + font-size: var(--text-4xl); + } +} + +.cta-band__sub { + font-family: var(--font-body); + font-size: var(--text-lg); + color: rgba(255, 255, 255, 0.85); + margin: 0 0 var(--space-6); +} + + +/* ============================================================================= + 14. FOOTER + ============================================================================= */ + +.footer { + background: var(--col-graphite); + color: rgba(255, 255, 255, 0.75); + padding: var(--space-8) 0 var(--space-4); +} + +.footer__inner { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .footer__inner { + padding-inline: var(--space-4); + } +} + +@media (min-width: 1024px) { + .footer__inner { + grid-template-columns: 1fr 2fr 1fr; + } +} + +.footer__brand { + display: flex; + align-items: center; + gap: 0.6rem; + text-decoration: none; + transition: color 0.2s ease; +} + +.footer__brand-her { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-copper); + letter-spacing: -0.01em; + line-height: 1; + flex-shrink: 0; + transition: color 0.2s ease; +} + +.footer__brand-tagline { + font-family: var(--font-body); + font-size: 0.65rem; + font-weight: 500; + color: var(--col-copper); + line-height: 1.38; + border-left: 1px solid rgba(181, 98, 42, 0.35); + padding-left: 0.6rem; + transition: color 0.2s ease; +} + +/* .footer__brand-amp covered by shared .amp rule in section 6 (Navigation) */ + +.footer__brand:hover .footer__brand-her, +.footer__brand:hover .footer__brand-tagline { + color: var(--col-copper-hover); +} + +.footer__nav-heading { + display: block; + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.1em; + color: rgba(255, 255, 255, 0.4); + margin-bottom: var(--space-2); +} + +.footer__nav-cols { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} + +.footer__nav { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.footer__nav a { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.75); + text-decoration: none; + transition: color 0.2s ease; +} + +.footer__nav a:hover { + color: var(--col-white); +} + +.footer__contact { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.footer__contact a { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.75); + text-decoration: none; + transition: color 0.2s ease; +} + +.footer__contact a:hover { + color: var(--col-white); +} + +/* Bottom strip — copyright / legal */ +.footer__bottom { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin: var(--space-6) auto 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: var(--space-4); + font-family: var(--font-body); + font-size: var(--text-xs); + color: rgba(255, 255, 255, 0.4); + text-align: center; +} + +@media (min-width: 768px) { + .footer__bottom { + padding-inline: var(--space-4); + } +} + + +/* ============================================================================= + 15. INNER PAGE HERO + ============================================================================= */ + +.page-hero { + background: var(--col-graphite); + color: var(--col-white); + padding-top: calc(var(--nav-height) + var(--space-2)); + padding-bottom: var(--space-6); +} + +.page-hero__inner { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .page-hero__inner { + padding-inline: var(--space-4); + } +} + +.page-hero__title { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-4xl); + color: var(--col-white); + line-height: 1.1; + margin-top: var(--space-2); +} + +@media (min-width: 1024px) { + .page-hero__title { + font-size: var(--text-5xl); + } +} + +.page-hero__lead { + font-family: var(--font-body); + font-size: var(--text-lg); + color: rgba(255, 255, 255, 0.8); + max-width: 640px; + margin-top: var(--space-3); + line-height: 1.6; +} + +.page-hero__inner > .btn { + margin-top: var(--space-4); +} + + +/* ============================================================================= + 16. BREADCRUMB + ============================================================================= */ + +.breadcrumb ol { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0 var(--space-1); + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.55); +} + +.breadcrumb li { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.breadcrumb a { + color: rgba(255, 255, 255, 0.55); + text-decoration: none; + transition: color 0.2s ease; +} + +.breadcrumb a:hover { + color: rgba(255, 255, 255, 0.85); +} + +/* Separator injected between list items */ +.breadcrumb li + li::before { + content: '/'; + color: rgba(255, 255, 255, 0.3); + speak: none; +} + +/* Current page */ +.breadcrumb [aria-current] { + color: var(--col-white); +} + + +/* ============================================================================= + 17. SERVICE DETAIL PAGE + ============================================================================= */ + +.service-detail { + padding: var(--space-8) 0; +} + +/* Gallery grid — auto-adapts to image count via :has() where supported */ +.service-detail__gallery { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-3); + margin-bottom: var(--space-8); +} + +@media (min-width: 768px) { + .service-detail__gallery { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .service-detail__gallery { + grid-template-columns: repeat(3, 1fr); + } + + /* When only 2 images, use 2 equal columns */ + .service-detail__gallery:has(figure:nth-child(2):last-child) { + grid-template-columns: repeat(2, 1fr); + } + + /* Single image — full width */ + .service-detail__gallery:has(figure:only-child) { + grid-template-columns: 1fr; + } +} + +.service-detail__gallery figure { + margin: 0; + overflow: hidden; + border-radius: var(--radius-md); +} + +.service-detail__gallery figure img { + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; + display: block; +} + +.service-detail__body { + max-width: 720px; +} + +.service-detail__body h2 { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-2xl); + color: var(--col-ink); + margin: 0 0 var(--space-3); +} + +/* Checkmark list */ +.service-detail__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.service-detail__list li { + display: flex; + align-items: flex-start; + gap: var(--space-2); + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-ink); +} + +.service-detail__list li::before { + content: '\2713'; + color: var(--col-copper); + font-weight: 700; + flex-shrink: 0; + margin-top: 0.1em; +} + +/* Service area callout block */ +.service-detail__area { + margin-top: var(--space-4); + padding: var(--space-3); + background: var(--col-mist); + border-radius: var(--radius-md); + border-left: 3px solid var(--col-copper); + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); +} + +.service-detail__cta, +.service-detail__body > .btn { + margin-top: var(--space-6); +} + + +/* ============================================================================= + 18. SERVICES HUB PAGE + ============================================================================= */ + +.services-hub { + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .services-hub { + padding-block: var(--space-8); + } +} + +.services-hub__intro { + text-align: center; + max-width: 640px; + margin: 0 auto var(--space-8); +} + + +/* ============================================================================= + 19. ABOUT PAGE + ============================================================================= */ + +.about-page { + padding: var(--space-8) 0; +} + +/* Two-column layout on desktop: content 55%, image 45% */ +.about-page__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); + align-items: start; + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .about-page__grid { + padding-inline: var(--space-4); + } +} + +@media (min-width: 1024px) { + .about-page__grid { + grid-template-columns: 55% 45%; + } +} + +.about-page__image img { + width: 100%; + border-radius: var(--radius-lg); + aspect-ratio: 3 / 4; + object-fit: cover; + object-position: top; +} + +.about-page__content { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.about-page__bio { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.75; + color: var(--col-ink); + margin: 0; +} + +.about-page__pullquote { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-copper); + line-height: 1.3; + border-left: 3px solid var(--col-copper); + padding-left: var(--space-4); + margin: var(--space-4) 0; +} + +@media (min-width: 1024px) { + .about-page__pullquote { + font-size: var(--text-3xl); + } +} + + +/* ============================================================================= + 20. TESTIMONIALS PAGE + ============================================================================= */ + +.testimonials-page { + padding: var(--space-8) 0; +} + +.testimonials-page__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +@media (min-width: 768px) { + .testimonials-page__grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.testimonial-full { + background: var(--col-linen); + border-radius: var(--radius-md); + padding: var(--space-6); +} + +.testimonial-full__quote { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-lg); + line-height: 1.6; + color: var(--col-ink); + margin: 0 0 var(--space-4); +} + +.testimonial-full__author { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-base); + color: var(--col-copper); + margin: 0; +} + +.testimonial-full__city { + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); + margin: 0.125rem 0 0; +} + + +/* ============================================================================= + 21. CONTACT / FORM PAGE + ============================================================================= */ + +.contact-page { + padding: var(--space-8) 0; +} + +/* Two-column layout: form 60%, secondary info 40% */ +.contact-page__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); + align-items: start; +} + +@media (min-width: 1024px) { + .contact-page__grid { + grid-template-columns: 60% 40%; + } +} + +/* Secondary contact methods column */ +.contact-secondary { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.contact-secondary__method { + display: flex; + align-items: flex-start; + gap: var(--space-3); +} + +.contact-secondary__icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--col-mist); + border-radius: 50%; + color: var(--col-copper); + flex-shrink: 0; +} + +.contact-secondary__label { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + color: var(--col-ink); + display: block; + margin: 0 0 0.125rem; +} + +.contact-secondary__value a { + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-copper); + text-decoration: none; + transition: text-decoration 0.2s ease; +} + +.contact-secondary__value a:hover { + text-decoration: underline; +} + + +/* ============================================================================= + 22. QUOTE FORM + ============================================================================= */ + +.quote-form { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.form-field { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.form-field label { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + color: var(--col-ink); +} + +.form-field input, +.form-field select, +.form-field textarea { + width: 100%; + border: 1.5px solid var(--col-chalk); + border-radius: var(--radius-md); + padding: 0.75rem 1rem; + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-ink); + background: var(--col-white); + appearance: none; + transition: border-color 0.2s ease, outline-color 0.2s ease; +} + +.form-field input:focus, +.form-field select:focus, +.form-field textarea:focus { + border-color: var(--col-copper); + outline: 2px solid rgba(181, 98, 42, 0.3); + outline-offset: 0; +} + +/* Input-specific */ +.form-field input::placeholder, +.form-field textarea::placeholder { + color: rgba(26, 26, 26, 0.35); +} + +/* Textarea sizing */ +.form-field textarea { + min-height: 120px; + resize: vertical; +} + +@media (min-width: 768px) { + .form-field textarea { + min-height: 160px; + } +} + +/* Validation error state */ +.form-field input[aria-invalid="true"], +.form-field select[aria-invalid="true"], +.form-field textarea[aria-invalid="true"] { + border-color: #C0392B; +} + +.form-field input[aria-invalid="true"]:focus, +.form-field select[aria-invalid="true"]:focus, +.form-field textarea[aria-invalid="true"]:focus { + outline: 2px solid rgba(192, 57, 43, 0.3); +} + +/* Inline error message — hidden until role="alert" is set */ +.form-field__error { + font-family: var(--font-body); + font-size: var(--text-sm); + color: #C0392B; + margin: 0; + display: none; +} + +.form-field__error[role="alert"] { + display: block; +} + +/* Hint text */ +.form-field__hint { + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); + margin: 0; +} + +/* Character counter */ +.form-field__char-count { + font-family: var(--font-body); + font-size: var(--text-xs); + color: var(--col-slate); + text-align: right; + margin: 0; +} + +.form-field__char-count.warn { + color: #B7791F; +} + +/* Submit row — full width on mobile, auto-width on md+ */ +.form__submit { + margin-top: var(--space-2); +} + +.form__submit .btn { + width: 100%; + justify-content: center; +} + +@media (min-width: 768px) { + .form__submit .btn { + width: auto; + min-width: 200px; + } +} + +/* Form-level error banner — hidden until role="alert" present */ +.form-error-banner { + background: #FEF2F2; + border: 1px solid #FECACA; + border-radius: var(--radius-md); + padding: var(--space-3); + font-family: var(--font-body); + font-size: var(--text-sm); + color: #991B1B; + margin-bottom: var(--space-4); + display: none; +} + +.form-error-banner[role="alert"] { + display: block; +} + +/* Success confirmation — hidden until .is-visible is toggled */ +.form-success { + display: none; + background: var(--col-mist); + border-radius: var(--radius-md); + padding: var(--space-6); + text-align: center; +} + +.form-success.is-visible { + display: block; +} + +.form-success__icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + background: rgba(181, 98, 42, 0.1); + border-radius: 50%; + color: var(--col-copper); + font-size: 2rem; + margin: 0 auto var(--space-4); +} + +.form-success__title { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-2xl); + color: var(--col-ink); + margin: 0 0 var(--space-2); +} + +.form-success__body { + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-slate); + line-height: 1.6; + margin: 0; +} + + +/* ============================================================================= + 23. VISUALLY HIDDEN / HONEYPOT + ============================================================================= */ + +/* Accessible screen-reader-only text */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; +} + +/* Spam-trap field — never shown, never submitted by real users */ +.visually-trap { + position: absolute; + opacity: 0; + pointer-events: none; + width: 0; + height: 0; + overflow: hidden; +} + + +/* ============================================================================= + 24. ANIMATION + ============================================================================= */ + +/* All motion-based transitions respect the user's OS preference */ + +@media (prefers-reduced-motion: no-preference) { + /* Scroll-triggered fade-up reveal */ + .fade-up { + opacity: 0; + transform: translateY(20px); + transition: opacity 0.5s ease, transform 0.5s ease; + } + + .fade-up.is-visible { + opacity: 1; + transform: translateY(0); + } + + /* Button scale on hover */ + .btn--primary:hover { + transform: scale(1.02); + } + + /* Nav compact transition — smooth border/shadow appearance */ + .nav { + transition: border-bottom 0.25s ease, box-shadow 0.25s ease; + } + + /* Service card lift */ + .service-card { + transition: transform 0.25s ease, box-shadow 0.25s ease; + } + + .service-card:hover { + transform: translateY(-2px); + } + + /* Image scale inside service card */ + .service-card__image img { + transition: transform 0.35s ease; + } + + /* Hamburger bar animation */ + .nav__toggle-bar { + transition: transform 0.25s ease, opacity 0.25s ease, width 0.25s ease; + } +} + +/* Hard-stop all animations for motion-sensitive users */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + /* Ensure fade-up elements are always visible */ + .fade-up { + opacity: 1; + transform: none; + } +} diff --git a/_site/index.html b/_site/index.html new file mode 100644 index 0000000..5abea46 --- /dev/null +++ b/_site/index.html @@ -0,0 +1,551 @@ + + + + + + Handywoman San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ +
+

San Francisco Handywoman

+

Fix it right.
The first time.

+

Precise, reliable, and easy to work with — Diana brings aerospace-level project coordination to your home improvement project.

+ + +
+ +
+ Diana on a sailboat with the Golden Gate Bridge in the background, smiling. +
+ +
+
+ + +
+
+

What Diana does

+

Services for every room — and beyond.

+
+ + +
+ + +
+
+

Why choose HER

+

What makes Diana different.

+
+ +
+ +
+ +

Reliable & Accountable

+

Diana sets a schedule and sticks to it. No chasing, no surprises — just clear communication and work delivered on time.

+
+ +
+ +

Project Coordination

+

With a background managing multimillion-dollar aerospace projects, Diana brings big-picture thinking and attention to detail to every home job.

+
+ +
+ +

Easy to Work With

+

Homeowners consistently describe working with Diana as refreshingly straightforward — honest pricing, calm approach, and no attitude.

+
+ +
+
+ + +
+
+

Client reviews

+

What people say.

+ All reviews +
+ +
+ + +
+
+

“She is so reliable, talented, hardworking and resourceful.”

+
+

Wendy Brummer

+ +

San Francisco

+ +
+
+
+ + + + + +
+
+

“Really dedicated to the best solution.”

+
+

David Snyder

+ +

Oakland

+ +
+
+
+ + + + +
+
+ + +
+
+

Serving San Francisco and the Bay Area

+

Within 50 miles of San Francisco — from Marin County to Palo Alto and across the East Bay.

+
    + +
  • San Francisco
  • + +
  • Oakland
  • + +
  • Berkeley
  • + +
  • San Jose
  • + +
  • Palo Alto
  • + +
  • Marin County
  • + +
  • Tiburon
  • + +
  • Sausalito
  • + +
  • Daly City
  • + +
  • South San Francisco
  • + +
  • Burlingame
  • + +
  • San Mateo
  • + +
  • Redwood City
  • + +
  • Menlo Park
  • + +
  • Fremont
  • + +
  • Alameda
  • + +
+
+
+ + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/js/main.js b/_site/js/main.js new file mode 100644 index 0000000..71b51b3 --- /dev/null +++ b/_site/js/main.js @@ -0,0 +1,479 @@ +/** + * main.js — HER Home Enhancement and Renovation + * + * Sections: + * 1. Navigation — mobile hamburger (focus trap, Escape, ARIA) + * 2. Navigation — compact + CTA visibility on scroll + * 3. Scroll-triggered fade-up animation (IntersectionObserver) + * 4. Quote form — validation, character counter, submission + */ + +(function () { + 'use strict'; + + /* ============================================================ + 1. Navigation — Mobile Hamburger + ============================================================ */ + + var navToggle = document.querySelector('.nav__toggle'); + var navOverlay = document.querySelector('.nav__overlay'); + var navOverlayClose = document.querySelector('.nav__overlay-close'); + + /** + * Returns an array of focusable elements within a container, + * in DOM order, skipping anything with tabindex="-1". + */ + function getFocusable(container) { + return Array.from( + container.querySelectorAll( + 'a[href], button:not([disabled]), input:not([disabled]), ' + + 'select:not([disabled]), textarea:not([disabled]), ' + + '[tabindex]:not([tabindex="-1"])' + ) + ); + } + + function openNav() { + if (!navOverlay || !navToggle) return; + navOverlay.removeAttribute('hidden'); + navToggle.setAttribute('aria-expanded', 'true'); + navToggle.setAttribute('aria-label', 'Close menu'); + document.body.classList.add('nav-open'); + // Move focus into overlay — first focusable item + var focusable = getFocusable(navOverlay); + if (focusable.length) focusable[0].focus(); + } + + function closeNav() { + if (!navOverlay || !navToggle) return; + navOverlay.setAttribute('hidden', ''); + navToggle.setAttribute('aria-expanded', 'false'); + navToggle.setAttribute('aria-label', 'Open menu'); + document.body.classList.remove('nav-open'); + navToggle.focus(); + } + + if (navToggle) { + navToggle.addEventListener('click', function () { + if (navToggle.getAttribute('aria-expanded') === 'true') { + closeNav(); + } else { + openNav(); + } + }); + } + + if (navOverlayClose) { + navOverlayClose.addEventListener('click', closeNav); + } + + if (navOverlay) { + // Close when a nav link inside the overlay is clicked + navOverlay.addEventListener('click', function (e) { + if (e.target && e.target.tagName === 'A') { + closeNav(); + } + }); + } + + // Keyboard handling: focus trap (Tab/Shift-Tab) and Escape close + document.addEventListener('keydown', function (e) { + if (!navOverlay || navOverlay.hasAttribute('hidden')) return; + + if (e.key === 'Escape') { + closeNav(); + return; + } + + if (e.key === 'Tab') { + var focusable = getFocusable(navOverlay); + if (!focusable.length) { + e.preventDefault(); + return; + } + var first = focusable[0]; + var last = focusable[focusable.length - 1]; + if (e.shiftKey) { + // Shift-Tab on first → wrap to last + if (document.activeElement === first) { + e.preventDefault(); + last.focus(); + } + } else { + // Tab on last → wrap to first + if (document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + } + }); + + + /* ============================================================ + 2. Navigation — Compact on Scroll + ============================================================ */ + + var navEl = document.querySelector('.nav'); + // #nav-cta starts with style="display:none" in HTML; we manage via style.display + var navCta = document.getElementById('nav-cta'); + + function handleNavScroll() { + var y = window.scrollY; + + if (navEl) { + if (y > 80) { + navEl.classList.add('nav--compact'); + } else { + navEl.classList.remove('nav--compact'); + } + } + + if (navCta) { + // Remove the inline display:none to show; restore it to hide + navCta.style.display = y > 400 ? '' : 'none'; + } + } + + window.addEventListener('scroll', handleNavScroll, { passive: true }); + // Apply immediately so state is correct before any scroll occurs + handleNavScroll(); + + + /* ============================================================ + 3. Scroll-triggered Fade-Up Animation + ============================================================ */ + + (function initFadeUp() { + // Respect user's motion preference — CSS already handles the fallback + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + if (!('IntersectionObserver' in window)) return; + + var fadeEls = document.querySelectorAll('.fade-up'); + if (!fadeEls.length) return; + + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + entry.target.classList.add('is-visible'); + observer.unobserve(entry.target); // one-shot — never toggle back + } + }); + }, { threshold: 0.15 }); + + fadeEls.forEach(function (el) { + observer.observe(el); + }); + }()); + + + /* ============================================================ + 4. Quote Form + ============================================================ */ + + (function initQuoteForm() { + + /** POST destination — replace with your API Gateway invoke URL */ + var FORM_ENDPOINT = 'REPLACE_WITH_API_GATEWAY_URL'; + + var form = document.getElementById('quote-form'); + if (!form) return; // form not present on this page — bail out + + // ── Element references ──────────────────────────────────── + + var honeypot = form.querySelector('input[name="website"]'); + var errorBanner = form.querySelector('.form-error-banner'); + var successPanel = form.querySelector('.form-success'); + var submitBtn = form.querySelector('.form__submit'); + var charCountEl = document.getElementById('description-char-count'); + var descriptionEl = document.getElementById('description'); + + var FIELD_IDS = ['name', 'address', 'phone', 'email', 'description']; + + // Save original submit button label so we can restore it after an error + var originalBtnText = submitBtn ? (submitBtn.textContent.trim() || 'Send Request') : 'Send Request'; + + // Ensure the error banner is focusable (needed for focus management) + if (errorBanner && !errorBanner.hasAttribute('tabindex')) { + errorBanner.setAttribute('tabindex', '-1'); + } + + // ── Validation rules ────────────────────────────────────── + + var RULES = { + name: function (val) { + return val.trim().length >= 2 + ? null + : 'Please enter your name.'; + }, + address: function (val) { + return val.trim().length >= 3 + ? null + : 'Please enter the service address or city.'; + }, + phone: function (val) { + return /^[+\d][\d\s\-().]{6,}$/.test(val.trim()) + ? null + : 'Please enter a valid phone number.'; + }, + email: function (val) { + var v = val.trim(); + return (v.indexOf('@') !== -1 && v.indexOf('.') !== -1) + ? null + : 'Please enter a valid email address.'; + }, + description: function (val) { + return val.trim().length >= 10 + ? null + : 'Please describe your project (at least 10 characters).'; + } + }; + + // ── Helper: show / clear individual field errors ────────── + + /** + * Mark a field as invalid and display its error message. + * @param {string} fieldId - The field's id attribute value + * @param {string} message - The error text to display + */ + function showError(fieldId, message) { + var field = document.getElementById(fieldId); + var errorEl = document.getElementById(fieldId + '-error'); + if (field) field.setAttribute('aria-invalid', 'true'); + if (errorEl) { + errorEl.textContent = message; + errorEl.removeAttribute('hidden'); + } + } + + /** + * Mark a field as valid and hide its error message. + * @param {string} fieldId - The field's id attribute value + */ + function clearError(fieldId) { + var field = document.getElementById(fieldId); + var errorEl = document.getElementById(fieldId + '-error'); + if (field) field.setAttribute('aria-invalid', 'false'); + if (errorEl) { + errorEl.textContent = ''; + errorEl.setAttribute('hidden', ''); + } + } + + // ── Helper: validate one field ──────────────────────────── + + /** + * Run validation for a single field. Shows or clears its error. + * @param {string} fieldId + * @returns {boolean} true if valid + */ + function validateField(fieldId) { + var field = document.getElementById(fieldId); + if (!field) return true; // element absent — treat as valid + var rule = RULES[fieldId]; + if (!rule) return true; + var errorMsg = rule(field.value); + if (errorMsg) { + showError(fieldId, errorMsg); + return false; + } + clearError(fieldId); + return true; + } + + // ── Helper: validate all fields ─────────────────────────── + + /** + * Run validation on every form field. Does NOT short-circuit — + * all fields are checked so every error is visible at once. + * @returns {boolean} true only if every field is valid + */ + function validateAll() { + var allValid = true; + FIELD_IDS.forEach(function (id) { + if (!validateField(id)) allValid = false; + }); + return allValid; + } + + // ── Blur-time validation ────────────────────────────────── + + FIELD_IDS.forEach(function (id) { + var field = document.getElementById(id); + if (field) { + field.addEventListener('blur', function () { + validateField(id); + }); + } + }); + + // ── Character counter ───────────────────────────────────── + + if (descriptionEl && charCountEl) { + // Set initial display + charCountEl.textContent = '0 / 2000'; + + descriptionEl.addEventListener('input', function () { + var len = descriptionEl.value.length; + charCountEl.textContent = len + ' / 2000'; + // classList.toggle(cls, force) is supported in all modern browsers + charCountEl.classList.toggle('warn', len > 1800); + }); + } + + // ── Banner helpers ──────────────────────────────────────── + + /** + * Display the error banner with a given message and move focus to it. + * @param {string} message + */ + function showBannerError(message) { + if (!errorBanner) return; + // Update the inner

if present to preserve markup; else use textContent + var bannerP = errorBanner.querySelector('p'); + if (bannerP) { + bannerP.textContent = message; + } else { + errorBanner.textContent = message; + } + errorBanner.removeAttribute('hidden'); + errorBanner.focus(); + } + + function hideBanner() { + if (!errorBanner) return; + errorBanner.setAttribute('hidden', ''); + var bannerP = errorBanner.querySelector('p'); + if (bannerP) { + bannerP.textContent = ''; + } else { + errorBanner.textContent = ''; + } + } + + // ── Success display ─────────────────────────────────────── + + /** + * Hide all form content except the success panel, then show the + * success panel and move focus to its title. + * (.form-success lives inside

in the HTML, so we cannot + * simply hide the element — instead we hide its siblings.) + */ + function showSuccess() { + // Hide every direct child of the form except .form-success + Array.from(form.children).forEach(function (child) { + if (!child.classList.contains('form-success')) { + child.setAttribute('hidden', ''); + } + }); + + if (successPanel) { + successPanel.removeAttribute('hidden'); + var title = successPanel.querySelector('.form-success__title'); + if (title) { + // Ensure the heading is programmatically focusable + if (!title.hasAttribute('tabindex')) { + title.setAttribute('tabindex', '-1'); + } + title.focus(); + } + } + } + + // ── Form state helpers ──────────────────────────────────── + + var allInputs; // captured at submit time, used in onError + + function disableForm() { + allInputs = form.querySelectorAll('input, textarea, select, button'); + allInputs.forEach(function (el) { el.disabled = true; }); + if (submitBtn) submitBtn.textContent = 'Sending\u2026'; // "Sending…" + form.setAttribute('aria-busy', 'true'); + } + + function enableForm() { + if (allInputs) { + allInputs.forEach(function (el) { el.disabled = false; }); + } + if (submitBtn) submitBtn.textContent = originalBtnText; + form.removeAttribute('aria-busy'); + } + + // ── Submit handler ──────────────────────────────────────── + + form.addEventListener('submit', function (e) { + e.preventDefault(); + + // Reset error banner from any previous attempt + hideBanner(); + + // 1. Validate — show all errors simultaneously + var isValid = validateAll(); + if (!isValid) { + showBannerError('Please correct the errors above before submitting.'); + return; + } + + // 2. Honeypot check — silently fake success so bots see no difference + if (honeypot && honeypot.value) { + showSuccess(); + return; + } + + // 3. Lock the form while the request is in flight + disableForm(); + + // 4. Build the JSON payload + var payload = { + name: (document.getElementById('name') || { value: '' }).value, + address: (document.getElementById('address') || { value: '' }).value, + phone: (document.getElementById('phone') || { value: '' }).value, + email: (document.getElementById('email') || { value: '' }).value, + description: (document.getElementById('description') || { value: '' }).value, + honeypot: honeypot ? honeypot.value : '' + }; + + // 5. 10-second hard timeout via AbortController + var controller = new AbortController(); + var abortTimer = setTimeout(function () { + controller.abort(); + }, 10000); + + // 6. POST to the API + fetch(FORM_ENDPOINT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + signal: controller.signal + }) + .then(function (response) { + // Treat any non-2xx HTTP status as an error + if (!response.ok) { + throw new Error('HTTP error ' + response.status); + } + return response.json(); + }) + .then(function (data) { + clearTimeout(abortTimer); + // Some API Gateway patterns return { ok: false } with HTTP 200 + if (data && data.ok === false) { + throw new Error(data.message || 'Server returned an error.'); + } + // 7. Success path + form.removeAttribute('aria-busy'); // clear before showSuccess hides children + showSuccess(); + }) + .catch(function () { + clearTimeout(abortTimer); + // 8. Error path — re-enable form and surface the error + enableForm(); + showBannerError( + 'Something went wrong sending your request. ' + + 'Please try again, or contact Diana directly.' + ); + }); + }); + + }()); // end initQuoteForm + +}()); // end top-level IIFE diff --git a/_site/robots.txt b/_site/robots.txt new file mode 100644 index 0000000..8823822 --- /dev/null +++ b/_site/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://her-sf.com/sitemap.xml diff --git a/_site/services/boat-work/index.html b/_site/services/boat-work/index.html new file mode 100644 index 0000000..f30d236 --- /dev/null +++ b/_site/services/boat-work/index.html @@ -0,0 +1,316 @@ + + + + + + Boat Work in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Marine Woodwork & Hardware

+

Boat Work

+

Quality boat maintenance from someone who knows the Bay — woodwork, paint, and marine hardware done right.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Marine woodwork and structural repairs
  • + +
  • Boat spray painting and refinishing
  • + +
  • Marine hardware replacement
  • + +
  • Deck maintenance and sealing
  • + +
  • Interior boat carpentry
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/carpentry-and-mounting/index.html b/_site/services/carpentry-and-mounting/index.html new file mode 100644 index 0000000..b70cfcf --- /dev/null +++ b/_site/services/carpentry-and-mounting/index.html @@ -0,0 +1,338 @@ + + + + + + Carpentry & Mounting in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Installation & Assembly

+

Carpentry & Mounting

+

Precise carpentry and secure mounting work — shelves that are level, TVs that stay put, and trim that fits perfectly.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Shelving and storage installation
  • + +
  • TV and display mounting
  • + +
  • Cabinet repairs and hardware replacement
  • + +
  • Door and window trim work
  • + +
  • Furniture assembly
  • + +
  • Custom carpentry projects
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/drywall/index.html b/_site/services/drywall/index.html new file mode 100644 index 0000000..f4de4a4 --- /dev/null +++ b/_site/services/drywall/index.html @@ -0,0 +1,336 @@ + + + + + + Drywall & Sheetrocking in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Repair & Replacement

+

Drywall & Sheetrocking

+

Patch holes, replace damaged sections, and finish walls so they look like they were never touched.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Drywall and sheetrocking installation
  • + +
  • Hole and damage patching
  • + +
  • Seam taping and joint compound
  • + +
  • Sanding and texture matching
  • + +
  • Wall preparation for painting
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/electrical/index.html b/_site/services/electrical/index.html new file mode 100644 index 0000000..8fedb30 --- /dev/null +++ b/_site/services/electrical/index.html @@ -0,0 +1,336 @@ + + + + + + Minor Electrical in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Lights, Fans & Fixtures

+

Minor Electrical

+

Safe, clean electrical work for the fixtures and fittings that make your home feel finished.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Light fixture installation and replacement
  • + +
  • Ceiling fan installation
  • + +
  • Outdoor and security lighting
  • + +
  • Bathroom vanity lighting
  • + +
  • Wall sconces and under-cabinet lights
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/flooring/index.html b/_site/services/flooring/index.html new file mode 100644 index 0000000..2ec30a8 --- /dev/null +++ b/_site/services/flooring/index.html @@ -0,0 +1,338 @@ + + + + + + Flooring & Wood Refinishing in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Floors, Decks & Surfaces

+

Flooring & Wood Refinishing

+

Restore tired floors and wood surfaces to their original beauty — or better.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Hardwood floor sanding and refinishing
  • + +
  • Floor staining and sealing
  • + +
  • Flooring touch-ups and repairs
  • + +
  • Exterior deck and stair refinishing
  • + +
  • Cabinet and furniture refinishing
  • + +
  • Interior and exterior wood surface restoration
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/index.html b/_site/services/index.html new file mode 100644 index 0000000..add2c87 --- /dev/null +++ b/_site/services/index.html @@ -0,0 +1,397 @@ + + + + + + Handywoman Services in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Services

+

Services done right.

+

Every job, every trade — handled with precision, care, and a contractor you can actually trust.

+
+
+ + +
+
+

From a leaky faucet to a full room repaint, Diana handles home improvement projects of all sizes across San Francisco and the Bay Area. Every job is approached with the same care and precision that defined her aerospace career.

+
+ + +
+ + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/painting/index.html b/_site/services/painting/index.html new file mode 100644 index 0000000..f77f08d --- /dev/null +++ b/_site/services/painting/index.html @@ -0,0 +1,336 @@ + + + + + + Painting in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Interior & Exterior

+

Painting

+

From prep to final coat, Diana handles painting projects of all sizes — interior rooms, exterior siding, and trim work.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Interior wall and ceiling painting
  • + +
  • Exterior siding and trim painting
  • + +
  • Prep work — patching, priming, and masking
  • + +
  • Trim and door painting
  • + +
  • Color consultation
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/plumbing/index.html b/_site/services/plumbing/index.html new file mode 100644 index 0000000..2d0951e --- /dev/null +++ b/_site/services/plumbing/index.html @@ -0,0 +1,336 @@ + + + + + + Minor Plumbing in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Sinks, Toilets & Showers

+

Minor Plumbing

+

Quick, clean plumbing repairs and replacements — no mess left behind.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Sink repairs and faucet replacement
  • + +
  • Toilet repairs and replacement
  • + +
  • Shower and tub repairs
  • + +
  • Showerhead and handle replacement
  • + +
  • Under-sink plumbing fixes
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/security-safety/index.html b/_site/services/security-safety/index.html new file mode 100644 index 0000000..231bb2e --- /dev/null +++ b/_site/services/security-safety/index.html @@ -0,0 +1,326 @@ + + + + + + Security & Safety Equipment in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Grab Bars, Locks & Cameras

+

Security & Safety Equipment

+

Make your home safer and more secure — for aging in place, peace of mind, or both.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Grab bar installation for showers and stairways
  • + +
  • Door security bars and reinforcement
  • + +
  • Window security mesh installation
  • + +
  • Security camera installation (mobile-monitored)
  • + +
  • Extra lock installation
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/services/tile-setting/index.html b/_site/services/tile-setting/index.html new file mode 100644 index 0000000..052e37d --- /dev/null +++ b/_site/services/tile-setting/index.html @@ -0,0 +1,336 @@ + + + + + + Tile Setting in San Francisco | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ +

Kitchens, Bathrooms & More

+

Tile Setting

+

Precise tile work for kitchens, bathrooms, entryways, and patios — all sizes and styles, installed right.

+ Request a Quote +
+
+ +
+
+ + + + + +
+

What's included

+ +
    + +
  • Tile removal and surface preparation
  • + +
  • New tile installation — kitchens, bathrooms, entryways, patios
  • + +
  • All tile sizes and types (ceramic, porcelain, natural stone)
  • + +
  • Grouting and sealing
  • + +
  • Backsplash installation
  • + +
+ + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ + + + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + + diff --git a/_site/sitemap.xml b/_site/sitemap.xml new file mode 100644 index 0000000..1bbba36 --- /dev/null +++ b/_site/sitemap.xml @@ -0,0 +1,96 @@ + + + + + + https://her-sf.com/ + weekly + 1.0 + + + + + https://her-sf.com/services/ + weekly + 0.9 + + + + + + https://her-sf.com/services/painting/ + monthly + 0.8 + + + + https://her-sf.com/services/tile-setting/ + monthly + 0.8 + + + + https://her-sf.com/services/drywall/ + monthly + 0.8 + + + + https://her-sf.com/services/plumbing/ + monthly + 0.8 + + + + https://her-sf.com/services/electrical/ + monthly + 0.8 + + + + https://her-sf.com/services/carpentry-and-mounting/ + monthly + 0.8 + + + + https://her-sf.com/services/flooring/ + monthly + 0.8 + + + + https://her-sf.com/services/security-safety/ + monthly + 0.8 + + + + https://her-sf.com/services/boat-work/ + monthly + 0.8 + + + + + + https://her-sf.com/contact/ + monthly + 0.8 + + + + + https://her-sf.com/about/ + yearly + 0.7 + + + + + https://her-sf.com/testimonials/ + monthly + 0.7 + + + diff --git a/_site/testimonials/index.html b/_site/testimonials/index.html new file mode 100644 index 0000000..56705af --- /dev/null +++ b/_site/testimonials/index.html @@ -0,0 +1,302 @@ + + + + + + Client Reviews | HER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Testimonials

+

What clients say.

+

From San Francisco homeowners who've worked with Diana.

+
+
+ + +
+
+ +
+
+

“Diana can pretty much do anything. She saved me when I was selling my house and had a big list of miscellaneous tasks to be done. She is so resourceful — if she didn't readily know a solution she researched it and presented me with options. What impressed me was her work ethic. She doesn't have to be hounded to finish a job. She takes accountability and delivers. She's a rare find because she is so reliable, talented, hardworking and resourceful!”

+
+

Wendy Brummer

+ +

San Francisco

+ +
+
+
+ +
+
+

“I have hired Diana Carey for a variety of tasks. She is hardworking, trustworthy, and does excellent work. Bonus points for excellent communication and a keen eye on keeping things as affordable as possible. Highly recommended!”

+
+

E. Blake Davis

+ +
+
+
+ +
+
+

“Diana has helped me several times and she is now my go-to handyperson for all needs. She has been great! Very reliable, communicates well, and really dedicated to the best solution. Diana has a wide variety of skills and does quality work at reasonable rates. She gets my top rating.”

+
+

David Snyder

+ +

Oakland

+ +
+
+
+ +
+
+

“If you need a project done well and quickly with a combination of big picture perspective and meticulous attention to detail, Diana is THE person. It's hard to find a more competent person who is so thorough and really cares about doing the project well and on time.”

+
+

Alice L. Harron

+ +
+
+
+ +
+
+ + +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
+ + +
+ + + + + + +