const { useState, useEffect, useRef, useMemo } = React;

// ---------- Odometer roll: counts 0 -> target on mount ----------
function OdometerNumber({ to = 850, suffix = "+", duration = 1700, delay = 760 }) {
  const [val, setVal] = useState(0);
  const [done, setDone] = useState(false);
  useEffect(() => {
    if (typeof window === "undefined") return;
    const reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduced) { setVal(to); setDone(true); return; }
    let raf, start;
    const tick = (now) => {
      if (!start) start = now;
      const t = Math.min(1, (now - start) / duration);
      // ease-out quint for that "settling" feel
      const eased = 1 - Math.pow(1 - t, 5);
      setVal(Math.floor(eased * to));
      if (t < 1) raf = requestAnimationFrame(tick);
      else { setVal(to); setDone(true); }
    };
    const id = setTimeout(() => { raf = requestAnimationFrame(tick); }, delay);
    return () => { clearTimeout(id); if (raf) cancelAnimationFrame(raf); };
  }, [to, duration, delay]);
  return (
    <span className={`odometer${done ? " odometer--done" : ""}`} aria-label={`${to}${suffix}`}>
      <span className="odo-num">{val.toLocaleString()}</span>
      <span className="odo-suffix">{suffix}</span>
    </span>
  );
}

const PHONE_DISPLAY = "(425) 449-3277";
const PHONE_HREF = "tel:+14254493277";
const EMAIL = "Valleycustomhomesllc@gmail.com";
const LICENSE = "VALLECH841N6";

const NAV_LINKS = [
  { id: "work", label: "Work" },
  { id: "services", label: "Services" },
  { id: "about", label: "About" },
  { id: "reviews", label: "Reviews" },
  { id: "quote", label: "Quote" },
];

// ---------- Wordmark ----------
function Logo({ inverted = false }) {
  const fg = inverted ? "#FFFFFF" : "#2A283C";
  const sub = inverted ? "rgba(255,255,255,0.7)" : "#8A9399";
  const [shouldDraw] = useState(() => {
    if (typeof sessionStorage === "undefined") return false;
    if (sessionStorage.getItem("vch-logo-drawn") === "1") return false;
    sessionStorage.setItem("vch-logo-drawn", "1");
    return true;
  });
  return (
    <a href="#top" className={`logo${shouldDraw ? " logo--draw" : ""}`} aria-label="Valley Custom Homes home">
      <svg width="34" height="34" viewBox="0 0 40 40" fill="none" aria-hidden="true">
        {/* abstract gable mark */}
        <path d="M4 30 L20 8 L36 30" pathLength="100" stroke={fg} strokeWidth="2.2" strokeLinecap="square" strokeLinejoin="miter" />
        <path d="M4 30 L36 30" pathLength="100" stroke={fg} strokeWidth="2.2" strokeLinecap="square" />
        <path d="M11 30 L11 22" pathLength="100" stroke={fg} strokeWidth="2.2" strokeLinecap="square" />
        <path d="M29 30 L29 22" pathLength="100" stroke={fg} strokeWidth="2.2" strokeLinecap="square" />
      </svg>
      <span className="logo-text">
        <span className="logo-name" style={{ color: fg }}>Valley Custom Homes</span>
        <span className="logo-sub" style={{ color: sub }}>Redmond · WA</span>
      </span>
    </a>
  );
}

// ---------- Nav ----------
function Nav() {
  const [open, setOpen] = useState(false);
  return (
    <header className="nav nav--solid">
      <div className="nav-inner">
        <Logo />
        <nav className="nav-links" aria-label="Primary">
          {NAV_LINKS.map(l => (
            <a key={l.id} href={`#${l.id}`} className="nav-link">{l.label}</a>
          ))}
        </nav>
        <div className="nav-cta">
          <a href={PHONE_HREF} className="nav-phone">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
              <path d="M5 4h4l2 5-3 2a12 12 0 0 0 5 5l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 6a2 2 0 0 1 2-2z" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round"/>
            </svg>
            {PHONE_DISPLAY}
          </a>
          <a href="#quote" className="btn btn--primary">Get a Free Quote</a>
        </div>
        <button className="nav-burger" onClick={() => setOpen(o => !o)} aria-label="Menu" aria-expanded={open}>
          <span /><span /><span />
        </button>
      </div>
      {open && (
        <div className="nav-mobile">
          {NAV_LINKS.map(l => (
            <a key={l.id} href={`#${l.id}`} onClick={() => setOpen(false)}>{l.label}</a>
          ))}
          <a href="#quote" onClick={() => setOpen(false)} className="btn btn--primary btn--block">Get a Free Quote</a>
        </div>
      )}
    </header>
  );
}

