patx/demo

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>CrossRoads Paving Solutions | Pavers, Driveways, Pool Decks & Patios</title>
  <meta name="description" content="CrossRoads Paving Solutions designs and installs interlocking paver driveways, patios, pool decks, and walkways for residential and commercial properties across South Florida." />
  <meta property="og:type" content="website" />
  <meta property="og:site_name" content="CrossRoads Paving Solutions" />
  <meta property="og:title" content="CrossRoads Paving Solutions" />
  <meta property="og:description" content="Interlocking paver systems for driveways, patios, pool decks, and walkways across South Florida." />
  <meta property="og:url" content="https://crossroadspaving.net/" />
  <meta property="og:image" content="https://crossroadspaving.net/crossroads-og-card.png" />
  <meta property="og:image:secure_url" content="https://crossroadspaving.net/crossroads-og-card.png" />
  <meta property="og:image:type" content="image/png" />
  <meta property="og:image:width" content="1200" />
  <meta property="og:image:height" content="630" />
  <meta property="og:image:alt" content="CrossRoads Paving Solutions paver driveways, pool decks, and patios in South Florida" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="CrossRoads Paving Solutions" />
  <meta name="twitter:description" content="Interlocking paver systems for driveways, patios, pool decks, and walkways across South Florida." />
  <meta name="twitter:image" content="https://crossroadspaving.net/crossroads-og-card.png" />
  <meta name="twitter:image:alt" content="CrossRoads Paving Solutions paver driveways, pool decks, and patios in South Florida" />
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Oswald:wght@400;500;600;700&display=swap" rel="stylesheet" />
  <style>
    :root {
      --ember: #cc4d2a;
      --ember-soft: #e16a3b;
      --ember-light: #ff9a6f;
      --stone: #f3f1ed;
      --paper: rgba(255, 250, 245, 0.78);
      --paper-strong: rgba(255, 250, 245, 0.92);
      --glass: rgba(18, 14, 12, 0.78);
      --glass-strong: rgba(18, 14, 12, 0.92);
      --line-light: rgba(255, 250, 245, 0.32);
      --line-dark: rgba(102, 71, 54, 0.14);
      --ink: #17120f;
      --muted-light: rgba(255, 250, 245, 0.84);
      --muted-dark: #6e655f;
      --font-display: "Oswald", sans-serif;
      --font-body: "Inter", sans-serif;
    }

    * { box-sizing: border-box; }
    html {
      scroll-behavior: smooth;
    }
    body {
      margin: 0;
      background: var(--ink);
      color: #fffaf5;
      font-family: var(--font-body);
      overflow-x: hidden;
      overscroll-behavior-y: none;
    }

    a { color: inherit; }
    .wrap { width: min(1200px, calc(100vw - 40px)); margin: 0 auto; }

    .scroll-video-stage {
      position: fixed;
      inset: 0;
      z-index: -3;
      overflow: hidden;
      background: #16110f;
    }
    .scroll-frame-canvas {
      width: 100%;
      height: 100%;
      display: block;
      transform: translateZ(0) scale(1.01);
      backface-visibility: hidden;
    }
    .scroll-video-stage::before,
    .scroll-video-stage::after {
      content: "";
      position: absolute;
      inset: 0;
      pointer-events: none;
    }
    .scroll-video-stage::before {
      background:
        linear-gradient(90deg, rgba(10, 7, 5, .88), rgba(10, 7, 5, .38) 48%, rgba(10, 7, 5, .72)),
        linear-gradient(180deg, rgba(10, 7, 5, .72), rgba(10, 7, 5, .22) 42%, rgba(10, 7, 5, .86));
      z-index: 1;
    }
    .scroll-video-stage::after {
      background: linear-gradient(90deg, rgba(204, 77, 42, .24), transparent 38%, rgba(204, 77, 42, .12));
      mix-blend-mode: soft-light;
      z-index: 2;
    }
    .scroll-progress {
      position: fixed;
      left: 0;
      top: 0;
      width: 100%;
      height: 3px;
      transform-origin: left;
      transform: scaleX(0);
      background: linear-gradient(90deg, var(--ember), var(--ember-light));
      z-index: 80;
    }

    .nav {
      position: fixed;
      inset: 0 0 auto 0;
      z-index: 70;
      backdrop-filter: blur(18px);
      background: rgba(16, 12, 10, 0.34);
      border-bottom: 1px solid rgba(255, 250, 245, 0.12);
      transition: background .25s ease, box-shadow .25s ease, border-color .25s ease;
    }
    .nav.is-scrolled,
    .nav.is-open {
      background: rgba(16, 12, 10, 0.78);
      border-bottom-color: rgba(255, 250, 245, .18);
      box-shadow: 0 18px 50px rgba(0, 0, 0, .24);
    }
    .nav-inner {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 20px;
      min-height: 84px;
      padding: 16px 0;
    }
    .brand {
      display: inline-flex;
      align-items: center;
      text-decoration: none;
    }
    .brand img {
      display: block;
      width: min(250px, 44vw);
      height: auto;
      object-fit: contain;
      filter: drop-shadow(0 8px 24px rgba(0, 0, 0, .24));
    }
    .nav-links {
      display: flex;
      gap: 28px;
      flex-wrap: wrap;
      justify-content: flex-end;
      align-items: center;
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: 0.12em;
      font-size: 0.9rem;
    }
    .nav-links a {
      text-decoration: none;
      color: rgba(255, 250, 245, .72);
      position: relative;
      transition: color .2s ease;
    }
    .nav-links a::after {
      content: "";
      position: absolute;
      left: 0;
      bottom: -8px;
      width: 100%;
      height: 2px;
      background: var(--ember-light);
      transform: scaleX(0);
      transform-origin: left;
      transition: transform .2s ease;
    }
    .nav-links a:hover,
    .nav-links a.is-active { color: #fffaf5; }
    .nav-links a:hover::after,
    .nav-links a.is-active::after { transform: scaleX(1); }
    .nav-toggle {
      display: none;
      width: 44px;
      height: 44px;
      border: 1px solid rgba(255, 250, 245, .22);
      background: rgba(255, 250, 245, .08);
      color: #fffaf5;
      padding: 0;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      gap: 5px;
      cursor: pointer;
    }
    .nav-toggle span {
      width: 20px;
      height: 2px;
      background: currentColor;
      transition: transform .2s ease, opacity .2s ease;
    }
    .nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
    .nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
    .nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

    main {
      position: relative;
      z-index: 1;
    }
    .chapter {
      position: relative;
      min-height: 125vh;
      display: grid;
      align-items: center;
      padding: clamp(132px, 14vh, 190px) 0 clamp(118px, 15vh, 210px);
      opacity: 1;
      pointer-events: auto;
      visibility: visible;
      transform: none;
      z-index: 2;
      scroll-margin-top: 96px;
    }
    .chapter.is-active {
      pointer-events: auto;
      visibility: visible;
    }
    .chapter.hero {
      min-height: 108vh;
      align-items: end;
      padding-bottom: 12vh;
    }
    .chapter.services,
    .chapter.feature,
    .chapter.contact-section {
      min-height: 148vh;
    }
    .chapter.products {
      min-height: 128vh;
    }
    .chapter > .wrap {
      max-height: none;
      overflow: visible;
    }
    .chapter-grid {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(300px, .64fr);
      gap: 46px;
      align-items: end;
    }
    .chapter-copy {
      max-width: 760px;
    }
    .chapter-copy.narrow {
      max-width: 620px;
    }
    .chapter-side {
      align-self: stretch;
      display: grid;
      align-content: end;
      gap: 22px;
    }
    .eyebrow,
    .section-kicker,
    .card-kicker,
    .projects-eyebrow,
    .contact-label,
    .sticky-call-label {
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .16em;
    }
    .eyebrow,
    .section-kicker,
    .card-kicker,
    .projects-eyebrow {
      color: var(--ember-light);
      font-size: .78rem;
      margin-bottom: 16px;
    }
    .eyebrow {
      display: inline-flex;
      align-items: center;
      gap: 12px;
      letter-spacing: .22em;
    }
    .eyebrow::before {
      content: "";
      width: 36px;
      height: 2px;
      background: var(--ember-soft);
    }
    h1,
    .section-title {
      margin: 0;
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: 0;
      line-height: .88;
      text-wrap: balance;
    }
    h1 {
      max-width: 10ch;
      font-size: clamp(4.2rem, 10vw, 9.4rem);
    }
    h1 .accent { color: var(--ember-light); }
    .section-title {
      max-width: 11ch;
      font-size: clamp(3rem, 7vw, 7rem);
    }
    .lead,
    .section-lead,
    .chapter-text {
      color: var(--muted-light);
      font-size: 1.02rem;
      line-height: 1.78;
    }
    .lead {
      max-width: 58ch;
      margin: 24px 0 0;
      font-size: 1.08rem;
    }
    .section-lead,
    .chapter-text {
      max-width: 64ch;
      margin: 24px 0 0;
    }
    .hero-actions {
      display: flex;
      flex-wrap: wrap;
      gap: 18px;
      margin-top: 36px;
    }
    .btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      min-height: 52px;
      padding: 0 22px;
      border: 1px solid transparent;
      text-decoration: none;
      text-transform: uppercase;
      letter-spacing: .14em;
      font-family: var(--font-display);
      font-size: .84rem;
      transition: transform .2s ease, background .2s ease, border-color .2s ease, color .2s ease;
    }
    .btn:hover { transform: translateY(-1px); }
    .btn-primary { background: var(--ember); color: #fff; }
    .btn-primary:hover { background: var(--ember-soft); }
    .btn-ghost {
      border-color: rgba(255, 250, 245, .36);
      color: #fffaf5;
      background: rgba(255, 250, 245, .08);
    }
    .btn-ghost:hover {
      border-color: var(--ember-light);
      color: #fff;
      background: rgba(225, 106, 59, .16);
    }
    .hero-meta,
    .chip-list {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
    }
    .hero-meta { margin-top: 28px; }
    .hero-meta span,
    .chip-list span {
      display: inline-flex;
      align-items: center;
      min-height: 36px;
      padding: 0 12px;
      border: 1px solid rgba(255, 250, 245, .3);
      background: rgba(18, 14, 12, .72);
      color: rgba(255, 250, 245, .9);
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .09em;
      font-size: .72rem;
      backdrop-filter: blur(12px);
    }

    .glass-panel,
    .card,
    .contact-card,
    .quote-form {
      border: 1px solid var(--line-light);
      background: var(--glass);
      backdrop-filter: blur(22px);
      box-shadow: 0 24px 80px rgba(0, 0, 0, .36);
    }
    .glass-panel {
      padding: 34px;
    }
    .strip {
      display: none;
      min-height: auto;
      padding: 0;
      border-block: 1px solid rgba(255, 250, 245, .12);
      background: rgba(16, 12, 10, .48);
      backdrop-filter: blur(18px);
    }
    .strip-inner {
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      gap: 16px;
      padding: 18px 0;
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .12em;
      font-size: .8rem;
      color: rgba(255, 250, 245, .78);
    }
    .strip-item { display: flex; gap: 10px; align-items: center; }
    .strip-dot {
      width: 10px;
      height: 10px;
      border-radius: 999px;
      background: var(--ember-soft);
      box-shadow: 0 0 0 6px rgba(225, 106, 59, .14);
      flex: 0 0 auto;
    }

    .services-intro {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(280px, .58fr);
      gap: 30px;
      align-items: stretch;
      margin-top: 42px;
    }
    .services-grid {
      margin-top: 30px;
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 18px;
    }
    .card {
      padding: 28px;
      min-height: 178px;
      position: relative;
      overflow: hidden;
    }
    .card::after {
      content: "";
      position: absolute;
      left: 0;
      bottom: 0;
      width: 100%;
      height: 3px;
      background: linear-gradient(90deg, var(--ember), transparent);
      transform: scaleX(0);
      transform-origin: left;
      transition: transform .28s ease;
    }
    .card:hover::after { transform: scaleX(1); }
    .card h3 {
      margin: 0 0 10px;
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .04em;
      font-size: 1.22rem;
      color: #fffaf5;
    }
    .card p {
      margin: 0;
      color: var(--muted-light);
      line-height: 1.74;
      font-size: .95rem;
    }
    .services-grid .card {
      min-height: 150px;
    }
    .license-card {
      background:
        linear-gradient(135deg, rgba(204, 77, 42, .36), transparent 52%),
        var(--glass-strong);
    }
    .license-list {
      display: grid;
      gap: 10px;
      margin-top: 18px;
    }
    .license-row {
      display: flex;
      justify-content: space-between;
      gap: 18px;
      padding-top: 12px;
      border-top: 1px solid rgba(255, 250, 245, .16);
      color: rgba(255, 250, 245, .86);
      font-size: .92rem;
    }
    .license-row strong {
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .08em;
      color: #fffaf5;
      font-size: .86rem;
    }

    .section-header {
      display: flex;
      align-items: baseline;
      gap: 2rem;
      margin-bottom: 4rem;
    }
    .section-eyebrow {
      font-family: var(--font-display);
      font-size: .65rem;
      letter-spacing: .3em;
      text-transform: uppercase;
      color: var(--ember-light);
      white-space: nowrap;
    }
    .feature .section-header h2 {
      margin: 0;
      font-family: var(--font-display);
      font-size: clamp(2.8rem, 5.5vw, 5.8rem);
      font-weight: 500;
      color: #fffaf5;
      line-height: .92;
      text-transform: uppercase;
    }
    .projects-mosaic {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: auto;
      gap: 1px;
      background: rgba(255, 154, 111, .18);
      margin-top: 3rem;
      border: 1px solid rgba(255, 154, 111, .2);
    }
    .project-tile {
      background: rgba(18, 14, 12, .92);
      aspect-ratio: 4 / 3;
      padding: 2rem;
      display: flex;
      align-items: flex-end;
      position: relative;
      overflow: hidden;
      isolation: isolate;
      cursor: zoom-in;
      border: 0;
      color: inherit;
      text-align: left;
    }
    .project-tile:focus-visible {
      outline: 2px solid var(--ember-light);
      outline-offset: 4px;
      z-index: 4;
    }
    .project-tile.tall {
      grid-row: span 2;
      aspect-ratio: auto;
    }
    .project-tile::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: 0;
      background:
        linear-gradient(to top, rgba(18, 14, 12, .92) 0%, rgba(18, 14, 12, .48) 48%, rgba(18, 14, 12, .16) 100%),
        linear-gradient(135deg, rgba(204, 77, 42, .34), transparent 42%);
    }
    .tile-image {
      position: absolute;
      inset: 0;
      z-index: -1;
      width: 100%;
      height: 100%;
      object-fit: cover;
      filter: saturate(.9) contrast(1.06);
      transform: scale(1.02);
      transition: transform .45s ease, filter .45s ease;
    }
    .project-tile:hover .tile-image {
      filter: saturate(1.05) contrast(1.08);
      transform: scale(1.07);
    }
    .tile-content {
      position: relative;
      z-index: 1;
      text-shadow: 0 2px 18px rgba(0, 0, 0, .48);
    }
    .tile-tag {
      font-family: var(--font-display);
      font-size: .65rem;
      letter-spacing: .2em;
      text-transform: uppercase;
      color: var(--ember-light);
      margin-bottom: .35rem;
    }
    .tile-title {
      font-family: var(--font-display);
      font-size: 1.18rem;
      font-weight: 500;
      color: #fffaf5;
      letter-spacing: .04em;
      text-transform: uppercase;
    }
    .projects-cta {
      margin-top: 3rem;
      text-align: center;
    }
    .project-video-row {
      margin-top: 1px;
      background: rgba(255, 154, 111, .18);
      border: 1px solid rgba(255, 154, 111, .2);
      border-top: 0;
      padding: 1px;
    }
    .project-video-frame {
      position: relative;
      width: 100%;
      aspect-ratio: 16 / 9;
      background: rgba(18, 14, 12, .92);
      overflow: hidden;
    }
    .project-video-frame iframe {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      border: 0;
      display: block;
    }
    .projects-cta p {
      margin: 0 0 1.25rem;
      color: var(--muted-light);
      font-size: .85rem;
      letter-spacing: .05em;
    }

    .product-grid,
    .contact-grid {
      display: grid;
      grid-template-columns: .92fr 1.08fr;
      gap: 30px;
      margin-top: 42px;
      align-items: start;
    }
    .contact-card,
    .quote-form {
      padding: 32px;
    }
    .contact-row {
      display: flex;
      gap: 14px;
      align-items: flex-start;
      padding: 22px 0;
      border-top: 1px solid rgba(255, 250, 245, .14);
    }
    .contact-row:first-of-type { border-top: 0; padding-top: 0; }
    .contact-label {
      font-size: .76rem;
      color: var(--ember-light);
      margin-bottom: 6px;
    }
    .contact-value {
      color: rgba(255, 250, 245, .82);
      line-height: 1.72;
      text-decoration: none;
    }
    .contact-value:hover { color: var(--ember-light); }
    .quote-form {
      display: grid;
      gap: 18px;
    }
    .form-row {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 18px;
    }
    .form-label {
      display: block;
      margin-bottom: 6px;
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .14em;
      font-size: .72rem;
      color: rgba(255, 250, 245, .64);
    }
    .form-input,
    .form-select,
    .form-textarea {
      width: 100%;
      border: 1px solid rgba(255, 250, 245, .16);
      background: rgba(255, 250, 245, .14);
      color: #fffaf5;
      padding: 14px;
      font: inherit;
      outline: none;
      border-radius: 0;
    }
    .form-input::placeholder,
    .form-textarea::placeholder { color: rgba(255, 250, 245, .42); }
    .form-input:focus,
    .form-select:focus,
    .form-textarea:focus {
      border-color: var(--ember-light);
      background: rgba(255, 250, 245, .14);
    }
    .form-select option {
      color: var(--ink);
    }
    .form-textarea { min-height: 130px; resize: vertical; }
    #form-success {
      display: none;
      padding: 12px 14px;
      border: 1px solid rgba(225, 106, 59, .4);
      color: #fffaf5;
      background: rgba(225, 106, 59, .16);
      font-family: var(--font-display);
      text-transform: uppercase;
      letter-spacing: .12em;
      font-size: .78rem;
    }

    .reveal {
      opacity: 0;
      transform: translateY(28px);
      transition: opacity 520ms ease, transform 640ms ease;
      will-change: opacity, transform;
    }
    .reveal.is-visible {
      opacity: 1;
      transform: none;
    }

    .sticky-call-cta {
      position: fixed;
      left: 50%;
      bottom: 1.25rem;
      transform: translate(-50%, 0);
      opacity: 1;
      pointer-events: auto;
      z-index: 60;
      display: inline-flex;
      align-items: center;
      gap: 12px;
      min-width: min(92vw, 360px);
      padding: 12px 14px;
      background: rgba(18, 14, 12, .82);
      border: 1px solid rgba(255, 250, 245, .18);
      backdrop-filter: blur(18px);
      text-decoration: none;
      color: #fffaf5;
      box-shadow: 0 18px 42px rgba(0, 0, 0, .32);
      transition: transform .25s ease, opacity .25s ease;
    }
    .sticky-call-cta.is-visible { transform: translate(-50%, 0); }
    .sticky-call-icon {
      width: 40px;
      height: 40px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      background: var(--ember);
      color: #fff;
      flex-shrink: 0;
    }
    .sticky-call-label {
      display: block;
      font-size: .7rem;
      color: rgba(255, 250, 245, .62);
    }
    .sticky-call-number {
      display: block;
      font-family: var(--font-display);
      letter-spacing: .06em;
      font-size: 1rem;
    }

    .image-modal {
      position: fixed;
      inset: 0;
      background: rgba(12, 9, 7, .78);
      backdrop-filter: blur(18px);
      display: grid;
      place-items: center;
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      transition: opacity .2s ease, visibility .2s ease;
      z-index: 1000;
      padding: 24px;
    }
    .image-modal.is-open {
      opacity: 1;
      visibility: visible;
      pointer-events: auto;
    }
    .image-modal img {
      max-width: 100%;
      max-height: 92vh;
      object-fit: contain;
      box-shadow: 0 24px 60px rgba(0, 0, 0, .35);
    }
    .image-modal-close {
      position: fixed;
      top: 1rem;
      right: 1rem;
      width: 44px;
      height: 44px;
      border: 1px solid rgba(255, 250, 245, .2);
      background: rgba(18, 14, 12, .55);
      color: #fffaf5;
      font: 300 2.5rem/1 var(--font-body);
      cursor: pointer;
    }
    body.modal-open { overflow: hidden; }

    .footer {
      display: none;
    }
    .footer-inner {
      display: flex;
      justify-content: space-between;
      gap: 20px;
      flex-wrap: wrap;
      border-top: 1px solid rgba(255, 250, 245, .12);
      padding-top: 22px;
    }

    @media (max-width: 920px) {
      .chapter-grid,
      .services-intro,
      .product-grid,
      .contact-grid {
        grid-template-columns: 1fr;
        display: grid;
      }
      .nav-inner {
        min-height: auto;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 14px;
      }
      .brand { flex: 1 1 auto; min-width: 0; }
      .nav-toggle { display: inline-flex; }
      .nav-links {
        display: none;
        order: 3;
        width: 100%;
        justify-content: flex-start;
        gap: 18px;
        padding: 16px 0 0;
        font-size: .84rem;
      }
      .nav.is-open .nav-links { display: flex; }
      .chapter { min-height: auto; padding: 124px 0 88px; }
      .chapter.hero { min-height: 100vh; }
      .chapter.services,
      .chapter.products,
      .chapter.feature,
      .chapter.contact-section { min-height: auto; }
      .strip-inner,
      .services-grid { grid-template-columns: 1fr 1fr; }
      .projects-mosaic { grid-template-columns: repeat(2, 1fr); }
    }
    @media (min-width: 921px) and (max-height: 840px) {
      .nav-inner { min-height: 72px; padding: 10px 0; }
      .brand img { width: 210px; }
      .chapter { padding: 120px 0 92px; }
      .chapter.hero { padding-bottom: 10vh; }
      h1 { font-size: clamp(3.8rem, 8vw, 7.2rem); }
      .section-title,
      .feature .section-header h2 { font-size: clamp(2.8rem, 5.2vw, 5.4rem); }
      .lead,
      .section-lead,
      .chapter-text { line-height: 1.62; }
      .services-intro,
      .product-grid,
      .contact-grid { margin-top: 28px; gap: 22px; }
      .services-grid { margin-top: 22px; gap: 14px; }
      .glass-panel,
      .card,
      .contact-card,
      .quote-form { padding: 22px; }
      .services-grid .card {
        min-height: 126px;
        padding: 20px;
      }
      .services-grid .card h3 { font-size: 1.02rem; margin-bottom: 8px; }
      .services-grid .card p { font-size: .86rem; line-height: 1.5; }
      .section-header { margin-bottom: 2.4rem; }
      .projects-mosaic { margin-top: 2rem; }
      .project-tile { padding: 1.5rem; }
      .form-textarea { min-height: 96px; }
      .contact-row { padding: 16px 0; }
    }
    @media (max-width: 640px) {
      .wrap { width: min(1200px, calc(100vw - 28px)); }
      .brand img { width: min(220px, 60vw); }
      h1 { max-width: 10ch; font-size: 3.45rem; }
      .section-title { max-width: 12ch; font-size: 3rem; }
      .lead,
      .section-lead,
      .chapter-text,
      .projects-cta p { font-size: .96rem; }
      .strip-inner,
      .services-grid,
      .form-row,
      .projects-mosaic { grid-template-columns: 1fr; }
      .section-header {
        display: block;
        margin-bottom: 2.5rem;
      }
      .section-eyebrow {
        display: block;
        margin-bottom: .85rem;
        white-space: normal;
        line-height: 1.5;
      }
      .project-tile,
      .project-tile.tall {
        aspect-ratio: 4 / 3;
      }
      .license-row { display: grid; gap: 4px; }
      .glass-panel,
      .card,
      .contact-card,
      .quote-form { padding: 20px; }
      .project-tile { padding: 1.75rem 1.25rem; }
      .hero-actions { align-items: stretch; }
      .btn { width: 100%; }
    }

    @media (prefers-reduced-motion: reduce) {
      html { scroll-behavior: auto; }
      .reveal {
        opacity: 1;
        transform: none;
        transition: none;
      }
      .scroll-frame-canvas {
        transform: none;
      }
    }
  </style>
