# Service picker (`service-picker`)

Fresha-style menu rows with duration, price and a (+). A running "Your visit" total, and on phones a sticky From $X · Book bar that becomes "2 services · 1 h 45 min · $110".

| | |
|---|---|
| Tag | Showcase |
| Size | 2.9 KB gzipped own code (JS 1.5 KB + CSS 1.4 KB; 7.5 KB raw) |
| Dependency | none (vanilla + native APIs) |
| Best for | Salons, barbers, nails, med spa, massage, detailers, groomers, any BOOK business with a real price list |
| Demo | `index.html` in this folder (live: https://wbl-code-lib.pages.dev/modules/service-picker/) |

## What it does
Turns a plain price list (works without JS) into a picker: each row toggles with a 44 px (+) that becomes a check, a summary card on wide screens lists the picks with a total and time, and a bar sticks to the bottom of the picker on phones (inside the section only, so it never fights the page's own sticky bar). The Book link carries ?services=id1,id2, and a wbl:services event fires for a booking widget to pick up.

## When to use
- Top Mobbin upgrade #2: replaces the static price list in a BOOK preview.
- "from" prices (consult-based colour) via data-from: the total says "from $285".
- Hook into the builder's booking_widget by listening for wbl:services.

## Don't
- Invent prices: sample prices on a preview must be labelled as sample.
- More than ~15 rows without categories; use Menu tabs above it for long menus.
- Stack it with another sticky bottom bar on the same screen: hide the page's Book bar while the picker is in view (data-hide on Sticky book bar).

## Reduced motion, no JS, accessibility
- Every toggle is a real &lt;button aria-pressed&gt; with a visually hidden name and price; the total is aria-live.
- Reduced motion: no bump on the total, no icon morph.
- No JS: a clean price list (no + buttons, no bar).

## Notes
- Prices are typed as data-price numbers; display text is yours (so "$65+" or "from" stays as written).

## Relation to the effects libraries
New. Batch 3's fly-to-cart is the GSAP add-on flourish; this is the full picker without a library.

## Use it
Copy this folder to the site as `/wbl/service-picker/` (keep `vendor/` if there is one), then:

```html
<!-- Service picker (WBL Code Library) -->
<!-- in <head>: -->
<script>document.documentElement.classList.add('wbl-js')</script>
<link rel="stylesheet" href="/wbl/service-picker/module.css">
<script src="/wbl/service-picker/module.js" defer></script>

<!-- markup: -->
<div class="wbl-sp" data-wbl="service-picker" data-book="https://example.com/book" data-currency="$">
  <div class="wbl-sp-list">
    <h3 class="wbl-sp-cat">Cuts</h3>
    <ul>
      <li class="wbl-sp-item" data-id="cut-w" data-price="65" data-min="60">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Women's cut + blow-dry</span><span class="wbl-sp-meta">1 h · with Maria or Dee</span></div>
        <span class="wbl-sp-pr">$65</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Women's cut + blow-dry, $65</span></button>
      </li>
      <li class="wbl-sp-item" data-id="cut-m" data-price="40" data-min="30">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Men's cut</span><span class="wbl-sp-meta">30 min · scissor or clipper</span></div>
        <span class="wbl-sp-pr">$40</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Men's cut, $40</span></button>
      </li>
      <li class="wbl-sp-item" data-id="cut-k" data-price="30" data-min="30">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Kids cut (under 12)</span><span class="wbl-sp-meta">30 min</span></div>
        <span class="wbl-sp-pr">$30</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Kids cut (under 12), $30</span></button>
      </li>
      <li class="wbl-sp-item" data-id="fringe" data-price="15" data-min="15">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Fringe trim</span><span class="wbl-sp-meta">15 min · between cuts</span></div>
        <span class="wbl-sp-pr">$15</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Fringe trim, $15</span></button>
      </li>
    </ul>
    <h3 class="wbl-sp-cat">Colour</h3>
    <ul>
      <li class="wbl-sp-item" data-id="gloss" data-price="55" data-min="45">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Gloss / toner</span><span class="wbl-sp-meta">45 min · shine and tone refresh</span></div>
        <span class="wbl-sp-pr">$55</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Gloss / toner, $55</span></button>
      </li>
      <li class="wbl-sp-item" data-id="roots" data-price="85" data-min="75">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Root touch-up</span><span class="wbl-sp-meta">1 h 15 min · single process</span></div>
        <span class="wbl-sp-pr">$85</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Root touch-up, $85</span></button>
      </li>
      <li class="wbl-sp-item" data-id="balayage" data-price="220" data-min="180" data-from>
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Balayage</span><span class="wbl-sp-meta">3 h · price after consult</span></div>
        <span class="wbl-sp-pr">from $220</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Balayage, $220</span></button>
      </li>
      <li class="wbl-sp-item" data-id="vivid" data-price="180" data-min="180" data-from>
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Vivid colour</span><span class="wbl-sp-meta">3 h · reds, pinks, coppers</span></div>
        <span class="wbl-sp-pr">from $180</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Vivid colour, $180</span></button>
      </li>
    </ul>
    <h3 class="wbl-sp-cat">Finish</h3>
    <ul>
      <li class="wbl-sp-item" data-id="blow" data-price="45" data-min="45">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Blow-dry</span><span class="wbl-sp-meta">45 min</span></div>
        <span class="wbl-sp-pr">$45</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Blow-dry, $45</span></button>
      </li>
      <li class="wbl-sp-item" data-id="treat" data-price="35" data-min="20">
        <div class="wbl-sp-txt"><span class="wbl-sp-name">Bond repair treatment</span><span class="wbl-sp-meta">20 min · add to any colour</span></div>
        <span class="wbl-sp-pr">$35</span>
        <button class="wbl-sp-add" type="button" aria-pressed="false"><span class="wbl-sp-vh">Add Bond repair treatment, $35</span></button>
      </li>
    </ul>
  </div>
  <aside class="wbl-sp-sum" aria-label="Your visit">
    <h3>Your visit</h3>
    <ol data-sp-lines></ol>
    <p data-sp-total aria-live="polite">Nothing picked yet</p>
    <a class="wbl-sp-book" data-sp-book href="https://example.com/book"><span data-sp-book-label>Book</span></a>
  </aside>
  <div class="wbl-sp-bar">
    <div class="wbl-sp-bar-t"><b data-sp-bar-txt aria-live="polite">From $15</b><span data-sp-bar-sub>10 services</span></div>
    <a class="wbl-sp-book" data-sp-book href="https://example.com/book"><span data-sp-book-label>Book</span></a>
  </div>
</div>
```

Colours come from CSS variables with safe defaults: `--wbl-accent`, `--wbl-on-accent`, `--wbl-ink`, `--wbl-paper`,
`--wbl-card`, `--wbl-line`, `--wbl-muted`, `--wbl-band`, `--wbl-radius`, `--wbl-head` (see the library README for the
one-line bridge to the preview builder's `--a / --ink / --bg …`).

## License
Our code: WBL-owned, original. No third-party code.