// ---------- Hero ----------
function Hero() {
  return (
    <section id="top" className="hero">
      <div className="hero-stage">

        {/* Left column: editorial content */}
        <div className="hero-content">
          <div className="hero-kicker">
            <span className="hero-kicker-tick" aria-hidden="true" />
            <span className="hero-kicker-text">Yardistry-recommended installer · Redmond, WA</span>
          </div>

          <h1 className="hero-headline">
            <span className="hide-mobile">Gazebos built for <em>Pacific Northwest weather.</em></span>
            <span className="show-mobile">Gazebos built for <em>Pacific Northwest weather.</em></span>
          </h1>

          <div className="hero-actions">
            <a href="#quote" className="btn btn--primary btn--lg">Get a Free Quote</a>
          </div>

          <div className="hero-credentials" aria-label="Builder credentials">
            <div className="hero-cred-line">
              <span className="hero-cred-num"><OdometerNumber to={850} suffix="" /></span>
              <span className="hero-cred-prose">
                outdoor structures built across the Eastside &amp; Greater Seattle, <em>since two-thousand five.</em>
              </span>
            </div>
            <div className="hero-cred-foot">
              <span className="hero-cred-key">Owner-operated</span>
              <span className="hero-cred-sep" aria-hidden="true">·</span>
              <span className="hero-cred-key">Bonded &amp; insured</span>
              <span className="hero-cred-sep" aria-hidden="true">·</span>
              <span className="hero-cred-key">Wa Lic.</span>
              <code className="hero-cred-license-num">{LICENSE}</code>
            </div>
          </div>
        </div>

        {/* Right column: photo + cafe lights */}
        <figure className="hero-media">
          <img
            src="images/pavilion-string-lights.jpeg"
            alt="Sixteen-foot cedar pavilion at dusk, strung with warm cafe lights over a hillside patio in Issaquah, Washington, finished by Valley Custom Homes"
          />
          <div className="hero-lights" aria-hidden="true">
            <span className="hero-light hero-light--1" />
            <span className="hero-light hero-light--2" />
            <span className="hero-light hero-light--3" />
            <span className="hero-light hero-light--4" />
            <span className="hero-light hero-light--5" />
            <span className="hero-light hero-light--6" />
            <span className="hero-light hero-light--7" />
            <span className="hero-light hero-light--8" />
            <span className="hero-light hero-light--9" />
          </div>
          <div className="hero-scrim" aria-hidden="true" />
          <figcaption className="hero-photo-cap">
            <em>Cedar Pavilion</em>
            <span className="hero-photo-cap-sep" aria-hidden="true">·</span>
            <span>Issaquah, 2024</span>
          </figcaption>
        </figure>

      </div>
    </section>
  );
}

// ---------- Service area / Atlas page ----------
function ServiceArea() {
  return (
    <section id="area" className="area">
      <div className="container area-grid">
        <figure className="area-map">
          <img
            src="images/service-area-map.jpg"
            alt="Map of Western Washington showing Valley Custom Homes service area from Bellingham to Olympia"
            loading="lazy"
          />
          <figcaption className="area-map-cap">
            <em>Western Washington</em>
            <span className="area-map-cap-sep">·</span>
            <span>Bellingham to Olympia</span>
          </figcaption>
        </figure>
        <div className="area-text">
          <div className="area-kicker">
            <span className="area-kicker-tick" aria-hidden="true" />
            <span>Service area</span>
          </div>
          <h2 className="area-title">
            Bellingham to Olympia.
          </h2>
          <p className="area-para">
            Based in Redmond. We work anywhere from the North Cascades down to the south Sound.
          </p>
          <p className="area-para area-pitch">
            Outside the map? Call us. We'll see what we can do.
          </p>
          <a href={PHONE_HREF} className="btn btn--primary btn--lg area-btn">
            Call {PHONE_DISPLAY}
          </a>
        </div>
      </div>
    </section>
  );
}

// ---------- Section header ----------
function SectionHead({ kicker, title, lead, align = "left" }) {
  return (
    <header className={`sec-head sec-head--${align}`}>
      {kicker && <div className="kicker">{kicker}</div>}
      <h2 className="sec-title">{title}</h2>
      {lead && <p className="sec-lead">{lead}</p>}
    </header>
  );
}