</head>
<body>
  <div class="scroll-video-stage" aria-hidden="true">
    <canvas class="scroll-frame-canvas" id="scroll-canvas"></canvas>
  </div>
  <div class="scroll-progress" id="scroll-progress" aria-hidden="true"></div>

  <header class="nav">
    <div class="wrap nav-inner">
      <a class="brand" href="https://crossroadspaving.net/" aria-label="CrossRoads Paving Solutions home">
        <img src="crossroads-logo-white.webp" alt="CrossRoads Paving Solutions" />
      </a>
      <button class="nav-toggle" type="button" aria-label="Open navigation" aria-controls="primary-navigation" aria-expanded="false">
        <span></span><span></span><span></span>
      </button>
      <nav id="primary-navigation" class="nav-links" aria-label="Primary">
        <a href="#services">Overview</a>
        <a href="#products">Products</a>
        <a href="#projects">Projects</a>
        <a href="#contact">Contact</a>
      </nav>
    </div>
  </header>

  <main>
    <section class="chapter hero" id="top" data-chapter>
      <div class="wrap chapter-grid">
        <div class="chapter-copy reveal">
          <div class="eyebrow">South Florida Pavers</div>
          <h1>CrossRoads <span class="accent">Paving</span> Solutions</h1>
          <p class="lead">
            Interlocking paver systems for driveways, patios, pool decks, walkways, and commercial hardscapes across Palm Beach, Broward, and Miami-Dade.
          </p>
          <div class="hero-actions">
            <a class="btn btn-primary" href="#contact">Request Estimate</a>
            <a class="btn btn-ghost" href="#projects">View Work</a>
          </div>
          <div class="hero-meta" aria-label="Core services">
            <span>Paver driveways</span>
            <span>Pool decks</span>
            <span>Natural stone</span>
            <span>Commercial installs</span>
          </div>
        </div>
      </div>
    </section>

    <div class="strip">
      <div class="wrap strip-inner">
        <div class="strip-item"><span class="strip-dot"></span>Licensed, bonded, insured</div>
        <div class="strip-item"><span class="strip-dot"></span>125+ years combined experience</div>
        <div class="strip-item"><span class="strip-dot"></span>Tri-county service area</div>
        <div class="strip-item"><span class="strip-dot"></span>Residential / commercial</div>
      </div>
    </div>

    <section id="services" class="chapter services" data-chapter>
      <div class="wrap">
        <div class="chapter-copy reveal">
          <div class="section-kicker">Company Overview</div>
          <h2 class="section-title">Built on trust</h2>
          <p class="section-lead">
            CrossRoads Paving Solutions is a licensed, bonded and insured paver installation contractor with over 125 years combined industry experience. We are dedicated to quality, service, and the South Florida communities we serve.
          </p>
        </div>

        <div class="services-intro reveal">
          <div class="glass-panel">
            <div class="card-kicker">Mission Statement</div>
            <p class="chapter-text">
              To serve our customers and our community with professionalism, excellence and integrity, to ensure lasting relationships with customers, suppliers and subcontractors, and to make valuable investments in the lives of our employees and their families.
            </p>
          </div>

          <article class="card license-card">
            <div class="card-kicker">Licensed, Bonded & Insured</div>
            <h3>County Licenses</h3>
            <p>Credentialed across the greater tri-county service area.</p>
            <div class="license-list">
              <div class="license-row"><strong>Broward</strong><span>CC#09-IBP-16248-*</span></div>
              <div class="license-row"><strong>Palm Beach</strong><span>CC#U-21445</span></div>
              <div class="license-row"><strong>Miami-Dade</strong><span>CC#E1000461</span></div>
            </div>
          </article>
        </div>

        <div class="services-grid reveal">
          <article class="card">
            <div class="card-kicker">Installation</div>
            <h3>Paver Installer</h3>
            <p>Primary installation work for driveways, patios, pool decks, and walkways.</p>
          </article>
          <article class="card">
            <div class="card-kicker">Curb Appeal</div>
            <h3>Paver Driveways</h3>
            <p>Durable driveway systems designed for curb appeal and long-term use.</p>
          </article>
          <article class="card">
            <div class="card-kicker">Outdoor Living</div>
            <h3>Paver Patios</h3>
            <p>Outdoor living surfaces built to feel finished, functional, and comfortable.</p>
          </article>
          <article class="card">
            <div class="card-kicker">Cool Surfaces</div>
            <h3>Paver Pool Decks</h3>
            <p>Pool deck builds using marble pool deck and natural stone paver options where appropriate.</p>
          </article>
          <article class="card">
            <div class="card-kicker">Premium Finish</div>
            <h3>Natural Stone Pavers</h3>
            <p>Elegant, practical stone products for premium hardscape applications.</p>
          </article>
          <article class="card">
            <div class="card-kicker">Poolside</div>
            <h3>Marble Pool Decks</h3>
            <p>Pool surfaces selected for cool comfort, durability, and visual quality.</p>
          </article>
        </div>
      </div>
    </section>

    <section id="products" class="chapter products" data-chapter>
      <div class="wrap chapter-grid">
        <div class="chapter-copy reveal">
          <div class="section-kicker">Paver Products</div>
          <h2 class="section-title">Lasting beauty, engineered</h2>
          <p class="section-lead">
            Pavers are highly durable, versatile and easy to maintain. They add value and lasting beauty to residential and commercial hardscapes while keeping the surface functional.
          </p>
          <div class="hero-actions">
            <a class="btn btn-primary" href="#contact">Request Estimate</a>
            <a class="btn btn-ghost" href="#projects">View Projects</a>
          </div>
        </div>
        <div class="chapter-side reveal">
          <div class="contact-card">
            <div class="contact-row">
              <div>
                <div class="contact-label">Products</div>
                <div class="chip-list">
                  <span>Concrete</span>
                  <span>Clay</span>
                  <span>Marble</span>
                  <span>Tumbled</span>
                  <span>Specialty</span>
                </div>
              </div>
            </div>
            <div class="contact-row">
              <div>
                <div class="contact-label">Suppliers</div>
                <div class="chip-list">
                  <span>Belgard</span>
                  <span>Tremron</span>
                  <span>Gem Pavers</span>
                  <span>StoneHardscapes</span>
                  <span>Artistic Pavers</span>
                </div>
              </div>
            </div>
            <div class="contact-row">
              <div>
                <div class="contact-label">Featured Materials</div>
                <div class="contact-value">
                  Natural stone pavers are cool to the touch, fade resistant, and slip resistant, making them a strong fit for South Florida pool decks.
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <section id="projects" class="chapter feature" data-chapter>
      <div class="wrap">
        <div class="section-header reveal">
          <span class="section-eyebrow">Portfolio</span>
          <h2>Recent Project Work</h2>
        </div>

        <div class="projects-mosaic reveal" aria-label="CrossRoads project portfolio">
          <button class="project-tile tall" type="button" aria-label="View full size image of residential paver driveway">
            <img class="tile-image" src="crossroads-project-residential-driveway.jpg" alt="Paver driveway installation with silver travertine pavers" loading="lazy" decoding="async" />
            <div class="tile-content">
              <div class="tile-tag">Residential</div>
              <div class="tile-title">Paver Driveway</div>
            </div>
          </button>
          <button class="project-tile" type="button" aria-label="View full size image of paver pool deck">
            <img class="tile-image" src="crossroads-project-pool-deck.jpg" alt="Paver pool deck installation with tumbled ivory pavers" loading="lazy" decoding="async" />
            <div class="tile-content">
              <div class="tile-tag">Pool Deck</div>
              <div class="tile-title">Tumbled Ivory Pavers</div>
            </div>
          </button>
          <button class="project-tile" type="button" aria-label="View full size image of natural stone pavers">
            <img class="tile-image" src="crossroads-project-natural-stone.jpg" alt="Natural stone paver pool deck" loading="lazy" decoding="async" />
            <div class="tile-content">
              <div class="tile-tag">Natural Stone</div>
              <div class="tile-title">Poolside Hardscape</div>
            </div>
          </button>
          <button class="project-tile" type="button" aria-label="View full size image of clay paver driveway">
            <img class="tile-image" src="crossroads-project-clay-driveway.jpg" alt="Clay paver driveway in Coral Springs" loading="lazy" decoding="async" />
            <div class="tile-content">
              <div class="tile-tag">Clay Pavers</div>
              <div class="tile-title">Driveway Finish</div>
            </div>
          </button>
          <button class="project-tile" type="button" aria-label="View full size image of driveway remodel">
            <img class="tile-image" src="crossroads-project-driveway-remodel.jpg" alt="Paver driveway installation and remodel" loading="lazy" decoding="async" />
            <div class="tile-content">
              <div class="tile-tag">Remodel</div>
              <div class="tile-title">Driveway Upgrade</div>
            </div>
          </button>
        </div>

        <div class="project-video-row reveal" aria-label="CrossRoads project video">
          <div class="project-video-frame">
            <iframe src="https://player.vimeo.com/video/456660705?autoplay=1&amp;muted=1&amp;loop=1&amp;autopause=0&amp;playsinline=1&amp;title=0&amp;byline=0&amp;portrait=0" title="CrossRoads Paving Solutions project video" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy" referrerpolicy="strict-origin-when-cross-origin"></iframe>
          </div>
        </div>

        <div class="projects-cta reveal">
          <p>See more completed CrossRoads work on Instagram</p>
          <a href="https://www.instagram.com/crossroadspavingsolutions" class="btn btn-ghost" target="_blank" rel="noopener">@crossroadspavingsolutions</a>
        </div>
      </div>
    </section>

    <section id="contact" class="chapter contact-section" data-chapter>
      <div class="wrap">
        <div class="chapter-copy reveal">
          <div class="section-kicker">Start A Project</div>
          <h2 class="section-title">Request an estimate</h2>
          <p class="section-lead">
            Tell us about your driveway, patio, pool deck, natural stone, or commercial paver project.
          </p>
        </div>

        <div class="contact-grid reveal">
          <div class="contact-card">
            <div class="contact-row">
              <div>
                <div class="contact-label">Service Area</div>
                <div class="contact-value">Palm Beach County, Broward County, and Miami-Dade County.</div>
              </div>
            </div>
            <div class="contact-row">
              <div>
                <div class="contact-label">Products</div>
                <div class="chip-list">
                  <span>Standard Concrete Pavers</span>
                  <span>Clay Pavers</span>
                  <span>Marble Pavers</span>
                  <span>Tumbled Pavers</span>
                  <span>Specialty Pavers</span>
                </div>
              </div>
            </div>
            <div class="contact-row">
              <div>
                <div class="contact-label">Phone / Text</div>
                <a class="contact-value" href="tel:+17862780478">(786) 278-0478</a>
              </div>
            </div>
          </div>

          <form class="quote-form" onsubmit="handleSubmit(event)">
            <div class="form-row">
              <div class="form-field">
                <label class="form-label" for="fname">First Name</label>
                <input class="form-input" type="text" id="fname" placeholder="John" />
              </div>
              <div class="form-field">
                <label class="form-label" for="lname">Last Name</label>
                <input class="form-input" type="text" id="lname" placeholder="Smith" />
              </div>
            </div>
            <div class="form-row">
              <div class="form-field">
                <label class="form-label" for="email">Email</label>
                <input class="form-input" type="email" id="email" placeholder="[email protected]" />
              </div>
              <div class="form-field">
                <label class="form-label" for="phone">Phone / Text</label>
                <input class="form-input" type="tel" id="phone" placeholder="(305) 000-0000" />
              </div>
            </div>
            <div class="form-field">
              <label class="form-label" for="service">Service Needed</label>
              <select class="form-select" id="service">
                <option>paver installer</option>
                <option>paver driveways</option>
                <option>paver patios</option>
                <option>paver pool decks</option>
                <option>natural stone pavers</option>
                <option>commercial paver installer</option>
              </select>
            </div>
            <div class="form-field">
              <label class="form-label" for="message">Tell Us About Your Project</label>
              <textarea class="form-textarea" id="message" placeholder="Project scope, address, product ideas, timeline, and any details helpful for estimating."></textarea>
            </div>
            <button class="btn btn-primary" type="submit">Send Message</button>
            <div id="form-success">Message sent. We'll be in touch shortly.</div>
          </form>
        </div>
      </div>
    </section>
  </main>

  <a class="sticky-call-cta" href="tel:+17862780478">
    <span class="sticky-call-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true"><path d="M6.6 10.8c1.4 2.7 3.6 4.9 6.3 6.3l2.1-2.1c.3-.3.8-.4 1.1-.2 1.1.4 2.2.6 3.5.6.5 0 .9.4.9.9V20c0 .5-.4.9-.9.9C10.7 20.9 3.1 13.3 3.1 4c0-.5.4-.9.9-.9h3.7c.5 0 .9.4.9.9 0 1.2.2 2.4.6 3.5.1.4.1.9-.2 1.1L6.6 10.8z"/></svg>
    </span>
    <span class="sticky-call-copy">
      <span class="sticky-call-label">Call or Text</span>
      <span class="sticky-call-number">(786) 278-0478</span>
    </span>
  </a>

  <div class="image-modal" id="image-modal" role="dialog" aria-modal="true" aria-hidden="true">
    <button class="image-modal-close" type="button" aria-label="Close full screen image">&times;</button>
    <img id="image-modal-img" src="" alt="" />
  </div>

  <footer class="footer">
    <div class="wrap footer-inner">
      <div>CrossRoads Paving Solutions</div>
      <div>crossroadspaving.net</div>
    </div>
  </footer>

  <script>
    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const nav = document.querySelector('.nav');
    const navToggle = document.querySelector('.nav-toggle');
    const navLinks = document.querySelectorAll('.nav-links a');
    const canvas = document.getElementById('scroll-canvas');
    const context = canvas.getContext('2d');
    const scrollProgress = document.getElementById('scroll-progress');
    const chapters = Array.from(document.querySelectorAll('[data-chapter][id]'));
    const firstFrameNumber = 20;
    const frameCount = 222;
    const framePath = 'crossroads-new-sequence/frame_';
    const frames = new Array(frameCount);
    let targetProgress = 0;
    let displayProgress = 0;
    let requestedFrameIndex = -1;
    let paintedFrameIndex = -1;
    let activeChapterIndex = -1;
    let canvasWidth = 0;
    let canvasHeight = 0;
    let resizeQueued = false;

    function clamp(value, min, max) {
      return Math.min(Math.max(value, min), max);
    }

    function getPageProgress() {
      const scrollable = document.documentElement.scrollHeight - window.innerHeight;
      if (scrollable <= 0) return 0;
      return clamp(window.scrollY / scrollable, 0, 1);
    }

    function smoothstep(value) {
      const t = clamp(value, 0, 1);
      return t * t * (3 - 2 * t);
    }

    function getStoryProgress() {
      if (chapters.length <= 1) return 0;

      const viewportHeight = window.innerHeight;
      const scrollY = window.scrollY;
      const maxChapterIndex = chapters.length - 1;

      for (let index = 0; index < maxChapterIndex; index += 1) {
        const nextTop = chapters[index + 1].offsetTop;
        const transitionStart = nextTop - viewportHeight * 0.62;
        const transitionEnd = nextTop - viewportHeight * 0.12;

        if (scrollY < transitionStart) return index / maxChapterIndex;
        if (scrollY <= transitionEnd) {
          const transitionProgress = smoothstep((scrollY - transitionStart) / (transitionEnd - transitionStart));
          return (index + transitionProgress) / maxChapterIndex;
        }
      }

      return 1;
    }

    function getActiveChapterIndex() {
      const marker = window.scrollY + window.innerHeight * 0.42;
      let activeIndex = 0;

      chapters.forEach((chapter, index) => {
        if (chapter.offsetTop <= marker) activeIndex = index;
      });

      return activeIndex;
    }

    function frameSrc(index) {
      return `${framePath}${String(index + firstFrameNumber).padStart(4, '0')}.jpg`;
    }

    function loadFrame(index) {
      if (index < 0 || index >= frameCount) return null;
      if (frames[index]) return frames[index];

      const entry = {
        image: new Image(),
        loaded: false,
        failed: false
      };
      const image = new Image();
      image.decoding = 'async';
      image.loading = 'eager';
      image.src = frameSrc(index);
      image.addEventListener('load', () => {
        entry.loaded = true;
        const expectedIndex = Math.round(displayProgress * (frameCount - 1));
        if (Math.abs(expectedIndex - index) <= 1 || paintedFrameIndex < 0) drawFrame(expectedIndex);
      }, { once: true });
      image.addEventListener('error', () => {
        entry.failed = true;
      }, { once: true });
      entry.image = image;
      frames[index] = entry;
      return entry;
    }

    function preloadAround(index) {
      for (let offset = -6; offset <= 12; offset += 1) loadFrame(index + offset);
    }

    function preloadChapterFrames() {
      const maxIndex = Math.max(chapters.length - 1, 1);
      chapters.forEach((_, chapterIndex) => {
        const anchorFrame = Math.round((chapterIndex / maxIndex) * (frameCount - 1));
        for (let offset = -8; offset <= 8; offset += 1) loadFrame(anchorFrame + offset);
      });
    }

    function preloadAllFrames() {
      let index = 0;
      const loadBatch = () => {
        const end = Math.min(index + 8, frameCount);
        for (; index < end; index += 1) loadFrame(index);
        if (index < frameCount) {
          if ('requestIdleCallback' in window) requestIdleCallback(loadBatch, { timeout: 500 });
          else setTimeout(loadBatch, 40);
        }
      };
      loadBatch();
    }

    function getDrawableFrame(index) {
      const direct = frames[index];
      if (direct && direct.loaded) return { entry: direct, index };

      for (let distance = 1; distance < frameCount; distance += 1) {
        const before = frames[index - distance];
        if (before && before.loaded) return { entry: before, index: index - distance };
        const after = frames[index + distance];
        if (after && after.loaded) return { entry: after, index: index + distance };
      }
      return null;
    }

    function resizeCanvas() {
      const pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
      canvasWidth = window.innerWidth;
      canvasHeight = window.innerHeight;
      canvas.width = Math.round(canvasWidth * pixelRatio);
      canvas.height = Math.round(canvasHeight * pixelRatio);
      context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
      context.imageSmoothingEnabled = true;
      context.imageSmoothingQuality = 'medium';
      requestedFrameIndex = -1;
      paintedFrameIndex = -1;
      drawFrame(Math.round(displayProgress * (frameCount - 1)));
    }

    function drawFrame(index) {
      const safeIndex = clamp(index, 0, frameCount - 1);
      requestedFrameIndex = safeIndex;
      loadFrame(safeIndex);
      preloadAround(safeIndex);

      const drawable = getDrawableFrame(safeIndex);
      if (!drawable || !canvasWidth || !canvasHeight) return;
      if (requestedFrameIndex === safeIndex && paintedFrameIndex === drawable.index) return;

      const source = drawable.entry.image;
      const sourceWidth = source.naturalWidth;
      const sourceHeight = source.naturalHeight;
      if (!sourceWidth || !sourceHeight) return;

      paintedFrameIndex = drawable.index;
      const imageRatio = sourceWidth / sourceHeight;
      const canvasRatio = canvasWidth / canvasHeight;
      let drawWidth = canvasWidth;
      let drawHeight = canvasHeight;
      let drawX = 0;
      let drawY = 0;

      if (imageRatio > canvasRatio) {
        drawHeight = canvasHeight;
        drawWidth = drawHeight * imageRatio;
        drawX = (canvasWidth - drawWidth) / 2;
      } else {
        drawWidth = canvasWidth;
        drawHeight = drawWidth / imageRatio;
        drawY = (canvasHeight - drawHeight) / 2;
      }

      context.clearRect(0, 0, canvasWidth, canvasHeight);
      context.drawImage(source, drawX, drawY, drawWidth, drawHeight);
    }

    function updateChrome(progress = getPageProgress()) {
      nav.classList.toggle('is-scrolled', window.scrollY > 16);
      scrollProgress.style.transform = `scaleX(${progress})`;
    }

    function updateChapters() {
      const nextActiveIndex = getActiveChapterIndex();

      chapters.forEach((chapter, index) => {
        chapter.classList.toggle('is-active', index === nextActiveIndex);
      });

      if (activeChapterIndex !== nextActiveIndex) {
        activeChapterIndex = nextActiveIndex;
        const id = chapters[nextActiveIndex].id;
        navLinks.forEach((link) => {
          link.classList.toggle('is-active', link.getAttribute('href') === `#${id}`);
        });
      }
    }

    function updateTargetProgress() {
      targetProgress = getStoryProgress();
      updateChrome(getPageProgress());
      updateChapters();
    }

    function tick() {
      const easing = prefersReducedMotion ? 1 : 0.34;
      displayProgress += (targetProgress - displayProgress) * easing;
      if (Math.abs(targetProgress - displayProgress) < 0.0005) displayProgress = targetProgress;

      const nextFrame = Math.round(displayProgress * (frameCount - 1));
      if (nextFrame !== requestedFrameIndex || nextFrame !== paintedFrameIndex) drawFrame(nextFrame);
      requestAnimationFrame(tick);
    }

    navToggle.addEventListener('click', () => {
      const isOpen = navToggle.getAttribute('aria-expanded') === 'true';
      navToggle.setAttribute('aria-expanded', String(!isOpen));
      nav.classList.toggle('is-open', !isOpen);
    });

    navLinks.forEach((link) => {
      link.addEventListener('click', (event) => {
        const targetIndex = chapters.findIndex((chapter) => `#${chapter.id}` === link.getAttribute('href'));
        if (targetIndex < 0) return;
        event.preventDefault();
        navToggle.setAttribute('aria-expanded', 'false');
        nav.classList.remove('is-open');
        const destination = Math.max(chapters[targetIndex].offsetTop, 0);
        window.scrollTo({ top: destination, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
      });
    });

    loadFrame(0);
    preloadChapterFrames();
    resizeCanvas();
    updateTargetProgress();
    updateChapters();
    preloadAllFrames();
    requestAnimationFrame(tick);

    const revealObserver = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) entry.target.classList.add('is-visible');
      });
    }, { threshold: 0.18, rootMargin: '0px 0px -8% 0px' });

    document.querySelectorAll('.reveal').forEach((element) => revealObserver.observe(element));

    window.crossroadsStory = {
      frameCount,
      get targetProgress() { return targetProgress; },
      get displayProgress() { return displayProgress; },
      get requestedFrameIndex() { return requestedFrameIndex; },
      get paintedFrameIndex() { return paintedFrameIndex; },
      get activeChapter() { return chapters[activeChapterIndex]?.id || null; }
    };

    window.addEventListener('scroll', updateTargetProgress, { passive: true });
    window.addEventListener('resize', () => {
      if (resizeQueued) return;
      resizeQueued = true;
      requestAnimationFrame(() => {
        resizeQueued = false;
        resizeCanvas();
        updateTargetProgress();
      });
    });

    function handleSubmit(event) {
      event.preventDefault();
      const form = event.target;
      const successMessage = document.getElementById('form-success');
      form.reset();
      form.querySelectorAll('input, select, textarea, button[type="submit"]').forEach((control) => {
        control.disabled = true;
      });
      form.classList.add('is-submitted');
      successMessage.style.display = 'block';
      successMessage.textContent = "Message sent. We'll be in touch shortly.";
    }

    const modal = document.getElementById('image-modal');
    const modalImage = document.getElementById('image-modal-img');
    const closeModalButton = modal.querySelector('.image-modal-close');

    function openProjectModal(tile) {
      const image = tile.querySelector('.tile-image');
      if (!image) return;
      modalImage.src = image.currentSrc || image.src;
      modalImage.alt = image.alt || 'CrossRoads project image';
      modal.classList.add('is-open');
      modal.setAttribute('aria-hidden', 'false');
      document.body.classList.add('modal-open');
    }

    document.querySelectorAll('.project-tile').forEach((tile) => {
      tile.addEventListener('click', () => openProjectModal(tile));
    });

    function closeModal() {
      modal.classList.remove('is-open');
      modal.setAttribute('aria-hidden', 'true');
      document.body.classList.remove('modal-open');
      modalImage.removeAttribute('src');
      modalImage.alt = '';
    }

    modalImage.addEventListener('click', (event) => event.stopPropagation());
    closeModalButton.addEventListener('click', (event) => {
      event.stopPropagation();
      closeModal();
    });
    modal.addEventListener('click', (event) => {
      if (event.target === modal) closeModal();
    });
    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape' && modal.classList.contains('is-open')) closeModal();
    });
  </script>
</body>
</html>