← back to NationalPaperHangers

views/public/book.ejs

505 lines

<%- include('../partials/head', { title }) %>
<%- include('../partials/header') %>
<style>
  .intake-steps{counter-reset:step;display:flex;gap:8px;margin:0 0 24px;flex-wrap:wrap}
  .intake-steps li{counter-increment:step;list-style:none;font-size:12px;letter-spacing:0.04em;text-transform:uppercase;color:var(--muted,#888);padding:8px 14px;border-radius:999px;background:var(--card-bg,#f3f1ea);font-weight:600}
  .intake-steps li::before{content:counter(step) ". "}
  .intake-steps li.is-done{color:#16a34a;background:#dcfce7}
  .intake-steps li.is-active{color:#fff;background:var(--ink,#0a0a0a)}
  .intake-step{display:none;padding:24px 28px;border-radius:14px;background:var(--card-bg,#fff);border:1px solid var(--border,#d4d2c8);margin:0 0 16px}
  .intake-step.is-active{display:block}
  .intake-step h2{font-size:22px;margin:0 0 6px}
  .intake-step .helper{font-size:13px;color:var(--muted,#666);margin:0 0 18px}
  .intake-step .nav{display:flex;justify-content:space-between;gap:12px;margin-top:20px}
  .radio-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px;margin:0 0 12px}
  .radio-cards label{display:block;border:2px solid var(--border,#d4d2c8);border-radius:10px;padding:14px 16px;cursor:pointer;background:var(--bg,#fff);transition:all .15s}
  .radio-cards label:hover{border-color:#9aa39a}
  .radio-cards input{position:absolute;opacity:0}
  .radio-cards input:checked + span,
  .radio-cards label:has(input:checked){border-color:var(--ink,#0a0a0a);background:#fff8e6}
  .radio-cards strong{display:block;font-size:14px;margin:0 0 2px}
  .radio-cards small{display:block;font-size:12px;color:var(--muted,#666);line-height:1.4}
  .signin-card{display:flex;align-items:center;gap:14px;border:1px solid var(--border,#d4d2c8);border-radius:14px;padding:14px 18px;background:#f8f7f2;margin:0 0 18px}
  .signin-card img{width:36px;height:36px;border-radius:50%;object-fit:cover;background:#ddd}
  .signin-card .who{flex:1;font-size:14px}
  .signin-card .who strong{display:block;font-size:15px}
  .signin-card .gbtn{display:inline-flex;align-items:center;gap:8px;background:#fff;color:#222;border:1px solid #ccc;padding:10px 18px;border-radius:8px;font-weight:600;text-decoration:none;font-size:14px}
  .signin-card .gbtn svg{width:18px;height:18px}
  /* Visit-kind picker removed 2026-05-06 (NPH only books consultations). */

  /* ── Room capture & measure — camera-driven quote brief ─────────── */
  .room-capture{margin:0 0 18px;padding:16px;border:1px dashed var(--border,#d4d2c8);border-radius:12px;background:var(--bg-alt,#f8f7f2)}
  .rc-intro strong{font-size:14px}
  .rc-intro .helper{margin:4px 0 12px;font-size:12px;color:var(--muted,#666);line-height:1.5}
  .rc-room{border:1px solid var(--border,#d4d2c8);border-radius:10px;padding:14px;margin:0 0 12px;background:var(--card-bg,#fff)}
  .rc-room-head{display:flex;gap:8px;align-items:center;margin:0 0 10px}
  .rc-room-label{flex:1}
  .rc-remove{border:none;background:transparent;font-size:16px;cursor:pointer;color:var(--muted,#999);padding:4px 8px;line-height:1}
  .rc-remove:hover{color:#dc2626}
  .rc-file-btn{display:block;cursor:pointer;margin:0 0 4px}
  .rc-file-btn>span{display:inline-block;padding:10px 16px;border-radius:8px;background:var(--ink,#0a0a0a);color:#fff;font-size:13px;font-weight:600}
  .rc-file-btn.rc-secondary>span{background:transparent;color:var(--ink,#0a0a0a);border:1px solid var(--border,#d4d2c8)}
  .rc-stage{margin-top:12px}
  .rc-photo-wrap{position:relative;display:inline-block;max-width:100%;touch-action:none;-webkit-user-select:none;user-select:none}
  .rc-photo{display:block;max-width:100%;max-height:380px;border-radius:8px}
  .rc-box{position:absolute;border:2px solid #f5c451;box-shadow:0 0 0 9999px rgba(0,0,0,0.32);box-sizing:border-box}
  .rc-handle{position:absolute;width:22px;height:22px;margin:-11px;border-radius:50%;background:#f5c451;border:2px solid #0a0a0a;cursor:grab;touch-action:none}
  .rc-handle[data-corner=tl]{left:0;top:0}.rc-handle[data-corner=tr]{left:100%;top:0}
  .rc-handle[data-corner=bl]{left:0;top:100%}.rc-handle[data-corner=br]{left:100%;top:100%}
  .rc-wallpaper-layer{position:absolute;inset:0;background-repeat:repeat;opacity:0.92;pointer-events:none}
  .rc-hint{font-size:12px;color:var(--muted,#666);margin:8px 0}
  .rc-dims{display:flex;gap:10px;margin:8px 0}
  .rc-dims label{flex:1;font-size:12px}
  .rc-estimate{font-size:12px;color:#16a34a;margin:0 0 8px;min-height:1em}
  .rc-wallpaper{margin-top:10px;border-top:1px solid var(--border,#eee);padding-top:10px}
  .rc-wallpaper summary{cursor:pointer;font-size:13px;font-weight:600}
  .rc-wp-specs{display:flex;gap:8px;margin:8px 0}
  .rc-wp-specs label{flex:1;font-size:12px}
  .rc-wp-note{font-size:11px;color:var(--muted,#888);margin:0}
  .rc-wp-note.is-error{color:#dc2626}
  .rc-uploading{font-size:12px;color:var(--muted,#666);margin-top:8px}
  .rc-uploading.is-error{color:#dc2626}
  /* Touch devices — finger-sized drag handles (44px hit area via padding). */
  @media (pointer: coarse) {
    .rc-handle{width:34px;height:34px;margin:-17px}
  }
  /* Small screens — the capture flow is the primary surface (phone camera),
     so stack the spec inputs, full-width the file buttons, cap photo height. */
  @media (max-width: 560px) {
    .room-capture{padding:12px}
    .rc-room{padding:12px}
    .rc-photo{max-height:58vh}
    .rc-dims,.rc-wp-specs{flex-wrap:wrap}
    .rc-dims label{flex:1 1 46%}
    .rc-wp-specs label{flex:1 1 100%}
    .rc-file-btn>span{display:block;text-align:center}
  }

  /* ── Sensor measurement (room-measure.js: WebXR AR / gyro clinometer) ─── */
  .rc-measure-btn{margin:2px 0 6px;width:100%}
  .rc-measure-note{font-size:11px;color:var(--muted,#888);margin:0 0 8px;min-height:1em}
  .rc-measure-note.is-error{color:#dc2626}
  .rc-measure-note.is-ok{color:#16a34a}
  .rmx-overlay{position:fixed;inset:0;z-index:99999;display:flex;flex-direction:column;justify-content:flex-end;pointer-events:none}
  .rmx-card{pointer-events:auto;background:rgba(12,12,12,0.94);color:#fff;border-radius:16px 16px 0 0;padding:18px 20px 22px;box-shadow:0 -8px 30px rgba(0,0,0,0.4);max-width:560px;width:100%;margin:0 auto}
  .rmx-title{font-size:15px;font-weight:700;margin:0 0 8px}
  .rmx-body{font-size:13px;line-height:1.5}
  .rmx-instr{margin:0 0 10px}
  .rmx-step{font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:#f5c451;font-weight:700;margin:0 0 4px}
  .rmx-status{margin:8px 0;font-size:12px;font-weight:600}
  .rmx-hit-ok{color:#4ade80}
  .rmx-hit-none{color:#fbbf24}
  .rmx-readout{font-size:20px;font-weight:700;margin:8px 0;font-variant-numeric:tabular-nums}
  .rmx-field{display:block;margin:8px 0}
  .rmx-field input{display:block;width:100%;margin-top:4px;padding:10px;border-radius:8px;border:1px solid #555;background:#1a1a1a;color:#fff;font-size:16px}
  .rmx-crosshair{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);font-size:54px;line-height:1;color:#f5c451;text-shadow:0 0 6px rgba(0,0,0,0.9);pointer-events:none}
  .rmx-actions{display:flex;gap:10px;margin-top:16px}
  .rmx-actions button{flex:1;padding:13px;border-radius:9px;font-size:14px;font-weight:700;cursor:pointer;border:0}
  .rmx-cancel{background:transparent;color:#bbb;border:1px solid #555}
  .rmx-primary{background:#f5c451;color:#0a0a0a}
  .rmx-primary:disabled{opacity:0.45;cursor:not-allowed}
</style>

<section class="book-page">
  <header class="book-head">
    <p class="kicker">Book · <%= installer.business_name %> · <%= installer.city %>, <%= installer.state %></p>
    <h1 class="display-sm">Schedule a consultation</h1>
    <p class="lede">Five quick questions, then pick a time on <%= installer.business_name %>'s live calendar.</p>
  </header>

  <% if (consumer) { %>
    <div class="signin-card" data-consumer-signin>
      <% if (consumer.picture_url) { %><img src="<%= consumer.picture_url %>" alt=""><% } %>
      <div class="who"><strong><%= consumer.name || consumer.email %></strong>Signed in with Google · <%= consumer.email %></div>
      <form method="post" action="/auth/google/logout" style="margin:0">
        <input type="hidden" name="_csrf" value="<%= csrfToken %>">
        <input type="hidden" name="next" value="/installer/<%= installer.slug %>/book">
        <button type="submit" class="btn btn-ghost btn-sm">Sign out</button>
      </form>
    </div>
  <% } else { %>
    <div class="signin-card">
      <div class="who"><strong>Sign in to save your project history</strong>Or continue as a guest — sign-in is optional but recommended.</div>
      <div style="display:flex;gap:8px;flex-wrap:wrap">
        <a class="gbtn" href="/auth/google/start?next=<%= encodeURIComponent('/installer/' + installer.slug + '/book') %>">
          <svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path fill="#FFC107" d="M43.6 20.5H42V20H24v8h11.3c-1.6 4.5-5.9 8-11.3 8-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3l5.7-5.7C34.5 6.1 29.5 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.7-.4-4z"/><path fill="#FF3D00" d="M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3l5.7-5.7C34.5 6.1 29.5 4 24 4 16.3 4 9.6 8.3 6.3 14.7z"/><path fill="#4CAF50" d="M24 44c5.4 0 10.3-2 14-5.4l-6.5-5.5c-2.1 1.4-4.7 2.3-7.5 2.3-5.4 0-9.7-3.5-11.3-8L6 32.4C9.3 38.8 16 44 24 44z"/><path fill="#1976D2" d="M43.6 20.5H42V20H24v8h11.3c-.7 2.1-2 3.9-3.7 5.2L37.9 39c-.5.4 6.1-4.4 6.1-15 0-1.3-.1-2.7-.4-4z"/></svg>
          Sign in with Google
        </a>
        <a class="gbtn" style="background:#0a66c2;color:#fff;border-color:#0a66c2" href="/auth/linkedin/start?next=<%= encodeURIComponent('/installer/' + installer.slug + '/book') %>">
          <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#fff"><path d="M20.5 2h-17A1.5 1.5 0 002 3.5v17A1.5 1.5 0 003.5 22h17a1.5 1.5 0 001.5-1.5v-17A1.5 1.5 0 0020.5 2zM8 19H5v-9h3zM6.5 8.25A1.75 1.75 0 118.3 6.5a1.78 1.78 0 01-1.8 1.75zM19 19h-3v-4.74c0-1.42-.6-1.93-1.38-1.93A1.74 1.74 0 0013 14.19a.66.66 0 000 .14V19h-3v-9h2.9v1.3a3.11 3.11 0 012.7-1.4c1.55 0 3.36.86 3.36 3.66z"/></svg>
          Sign in with LinkedIn
        </a>
      </div>
    </div>
  <% } %>

  <% if (!calendarEnabled) { %>
    <%
      function _safeWeb(u) {
        if (!u) return null;
        try { var x = new URL(u); return (x.protocol === 'http:' || x.protocol === 'https:') ? x.toString() : null; }
        catch(e){ return null; }
      }
      var _site = _safeWeb(installer.website);
      var _reason = (typeof calendarBlockedReason !== 'undefined') ? calendarBlockedReason : 'free_tier';
      var _heading, _explainer;
      if (_reason === 'unclaimed') {
        _heading = 'Available times appear after the studio claims their listing';
        _explainer = '<strong>' + installer.business_name + '</strong> is in the National Paper Hangers directory but the studio team hasn\'t claimed and configured the listing yet. Real bookable slots become available once they sign up and set their working hours. Until then, reach out directly:';
      } else if (_reason === 'no_schedule') {
        _heading = 'Studio hasn\'t set their availability yet';
        _explainer = '<strong>' + installer.business_name + '</strong> is on a paid plan but hasn\'t finished configuring their calendar. Bookable slots appear once they set working hours. In the meantime, reach them directly:';
      } else {
        _heading = 'Self-booking isn\'t enabled for this studio yet';
        _explainer = 'Reach <strong>' + installer.business_name + '</strong> directly — they typically respond within ' + installer.response_time_hours + ' hours:';
      }
    %>
    <div class="callout">
      <h2 style="margin-top:0"><%= _heading %></h2>
      <p><%- _explainer %></p>
      <div class="profile-actions" style="margin:16px 0">
        <% if (_site) { %><a href="<%= _site %>" target="_blank" rel="noopener" class="btn btn-primary">Visit studio site ↗</a><% } %>
        <% if (installer.instagram_handle) { %><a href="https://instagram.com/<%= encodeURIComponent(installer.instagram_handle) %>" target="_blank" rel="noopener" class="btn btn-ghost">@<%= installer.instagram_handle %></a><% } %>
        <% if (installer.phone) { %><a href="tel:<%= installer.phone %>" class="btn btn-ghost">Call <%= installer.phone %></a><% } %>
      </div>
      <% if (_reason === 'unclaimed') { %>
        <form method="post" action="/installer/<%= installer.slug %>/notify-when-live" style="margin:24px 0;padding:20px;border-top:1px solid var(--border);background:var(--bg-alt,#f8f7f2);border-radius:10px">
          <input type="hidden" name="_csrf" value="<%= typeof csrfToken !== 'undefined' ? csrfToken : '' %>">
          <h3 style="margin:0 0 8px;font-size:18px">Want a heads-up when they're on?</h3>
          <p class="muted" style="margin:0 0 12px;font-size:13px">We'll email you the moment <%= installer.business_name %> claims their listing and turns on bookings — usually within a week of outreach.</p>
          <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
            <label style="flex:2;min-width:220px">Email
              <input type="email" name="email" required placeholder="you@example.com" style="width:100%;box-sizing:border-box">
            </label>
            <label style="flex:1;min-width:80px">ZIP
              <input type="text" name="zip" maxlength="10" placeholder="optional" style="width:100%;box-sizing:border-box">
            </label>
            <button type="submit" class="btn btn-primary">Notify me</button>
          </div>
          <p class="muted" style="margin:10px 0 0;font-size:11px">One email when they're live, then nothing. We don't share emails — you can unsubscribe from the notification with one click.</p>
        </form>
        <p class="muted" style="margin-top:16px;font-size:13px">Are you the owner of <%= installer.business_name %>? <a href="/installer/<%= installer.slug %>#claim">Claim this listing →</a> and configure your calendar to start receiving bookings directly through NPH.</p>
      <% } else { %>
        <p class="muted">Want to book directly into a calendar instead? <a href="/find">Browse studios with live calendars →</a></p>
      <% } %>
      <a href="/installer/<%= installer.slug %>" class="btn btn-ghost btn-sm" style="margin-top:8px">← Back to <%= installer.business_name %>'s profile</a>
    </div>
  <% } else { %>
    <div class="book-layout">
      <div class="book-calendar" data-installer-slug="<%= installer.slug %>">
        <div class="calendar-controls">
          <button type="button" class="btn btn-ghost" data-cal-prev>← Earlier</button>
          <span data-cal-range>Loading…</span>
          <button type="button" class="btn btn-ghost" data-cal-next>Later →</button>
        </div>
        <div class="slot-grid" data-cal-slots>
          <p class="slot-loading">Loading available slots…</p>
        </div>
      </div>

      <form class="book-form" id="book-form" autocomplete="on" data-intake-wizard>
        <input type="hidden" name="installer_slug" value="<%= installer.slug %>">
        <input type="hidden" name="scheduled_start" id="scheduled_start" required>
        <input type="hidden" name="scheduled_end" id="scheduled_end" required>
        <!-- NPH only books the initial visit (consultation). Install scheduling
             happens directly between the consumer and the installer after the
             visit — keeps NPH on the lead-coordination side of CSLB §7026. -->
        <input type="hidden" name="project_type" id="project_type" value="consultation">

        <p class="visit-kind-note" style="margin:0 0 16px;padding:14px 18px;border-left:3px solid var(--brass,#b8860b);background:var(--bg-alt,#f8f7f2);border-radius:0 8px 8px 0;font-size:14px;line-height:1.5">
          <strong>You're scheduling an initial visit</strong> with <%= installer.business_name %> — walk-through, measurement, and recommendations. The studio brings samples and shares a quote after. Install scheduling, contracts, and pricing for the work itself happen directly between you and the studio.
        </p>

        <ol class="intake-steps">
          <li class="is-active" data-step-pill="1">Scope</li>
          <li data-step-pill="2">Wallpaper</li>
          <li data-step-pill="3">Who</li>
          <li data-step-pill="4">Where</li>
          <li data-step-pill="5">Slot &amp; details</li>
        </ol>

        <!-- ===== Step 1 · Scope ===== -->
        <fieldset class="intake-step is-active" data-step="1">
          <h2>What's the scope?</h2>
          <p class="helper">Rough is fine — the studio confirms on the visit. This helps them arrive prepared (lift, paste table, crew size).</p>

          <!-- ===== Camera capture & measure (UX #6) ===== -->
          <div class="room-capture" data-room-capture>
            <div class="rc-intro">
              <strong>📷 Capture &amp; measure your rooms</strong>
              <p class="helper">Photograph each wall, drag the box to frame it, and confirm the size — your studio quotes from real footage and true dimensions instead of a guess. You can also drop in the wallpaper you're considering to see it on the wall at scale. Optional, but it gets you a faster, tighter quote.</p>
            </div>
            <div class="rc-rooms" data-rc-rooms></div>
            <button type="button" class="btn btn-ghost" data-rc-add>+ Add a room</button>
            <input type="hidden" name="room_captures" data-rc-field value="[]">
          </div>

          <template id="rc-room-tpl">
            <div class="rc-room">
              <div class="rc-room-head">
                <input type="text" class="rc-room-label" placeholder="Room name — e.g. Dining room">
                <button type="button" class="rc-remove" aria-label="Remove room">✕</button>
              </div>
              <label class="rc-file-btn">
                <span class="rc-photo-cta">📷 Take / choose wall photo</span>
                <input type="file" accept="image/*" capture="environment" class="rc-photo-input" hidden>
              </label>
              <div class="rc-uploading" hidden>Uploading…</div>
              <div class="rc-stage" hidden>
                <div class="rc-photo-wrap">
                  <img class="rc-photo" alt="Wall photo">
                  <div class="rc-box">
                    <div class="rc-wallpaper-layer"></div>
                    <span class="rc-handle" data-corner="tl"></span>
                    <span class="rc-handle" data-corner="tr"></span>
                    <span class="rc-handle" data-corner="bl"></span>
                    <span class="rc-handle" data-corner="br"></span>
                  </div>
                </div>
                <p class="rc-hint">Drag the yellow corners so the box frames the wall edge-to-edge, then confirm the size below.</p>
                <div class="rc-dims">
                  <label>Ceiling height (ft)<input type="number" class="rc-height" min="6" max="40" step="0.5" value="9"></label>
                  <label>Wall width (ft)<input type="number" class="rc-width" min="1" max="120" step="0.5"></label>
                </div>
                <button type="button" class="btn btn-ghost rc-measure-btn" hidden></button>
                <p class="rc-measure-note"></p>
                <p class="rc-estimate"></p>
                <label class="rc-file-btn rc-secondary">
                  <span class="rc-video-cta">🎬 Add a room walkthrough video (optional)</span>
                  <input type="file" accept="video/*" capture="environment" class="rc-video-input" hidden>
                </label>
                <details class="rc-wallpaper">
                  <summary>🪞 Preview your own wallpaper on this wall</summary>
                  <p class="helper">Upload the wallpaper image and give us its exact specs — we tile it on the wall at true scale.</p>
                  <label class="rc-file-btn rc-secondary">
                    <span class="rc-wp-cta">Upload wallpaper image</span>
                    <input type="file" accept="image/*" class="rc-wp-input" hidden>
                  </label>
                  <div class="rc-wp-specs">
                    <label>Panel width (in)<input type="number" class="rc-wp-width" min="1" max="120" step="0.25" placeholder="27"></label>
                    <label>Vertical repeat (in)<input type="number" class="rc-wp-repeat" min="0" max="240" step="0.25" placeholder="18"></label>
                    <label>Match<select class="rc-wp-match"><option value="straight">Straight match</option><option value="half_drop">Half-drop</option></select></label>
                  </div>
                  <p class="rc-wp-note">Exact panel width and vertical repeat are required — without them we can't show the paper at true scale.</p>
                </details>
              </div>
            </div>
          </template>

          <label>Rooms / walls being papered <input type="text" name="surfaces" placeholder="e.g. Dining room walls, primary suite, powder room"></label>
          <div class="row3">
            <label>Approx. square feet <input type="number" name="square_feet" min="0" step="10" placeholder="e.g. 320"></label>
            <label>Ceiling height (ft) <input type="number" name="ceiling_height_ft" min="6" max="40" step="0.5" placeholder="9, 10, 12"></label>
            <label>Estimated rolls <input type="number" name="roll_count_estimate" min="0" step="1" placeholder="optional"></label>
          </div>
          <label>Surface state
            <select name="surface_state">
              <option value="">Choose…</option>
              <option value="new_plaster">New plaster (needs lining)</option>
              <option value="painted_drywall">Painted drywall</option>
              <option value="wallpaper_to_remove">Existing wallpaper to remove</option>
              <option value="brick">Brick / masonry</option>
              <option value="wood_panel">Wood panel</option>
              <option value="other">Other</option>
            </select>
          </label>
          <label>Access
            <select name="access_constraints">
              <option value="">Choose…</option>
              <option value="ground_floor">Ground floor / accessible</option>
              <option value="second_floor">Second floor (no lift)</option>
              <option value="atrium_double_height">Atrium / double-height (lift required)</option>
              <option value="high_rise">High-rise (freight elevator + COI)</option>
              <option value="restricted_hours">Restricted hours / occupied space</option>
            </select>
          </label>
          <div class="nav"><span></span><button type="button" class="btn btn-primary" data-step-next="2">Next →</button></div>
        </fieldset>

        <!-- ===== Step 2 · Has wallpaper been selected? ===== -->
        <fieldset class="intake-step" data-step="2">
          <h2>Has the wallpaper been selected?</h2>
          <p class="helper">If yes, the studio can confirm they install that brand and that it's in stock; if no, they'll bring samples and recommendations.</p>
          <div class="radio-cards">
            <label><input type="radio" name="product_sourced" value="true"><span><strong>Yes — already chosen</strong><small>Brand and pattern are picked. May or may not be ordered yet.</small></span></label>
            <label><input type="radio" name="product_sourced" value="false" checked><span><strong>Not yet</strong><small>Looking for guidance on brand and pattern.</small></span></label>
          </div>
          <div data-show-if='[name="product_sourced"]:checked[value="true"]' style="margin-top:8px">
            <label>Material
              <select name="material">
                <option value="">Choose…</option>
                <option value="hand_painted">Hand-painted</option>
                <option value="silk">Silk</option>
                <option value="grasscloth">Grasscloth</option>
                <option value="vinyl">Vinyl</option>
                <option value="mural">Mural</option>
                <option value="metallic_leaf">Metallic leaf</option>
                <option value="other">Other</option>
              </select>
            </label>
            <label>Brand
              <input type="text" name="brand" list="brand-suggestions" placeholder="e.g. Fromental, de Gournay, Phillip Jeffries">
              <datalist id="brand-suggestions">
                <option value="de Gournay"></option><option value="Fromental"></option>
                <option value="Phillip Jeffries"></option><option value="Maya Romanoff"></option>
                <option value="Schumacher"></option><option value="Cole &amp; Son"></option>
                <option value="Pierre Frey"></option><option value="Calico Wallpaper"></option>
                <option value="Élitis"></option><option value="Holland &amp; Sherry"></option>
                <option value="Porter Teleo"></option><option value="Adelphi Paper Hangings"></option>
                <option value="Mind the Gap"></option><option value="Designer Wallcoverings"></option>
                <option value="Wallquest"></option><option value="York Wallcoverings"></option>
              </datalist>
            </label>
            <label>Pattern / SKU (if known) <input type="text" name="brand_sku" placeholder="e.g. Earlham 7821 / Bois 113"></label>
          </div>
          <div class="nav"><button type="button" class="btn btn-ghost" data-step-prev="1">← Back</button><button type="button" class="btn btn-primary" data-step-next="3">Next →</button></div>
        </fieldset>

        <!-- ===== Step 3 · Who is ordering? ===== -->
        <fieldset class="intake-step" data-step="3">
          <h2>Who's commissioning the work?</h2>
          <p class="helper">Studios price and brief differently for trade clients vs. homeowners — this is just so they can prep the right way.</p>
          <div class="radio-cards">
            <label><input type="radio" name="customer_role" value="homeowner" checked><span><strong>Homeowner</strong><small>I live in the home or own it.</small></span></label>
            <label><input type="radio" name="customer_role" value="designer"><span><strong>Interior designer</strong><small>Designing on behalf of a client.</small></span></label>
            <label><input type="radio" name="customer_role" value="architect"><span><strong>Architect</strong><small>Specifying for a project.</small></span></label>
            <label><input type="radio" name="customer_role" value="contractor"><span><strong>General contractor</strong><small>Coordinating trades on a build.</small></span></label>
            <label><input type="radio" name="customer_role" value="property_mgr"><span><strong>Property manager</strong><small>Hospitality, commercial, or multi-unit.</small></span></label>
            <label><input type="radio" name="customer_role" value="other"><span><strong>Other</strong><small>Tell us in the notes.</small></span></label>
          </div>
          <div class="nav"><button type="button" class="btn btn-ghost" data-step-prev="2">← Back</button><button type="button" class="btn btn-primary" data-step-next="4">Next →</button></div>
        </fieldset>

        <!-- ===== Step 4 · Where is the project? ===== -->
        <fieldset class="intake-step" data-step="4">
          <h2>Where's the project?</h2>
          <p class="helper">The address stays private — only <%= installer.business_name %> sees it after they accept. ZIP is enough to start; full address comes before the visit.</p>
          <label>Street <input type="text" name="address_line1" autocomplete="address-line1"></label>
          <label>Apt / Suite <input type="text" name="address_line2" autocomplete="address-line2"></label>
          <div class="row3">
            <label>City <input type="text" name="city" autocomplete="address-level2"></label>
            <label>State <input type="text" name="state" maxlength="2" autocomplete="address-level1"></label>
            <label>ZIP <input type="text" name="zip" maxlength="10" autocomplete="postal-code" required></label>
          </div>
          <div class="nav"><button type="button" class="btn btn-ghost" data-step-prev="3">← Back</button><button type="button" class="btn btn-primary" data-step-next="5">Next →</button></div>
        </fieldset>

        <!-- ===== Step 5 · Slot + contact details ===== -->
        <fieldset class="intake-step" data-step="5">
          <h2>Pick a time and confirm</h2>
          <p class="helper">All available slots are pulled from <%= installer.business_name %>'s live calendar — you're not waiting for a callback.</p>

          <div class="selected-slot" data-selected-slot><span>Pick a slot from the calendar at left to continue</span></div>

          <% if (consumer) { %>
            <p class="helper" style="background:#dcfce7;color:#166534;padding:8px 12px;border-radius:6px">Signed in as <%= consumer.email %> — name and email are pre-filled.</p>
          <% } %>

          <label>Name<input type="text" name="customer_name" required value="<%= consumer ? (consumer.name || '') : '' %>"></label>
          <label>Email<input type="email" name="customer_email" required value="<%= consumer ? consumer.email : '' %>"></label>
          <label>Phone<input type="tel" name="customer_phone" autocomplete="tel"></label>

          <!-- project_type is set at the top by the visit-vs-install picker;
               the select that lived here would override it on submit. -->
          <label>Budget band
            <select name="budget_band">
              <option value="">Prefer not to say</option>
              <option value="under_5k">Under $5k</option>
              <option value="5k_15k">$5k – $15k</option>
              <option value="15k_50k">$15k – $50k</option>
              <option value="50k_plus">$50k +</option>
            </select>
          </label>
          <label>Anything else? <textarea name="customer_notes" rows="3" placeholder="Color preferences, deadlines, samples already in hand…"></textarea></label>

          <div class="nav" style="margin-top:8px"><button type="button" class="btn btn-ghost" data-step-prev="4">← Back</button><span></span></div>
        </fieldset>

        <%
          // Booking deposit pricing — env-tuneable. Defaults match lib/stripe.js.
          var _depositCents = parseInt(process.env.NPH_DEFAULT_DEPOSIT_CENTS || '9900', 10);
          var _depositLabel = '$' + (_depositCents / 100).toFixed(_depositCents % 100 ? 2 : 0);
          var _hasPubKey = !!stripePublishableKey;
        %>

        <fieldset class="deposit-block">
          <legend>Reserve your slot · <%= _depositLabel %> deposit</legend>
          <p class="muted" style="margin:0 0 12px">
            A <strong><%= _depositLabel %></strong> deposit secures the appointment and is fully credited toward your project.
            Cancel up to 48 hours before the visit for a full refund.
            <span class="muted-2">NPH operates as a referral and lead-coordination service — your installation contract is between you and <%= installer.business_name %> directly. NPH forwards the balance of the deposit to the studio after the visit.</span>
          </p>
          <p class="muted" style="margin:0 0 12px;font-size:12px;line-height:1.55">
            <strong>Before signing any installation contract:</strong> verify CA contractor licenses at <a href="https://search.cslb.ca.gov/" target="_blank" rel="noopener">search.cslb.ca.gov</a>. NPH does not warrant or supervise installation work performed by independent studios in this directory.
          </p>
          <% if (_hasPubKey) { %>
            <div id="deposit-card-element" data-stripe-card aria-label="Card details"></div>
            <div id="deposit-card-errors" role="alert" class="form-error"></div>
          <% } else { %>
            <p class="callout-soft">
              No card required at this stage. Submit your request and <strong><%= installer.business_name %></strong> will confirm the slot, then a secure payment link for the <%= _depositLabel %> deposit lands in your inbox.
            </p>
          <% } %>
        </fieldset>

        <button type="submit" class="btn btn-primary btn-lg" disabled data-submit>
          <% if (_hasPubKey) { %>Reserve · pay <%= _depositLabel %><% } else { %>Request this slot<% } %>
        </button>
        <p class="form-fineprint">By confirming, you agree to the studio's response and cancellation terms. Insurance and licensing are self-reported by installers; verify directly before any contract.</p>
      </form>
    </div>
  <% } %>
</section>

<%- include('../partials/footer') %>

<% if (stripePublishableKey) { %>
  <script src="https://js.stripe.com/v3/"></script>
  <script>window.NPH_STRIPE_PK = <%- JSON.stringify(stripePublishableKey) %>;</script>
<% } %>
<script src="/js/calendar-consumer.js" defer></script>
<script src="/js/room-measure.js" defer></script>
<script src="/js/room-capture.js" defer></script>
<script>
  // 5-step intake wizard navigation. The form itself still posts to the same
  // /api/installers/:slug/book endpoint that calendar-consumer.js wires up —
  // we're only managing which fieldset is visible.
  (function(){
    var form = document.querySelector('[data-intake-wizard]');
    if (!form) return;
    function go(n){
      form.querySelectorAll('.intake-step').forEach(function(s){ s.classList.toggle('is-active', s.getAttribute('data-step') === String(n)); });
      form.querySelectorAll('[data-step-pill]').forEach(function(p){
        var v = parseInt(p.getAttribute('data-step-pill'), 10);
        p.classList.toggle('is-active', v === n);
        p.classList.toggle('is-done', v < n);
      });
      // Smooth scroll the active step into view (helps on mobile).
      var active = form.querySelector('.intake-step.is-active');
      if (active && active.scrollIntoView) active.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
    form.addEventListener('click', function(e){
      var n = e.target.closest('[data-step-next]'); if (n) { e.preventDefault(); go(parseInt(n.getAttribute('data-step-next'), 10)); return; }
      var p = e.target.closest('[data-step-prev]'); if (p) { e.preventDefault(); go(parseInt(p.getAttribute('data-step-prev'), 10)); return; }
    });

    // Step 2 conditional reveal: brand fields only when product_sourced=true.
    function syncProductFields(){
      var on = form.querySelector('input[name="product_sourced"]:checked');
      var box = form.querySelector('[data-show-if]');
      if (box) box.style.display = (on && on.value === 'true') ? 'block' : 'none';
    }
    form.querySelectorAll('input[name="product_sourced"]').forEach(function(r){ r.addEventListener('change', syncProductFields); });
    syncProductFields();

    // Visit-kind picker removed 2026-05-06 — NPH only books the initial
    // consultation; install scheduling stays between consumer + installer.
    // The hidden project_type input is statically set to 'consultation'.
  })();

  gtag('event', 'booking_started', {
    installer_slug: '<%= installer.slug %>',
    installer_name: '<%= installer.business_name.replace(/'/g, "\\'") %>'
  });
</script>