// ---------- Services ----------
const SERVICES = [
  {
    num: "01",
    label: "Gazebos",
    title: "Gazebos",
    italic: null,
    blurb: "Hardtop and cedar-frame gazebos sized for dining, lounging, or hot-tub coverage. Engineered for PNW snow loads and built to outlast the deck beneath them.",
    img: "images/gazebo-forest.jpg",
    alt: "Cedar gazebo with hardtop roof on a fire-pit pad in the woods, North Bend",
    aspect: "4 / 3",
    layout: "text-photo",
    specs: [
      ["Sizes", "10×10 to 14×16"],
      ["Roof", "Hardtop or shingle"],
      ["Anchoring", "Pad, deck, or paver-set"],
    ],
  },
  {
    num: "02",
    label: "Pergolas & Pavilions",
    title: "Pergolas & Pavilions",
    italic: null,
    blurb: "Open-rafter pergolas for filtered shade, or fully roofed pavilions when you want the patio usable in any weather. Cedar, fir, or powder-coated aluminum.",
    img: "images/gallery/403720828966518790.jpg",
    alt: "Cedar pergola over a backyard patio with string lighting, finished by Valley Custom Homes",
    img2: "images/gallery/489769709990625281.jpg",
    alt2: "Open-frame cedar pavilion over a backyard patio in Redmond, Washington, finished by Valley Custom Homes",
    layout: "fullbleed",
    caption: "Open-Frame Pavilion · Redmond · 2024",
    specs: [
      ["Roof", "Open-rafter or louvered"],
      ["Material", "Cedar, fir, or aluminum"],
      ["Lighting", "Beam-ready, electrical handled"],
    ],
  },
  {
    num: "03",
    label: "Covered Patios",
    title: "Covered Patios",
    italic: null,
    blurb: "Roof extensions and freestanding covers that turn a patio or deck into a four-season outdoor room. Tied into existing structures with permitted, engineered details.",
    img: "images/gallery/484638858233167883.jpg",
    alt: "Covered deck with hardtop gazebo over a stone patio in Snoqualmie",
    aspect: "3 / 4",
    layout: "photo-text",
    specs: [
      ["Connection", "Attached or freestanding"],
    ],
  },
];

function SpecTable({ specs }) {
  return (
    <dl className="svc-specs">
      {specs.map(([k, v], i) => (
        <div className="svc-spec" key={i}>
          <dt>{k}</dt>
          <dd>{v}</dd>
        </div>
      ))}
    </dl>
  );
}

function ServiceEntry({ service }) {
  const { num, label, title, italic, blurb, img, img2, alt, alt2, aspect, layout, specs, caption } = service;

  if (layout === "fullbleed") {
    return (
      <article className="svc svc--fullbleed" aria-labelledby={`svc-${num}-title`}>
        <div className="container svc-pair">
          <div className="svc-pair-photos">
            <figure className="svc-pair-photo svc-pair-photo--natural">
              <img src={img} alt={alt} loading="lazy" />
            </figure>
          </div>
          <div className="svc-pair-text">
            <div className="svc-kicker"><span className="svc-num">{num}</span><span className="svc-num-sep" aria-hidden="true">/</span><span className="svc-num-total">03</span></div>
            <h3 id={`svc-${num}-title`} className="svc-title">
              {title}{italic && <> <em>{italic}</em></>}
            </h3>
            <p className="svc-blurb">{blurb}</p>
            <SpecTable specs={specs} />
            {img2 && (
              <figure className="svc-pair-photo">
                <img src={img2} alt={alt2 || alt} loading="lazy" />
              </figure>
            )}
          </div>
        </div>
      </article>
    );
  }

  const photo = (
    <figure className="svc-photo" style={aspect ? { aspectRatio: aspect } : undefined}>
      <img src={img} alt={alt} loading="lazy" />
    </figure>
  );

  const text = (
    <div className="svc-text">
      <div className="svc-kicker"><span className="svc-num">{num}</span><span className="svc-num-sep" aria-hidden="true">/</span><span className="svc-num-total">03</span></div>
      <h3 id={`svc-${num}-title`} className="svc-title">
        {title}{italic && <> <em>{italic}</em></>}
      </h3>
      <p className="svc-blurb">{blurb}</p>
      <SpecTable specs={specs} />
      <a href="#quote" className="svc-link">Quote this build <span aria-hidden="true">→</span></a>
    </div>
  );

  return (
    <article className={`svc svc--${layout}`} aria-labelledby={`svc-${num}-title`}>
      <div className="container svc-grid">
        {layout === "text-photo" ? (
          <>
            {text}
            {photo}
          </>
        ) : (
          <>
            {photo}
            {text}
          </>
        )}
      </div>
    </article>
  );
}

function Services() {
  return (
    <section id="services" className="services">
      <div className="container services-head">
        <SectionHead
          title={<>Gazebos, pergolas, covered patios.</>}
        />
      </div>

      <div className="svc-stack">
        {SERVICES.map((s) => <ServiceEntry key={s.num} service={s} />)}
      </div>
    </section>
  );
}

// ---------- Costco / Yardistry specialist ----------
function Costco() {
  return (
    <section id="costco" className="costco">
      <div className="container costco-supplement">
        <div className="costco-folio">
          <span className="costco-folio-num">02</span>
          <span className="costco-folio-rule" aria-hidden="true" />
          <span className="costco-folio-label">A Costco specialty · Yardistry hardtops, since 2014</span>
        </div>

        <div className="costco-spread">
          <div className="costco-pitch">
            <h2 className="costco-headline">
              Bought it from Costco?<br/>
              <em>We'll put it in the ground.</em>
            </h2>
            <p className="costco-lead">
              We unpack the kit and put it in the ground. Built for our snow load, anchored to last as long as the deck beneath it.
            </p>
            <div className="costco-cta">
              <a href="#quote" className="btn btn--primary btn--lg costco-btn">
                <span>Quote this build</span>
                <span className="costco-btn-arrow" aria-hidden="true">→</span>
              </a>
              <a href={PHONE_HREF} className="costco-phone">
                <span className="costco-phone-prefix">or call</span>
                <span className="costco-phone-num">{PHONE_DISPLAY}</span>
              </a>
            </div>
          </div>

          <div className="costco-pane">
            <figure className="costco-photo">
              <img src="images/gazebo-forest.jpg" alt="Installed Yardistry hardtop gazebo, 12 by 14, in Sammamish" loading="lazy" />
            </figure>

            <p className="costco-specline">
              Sizes <strong>10×10 to 12×16</strong>. Hardtop aluminum or cedar frame. Anchored to pad, deck, or pavers.
            </p>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- About ----------
function About() {
  return (
    <section id="about" className="about">
      <div className="container about-grid">
        <div className="about-text">
          <h2 className="about-title">
            20 years on the saw.<br/>
            850 builds done.
          </h2>
          <p className="about-para">
            Jon started framing in 2005. Since then he's put up more than 850 outdoor structures across King and Snohomish counties: anything from a 10×10 starter gazebo to a 16-foot pavilion with full electrical.
          </p>
          <p className="about-para">
            Valley Custom Homes is one guy. Same hands on every build, start to finish. No salesman, no sub-out.
          </p>
        </div>
        <figure className="about-photo">
          <img src="images/johnceo.jpg" alt="Jon, owner and lead carpenter of Valley Custom Homes" loading="lazy" />
          <figcaption className="about-photo-cap">
            <span className="about-photo-cap-name">Jon</span>
            <span className="about-photo-cap-sep" aria-hidden="true">·</span>
            Owner, lead carpenter
          </figcaption>
        </figure>
      </div>
    </section>
  );
}

// ---------- Portfolio ----------
// Curated from images/gallery — different builds, cities, seasons
const PORTFOLIO = [
  { src: "images/gallery/556233677035823106.jpg", title: "Sammamish",  yr: "Aug '25",  h: 1.33 },
  { src: "images/gazebo-forest.jpg",              title: "Issaquah",   yr: "Jul '25",  h: 1.33 },
  { src: "images/gallery/511647521223548940.jpg", title: "Bellevue",   yr: "Sept '24", h: 1.33 },
  { src: "images/gallery/473272991525920795.jpg", title: "Kirkland",   yr: "May '24",  h: 1.33 },
  { src: "images/gallery/445117165350125573.jpg", title: "Snoqualmie", yr: "Mar '24",  h: 1.33 },
  { src: "images/gallery/404273102841626632.jpg", title: "Duvall",     yr: "Oct '23",  h: 0.66 },
  { src: "images/gallery/385537478994075658.jpg", title: "Bothell",    yr: "Jul '23",  h: 1.33 },
];

function Portfolio({ onOpen, lbIndex }) {
  return (
    <section id="work" className="portfolio">
      <div className="container">
        <SectionHead
          title={<>Seven recent builds.</>}
          lead="Out of more than 850. Click any photo to enlarge."
        />
      </div>
      <div className="portfolio-rail" role="list">
        {PORTFOLIO.map((p, i) => (
          <button
            key={i}
            role="listitem"
            className={`port-card port-card--${p.h < 1 ? "tall" : p.h > 1.3 ? "wide" : "regular"}`}
            onClick={() => onOpen(i)}
            aria-label={`Open ${p.title} ${p.yr}`}
          >
            <figure className="port-figure">
              <div className="port-photo" style={{ aspectRatio: p.h }}>
                <img
                  src={p.src}
                  alt={`${p.title} build, ${p.yr}`}
                  loading="lazy"
                  style={lbIndex === i ? undefined : { viewTransitionName: `port-${i}` }}
                />
              </div>
              <figcaption className="port-meta">
                <div className="port-num">{String(i + 1).padStart(2, "0")}</div>
                <div className="port-tag">
                  <span>{p.title}</span>
                  <span className="port-tag-sep" aria-hidden="true">·</span>
                  <span>{p.yr}</span>
                </div>
              </figcaption>
            </figure>
          </button>
        ))}
        <div className="port-end" role="listitem">
          <div className="port-end-mark"><span className="port-end-plus">+</span><span className="port-end-num">843</span></div>
          <div className="port-end-label">more in the archive</div>
          <a href="gallery.html" className="port-end-link">View the full gallery <span aria-hidden="true">→</span></a>
        </div>
      </div>
      <div className="container portfolio-cta">
        <a href="gallery.html" className="btn btn--primary btn--lg">View all gazebo photos <span aria-hidden="true">→</span></a>
      </div>
    </section>
  );
}

function Lightbox({ index, onClose, onPrev, onNext }) {
  useEffect(() => {
    if (index === null) return;
    const onKey = e => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowLeft") onPrev();
      if (e.key === "ArrowRight") onNext();
    };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [index, onClose, onPrev, onNext]);
  if (index === null) return null;
  const p = PORTFOLIO[index];
  return (
    <div className="lightbox" onClick={onClose} role="dialog" aria-modal="true">
      <button className="lb-close" onClick={onClose} aria-label="Close">×</button>
      <button className="lb-nav lb-nav--prev" onClick={e => { e.stopPropagation(); onPrev(); }} aria-label="Previous">‹</button>
      <button className="lb-nav lb-nav--next" onClick={e => { e.stopPropagation(); onNext(); }} aria-label="Next">›</button>
      <figure className="lb-figure" onClick={e => e.stopPropagation()}>
        <img
          src={p.src}
          alt={p.title}
          style={{ viewTransitionName: `port-${index}` }}
        />
        <figcaption>
          <span>{p.title}</span>
          <span className="lb-loc">{p.yr}</span>
          <span className="lb-count">{index + 1} / {PORTFOLIO.length}</span>
        </figcaption>
      </figure>
    </div>
  );
}

// ---------- Reviews ----------
const THUMBTACK_URL = "https://www.thumbtack.com/wa/redmond/handyman/valley-custom-homes-llc/service/282060010380608674";

const REVIEWS = [
  { quote: "We ended up not needing them to install a gazebo but they were super communicative and appreciated them scoping the opportunity. If we have more complex projects, will use them.", name: "Morgan B.", where: "Gazebo Installation", yrs: "Nov 2025" },
  { quote: "Did an excellent job installing our gazebo.", name: "Jeff W.", where: "Gazebo Installation", yrs: "Apr 2025" },
  { quote: "Jon was very responsive and communicative throughout the whole process. He provided a very detailed and fair bid and did an excellent job fixing our broken fence. If we could give more stars, we would. Impeccable work ethic and professionalism.", name: "Yulia S.", where: "Fence & Gate Repairs", yrs: "Dec 2024" },
  { quote: "Would absolutely hire again.", name: "Tim O.", where: "Gazebo Installation", yrs: "Oct 2024" },
  { quote: "Jon did a fantastic (and efficient) job completing and installing my pergola. I had built the original myself — it took me several days. Jon built and installed the replacement in about a half day and the work was perfect. Still looks great nearly 9 months later!", name: "Patrick O.", where: "Pergola Installation", yrs: "Sep 2024" },
  { quote: "Jon is very professional. He built our gazebo with great quality and we are very happy with his service.", name: "Vinod K.", where: "Gazebo Installation", yrs: "Aug 2024" },
  { quote: "I couldn't be more thrilled with the gazebo they assembled for me. From start to finish, the experience was outstanding. The finished gazebo has transformed my backyard into a beautiful and serene oasis. I highly recommend them to anyone looking for top-notch construction services.", name: "Clint W.", where: "Gazebo Installation", yrs: "Jul 2024" },
  { quote: "Jon did a great job installing our gazebo. He was responsive, efficient and cleaned up the area after the work was done. The quality of his work was excellent and we are very happy with the completed project.", name: "Stephanie C.", where: "Gazebo Installation", yrs: "Mar 2024" },
  { quote: "We had ordered a Yardistry gazebo (12x10) from Costco. Jon is actually recommended by Yardistry, which is how I found him. He fully unpacked and built it in about 8 hours, bolted to my patio, and hung my string lights too. Very impressed and so worth the money!", name: "Rachel V.", where: "Gazebo Installation", yrs: "Mar 2024" },
  { quote: "Five stars. No comment left.", name: "Vanessa C.", where: "Gazebo Installation", yrs: "Feb 2024" },
  { quote: "Absolutely amazing. Will definitely recommend!!!!!", name: "Kimberleyleilani B.", where: "Gazebo Installation", yrs: "Aug 2023" },
  { quote: "He gave us sound advice on which gazebo to get, then solved the dilemma of installing it in uneven soil — sturdy, balanced, and beautiful. Showed up when he said he would, communicated, cleaned up all debris, at a reasonable fair price. Hire this guy!", name: "Gerti B.", where: "Gazebo Installation", yrs: "Aug 2023" },
  { quote: "Five stars. No comment left.", name: "James V.", where: "Gazebo Installation", yrs: "Aug 2023" },
  { quote: "Five stars. No comment left.", name: "Brian H.", where: "Gazebo Installation", yrs: "Jul 2023" },
  { quote: "Jon installed our Costco 10x12 ft gazebo. It was a great experience. He was incredibly responsive, professional and really easy to work with.", name: "Carole O.", where: "Gazebo Installation", yrs: "Jul 2023" },
  { quote: "Five stars. No comment left.", name: "Vickie H.", where: "Gazebo Installation", yrs: "Jul 2023" },
];

function Reviews() {
  const cards = [...REVIEWS, ...REVIEWS];
  return (
    <section id="reviews" className="reviews">
      <div className="container">
        <SectionHead
          title={<>16 recent reviews. Pulled from Thumbtack.</>}
          lead="Click any card to read it on the source."
        />
      </div>

      <div className="rev-marquee" aria-label="Customer reviews">
        <div className="rev-track">
          {cards.map((r, i) => {
            const isShort = /^Five stars\.?\s*No comment/i.test(r.quote);
            const isClone = i >= REVIEWS.length;
            return (
              <a
                key={i}
                className="rev-card"
                href={THUMBTACK_URL}
                target="_blank"
                rel="noopener noreferrer"
                aria-label={`Read ${r.name}'s review on Thumbtack`}
                aria-hidden={isClone ? "true" : undefined}
                tabIndex={isClone ? -1 : 0}
              >
                <div className="rev-mark" aria-label={isClone ? undefined : "Five stars"}>
                  <span aria-hidden="true">★</span>
                  <span aria-hidden="true">★</span>
                  <span aria-hidden="true">★</span>
                  <span aria-hidden="true">★</span>
                  <span aria-hidden="true">★</span>
                </div>
                <blockquote className={`rev-quote ${isShort ? "rev-quote--short" : ""}`}>
                  {isShort ? "Five stars." : <>"{r.quote}"</>}
                </blockquote>
                <footer className="rev-attribution">
                  <span className="rev-name">{r.name}</span>
                  <span className="rev-meta">
                    <span>{r.where}</span>
                    <span className="rev-meta-sep" aria-hidden="true">·</span>
                    {r.yrs}
                  </span>
                  <span className="rev-source" aria-hidden="true">
                    <span>Read on Thumbtack</span>
                    <span className="rev-source-arrow">↗</span>
                  </span>
                </footer>
              </a>
            );
          })}
        </div>
      </div>

      <div className="container rev-link">
        <a href={THUMBTACK_URL} target="_blank" rel="noopener noreferrer" className="btn btn--primary btn--lg">
          Read all 103 reviews on Thumbtack <span aria-hidden="true">→</span>
        </a>
      </div>
    </section>
  );
}

// ---------- Quote form ----------
function Quote() {
  const [form, setForm] = useState({
    name: "", phone: "", email: "", type: "", zip: "", message: "",
  });
  const [errors, setErrors] = useState({});
  const [submitted, setSubmitted] = useState(false);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const validateField = (k, v) => {
    if (k === "name" && !v.trim()) return "We need a name to call you back.";
    if (k === "phone" && v && !/^[\d\s().+-]{10,}$/.test(v)) return "Phone needs an area code, like (425) 555-0199.";
    if (k === "email" && v && !/^\S+@\S+\.\S+$/.test(v)) return "Email is missing an @ or a domain.";
    if (k === "zip" && v && !/^\d{5}$/.test(v)) return "We need a 5-digit ZIP to scope your area.";
    return null;
  };

  const validate = () => {
    const e = {};
    const nameErr = validateField("name", form.name); if (nameErr) e.name = nameErr;
    if (!/^[\d\s().+-]{10,}$/.test(form.phone)) e.phone = form.phone ? "Phone needs an area code, like (425) 555-0199." : "Phone, so Jon can call you back.";
    if (!/^\S+@\S+\.\S+$/.test(form.email)) e.email = form.email ? "Email is missing an @ or a domain." : "Email, in case Jon needs to send a quote.";
    if (!form.type) e.type = "Pick a project type below.";
    if (!/^\d{5}$/.test(form.zip)) e.zip = form.zip ? "ZIPs are 5 digits." : "ZIP, so we know where to drive.";
    return e;
  };

  const handleBlur = (k) => {
    const err = validateField(k, form[k]);
    setErrors(prev => ({ ...prev, [k]: err || undefined }));
  };

  const submit = (e) => {
    e.preventDefault();
    const v = validate();
    setErrors(v);
    if (Object.keys(v).length === 0) {
      setSubmitted(true);
    }
  };

  if (submitted) {
    return (
      <section id="quote" className="quote">
        <div className="container quote-grid">
          <div className="quote-text quote-thanks">
            <div className="quote-kicker">
              <span className="quote-kicker-tick" aria-hidden="true" />
              <span>Got it</span>
            </div>
            <h2 className="quote-title">
              Thanks, {form.name.split(" ")[0]}.<br/>
              <em>Jon will call you back.</em>
            </h2>
            <p className="quote-lead">
              Usually within one business day. If it's urgent, ring <a href={PHONE_HREF} className="quote-thanks-phone">{PHONE_DISPLAY}</a> directly.
            </p>
            <button type="button" className="btn btn--primary" onClick={() => { setSubmitted(false); setForm({ name:"",phone:"",email:"",type:"",zip:"",message:"" }); }}>
              Send another <span aria-hidden="true">→</span>
            </button>
          </div>
          <QuoteSidebar />
        </div>
      </section>
    );
  }

  return (
    <section id="quote" className="quote">
      <div className="container quote-grid">
        <div className="quote-text">
          <div className="quote-kicker">
            <span className="quote-kicker-tick" aria-hidden="true" />
            <span>Free quote</span>
          </div>
          <h2 className="quote-title">
            Tell us about<br/>
            <em>your backyard.</em>
          </h2>
          <p className="quote-lead">No fees, no pressure. Most quotes come back within a business day.</p>

          <form className="quote-form" onSubmit={submit} noValidate>
            <div className="field">
              <label htmlFor="f-name">Name</label>
              <input id="f-name" value={form.name} onChange={e => set("name", e.target.value)} onBlur={() => handleBlur("name")} placeholder="Jane Doe" />
              {errors.name && <span className="err">{errors.name}</span>}
            </div>

            <div className="field-row">
              <div className="field">
                <label htmlFor="f-phone">Phone</label>
                <input id="f-phone" inputMode="tel" value={form.phone} onChange={e => set("phone", e.target.value)} onBlur={() => handleBlur("phone")} placeholder="(425) 555-0199" />
                {errors.phone && <span className="err">{errors.phone}</span>}
              </div>
              <div className="field">
                <label htmlFor="f-email">Email</label>
                <input id="f-email" type="email" value={form.email} onChange={e => set("email", e.target.value)} onBlur={() => handleBlur("email")} placeholder="you@email.com" />
                {errors.email && <span className="err">{errors.email}</span>}
              </div>
            </div>

            <div className="field">
              <label>Project type</label>
              <div className="chips">
                {["Gazebo", "Pergola", "Pavilion", "Covered patio", "Not sure yet"].map(t => (
                  <button type="button" key={t} className={`chip ${form.type === t ? "chip--active" : ""}`} onClick={() => set("type", t)}>
                    {t}
                  </button>
                ))}
              </div>
              {errors.type && <span className="err">{errors.type}</span>}
            </div>

            <div className="field-row">
              <div className="field">
                <label htmlFor="f-zip">ZIP code</label>
                <input id="f-zip" inputMode="numeric" maxLength="5" value={form.zip} onChange={e => set("zip", e.target.value.replace(/\D/g,""))} onBlur={() => handleBlur("zip")} placeholder="98052" />
                {errors.zip && <span className="err">{errors.zip}</span>}
              </div>
              <div className="field">
                <label htmlFor="f-when">Ideal start</label>
                <select id="f-when" defaultValue="">
                  <option value="" disabled>Select…</option>
                  <option>ASAP</option>
                  <option>Next 1–3 months</option>
                  <option>Spring 2026</option>
                  <option>Just exploring</option>
                </select>
              </div>
            </div>

            <div className="field">
              <label htmlFor="f-msg">Tell us about the project</label>
              <textarea id="f-msg" rows="4" value={form.message} onChange={e => set("message", e.target.value)} placeholder="Footprint you're picturing, photos welcome, any quirks of the site…" />
            </div>

            <div className="quote-actions">
              <button type="submit" className="btn btn--primary btn--lg">Send to Jon <span aria-hidden="true">→</span></button>
              <span className="quote-fineprint">By submitting, you agree to be contacted. We never share your info.</span>
            </div>
          </form>
        </div>

        <QuoteSidebar />
      </div>
    </section>
  );
}

function QuoteSidebar() {
  return (
    <aside className="quote-side">
      <div className="quote-side-contact">
        <div className="quote-side-kicker">
          <span className="quote-side-kicker-tick" aria-hidden="true" />
          <span>Or talk now</span>
        </div>
        <a href={PHONE_HREF} className="quote-phone">{PHONE_DISPLAY}</a>
        <a href={`mailto:${EMAIL}`} className="quote-email">{EMAIL}</a>
        <dl className="quote-hours">
          <div className="quote-hours-row">
            <dt>Mon–Sat</dt>
            <dd>7am–6pm</dd>
          </div>
          <div className="quote-hours-row">
            <dt>Sun</dt>
            <dd>By appointment</dd>
          </div>
        </dl>
        <div className="quote-license">
          <span className="quote-license-key">WA License</span>
          <code className="quote-license-num">{LICENSE}</code>
        </div>
      </div>
      <figure className="quote-side-photo">
        <img src="images/gazebo-tan.jpeg" alt="Tan-cedar pavilion on a gravel pad finished by Valley Custom Homes in Duvall" loading="lazy" />
        <figcaption className="quote-side-cap">
          <span className="quote-side-cap-quote">"Most builds wrap in two to four days on site."</span>
          <span className="quote-side-cap-attrib">Jon</span>
        </figcaption>
      </figure>
    </aside>
  );
}

// ---------- Footer ----------
function Footer() {
  return (
    <footer className="footer">
      <div className="container footer-inner">
        <div className="footer-mast">
          <Logo inverted />
          <p className="footer-tagline">
            Outdoor structures across western Washington.
          </p>
        </div>

        <div className="footer-grid">
          <div className="footer-col">
            <div className="footer-key">Visit</div>
            <p className="footer-val">Redmond, WA</p>
            <p className="footer-val footer-val--muted">Bellingham to Olympia</p>
          </div>
          <div className="footer-col">
            <div className="footer-key">Contact</div>
            <a href={PHONE_HREF} className="footer-link footer-link--phone">{PHONE_DISPLAY}</a>
            <a href={`mailto:${EMAIL}`} className="footer-link">{EMAIL}</a>
            <a
              className="footer-link footer-link--muted"
              href="https://www.thumbtack.com/wa/redmond/handyman/valley-custom-homes-llc/service/282060010380608674"
              target="_blank"
              rel="noopener noreferrer"
            >
              Thumbtack profile
            </a>
            <a className="footer-link footer-link--muted" href="gallery.html">All gazebo photos</a>
          </div>
          <div className="footer-col">
            <div className="footer-key">License</div>
            <p className="footer-val">WA General Contractor</p>
            <code className="footer-license">{LICENSE}</code>
            <p className="footer-val footer-val--muted">Bonded &amp; insured</p>
          </div>
        </div>
      </div>

      <div className="container footer-base">
        <span className="footer-copy">&copy; 2026 Valley Custom Homes LLC. All rights reserved.</span>
        <span className="footer-craft">Owner-operated since 2005.</span>
      </div>
    </footer>
  );
}

// ---------- Sticky mobile CTA ----------
function MobileCta() {
  return (
    <div className="mobile-cta">
      <a href={PHONE_HREF} className="mc-call">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
          <path d="M5 4h4l2 5-3 2a12 12 0 0 0 5 5l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 6a2 2 0 0 1 2-2z" stroke="currentColor" strokeWidth="1.6"/>
        </svg>
        Call
      </a>
      <a href="#quote" className="mc-quote">Get a free quote</a>
    </div>
  );
}

// ---------- App ----------
function App() {
  const [lb, setLb] = useState(null);
  const withTransition = (fn) => {
    if (typeof document !== "undefined" && document.startViewTransition) {
      document.startViewTransition(fn);
    } else {
      fn();
    }
  };
  const open = (i) => withTransition(() => setLb(i));
  const close = () => withTransition(() => setLb(null));
  const prev = () => setLb(i => (i - 1 + PORTFOLIO.length) % PORTFOLIO.length);
  const next = () => setLb(i => (i + 1) % PORTFOLIO.length);

  // Reveal on scroll
  useEffect(() => {
    const els = document.querySelectorAll("[data-reveal]");
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.add("is-in");
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0, rootMargin: "0px 0px -10% 0px" });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  }, []);

  return (
    <>
      <Nav />
      <main>
        <Hero />
        <div data-reveal><ServiceArea /></div>
        <div data-reveal><Costco /></div>
        <div data-reveal><Services /></div>
        <div data-reveal><About /></div>
        <div data-reveal><Portfolio onOpen={open} lbIndex={lb} /></div>
        <div data-reveal><Reviews /></div>
        <div data-reveal><Quote /></div>
      </main>
      <Footer />
      <MobileCta />
      <Lightbox index={lb} onClose={close} onPrev={prev} onNext={next} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
