Create Your Own Combo

Choose a Beanie

Beanie preview
Headband overlay

Choose a Headband

Combo Builder

<div class="tw-combo">
  <h2>Create Your Own Combo</h2>

  <div class="tw-grid">
    <div class="tw-col">
      <h3>Choose a Beanie</h3>
      <select id="twBeanieSelect">
        <option value="">Loading…</option>
      </select>
      <div class="tw-preview">
        <img id="twBeanieImg" alt="" />
        <div id="twBeanieMeta"></div>
      </div>
    </div>

    <div class="tw-col">
      <h3>Choose a Headband</h3>
      <select id="twHeadbandSelect">
        <option value="">Loading…</option>
      </select>
      <div class="tw-preview">
        <img id="twHeadbandImg" alt="" />
        <div id="twHeadbandMeta"></div>
      </div>
    </div>
  </div>

  <div class="tw-actions">
    <button id="twResetBtn" type="button">Reset</button>
    <button id="twAddBtn" type="button" disabled>Add Combo to Cart</button>
    <div id="twStatus" role="status" aria-live="polite"></div>
  </div>
</div>

<style>
  .tw-combo{max-width:1000px;margin:0 auto;padding:24px}
  .tw-grid{display:grid;grid-template-columns:1fr;gap:24px}
  @media(min-width:800px){.tw-grid{grid-template-columns:1fr 1fr}}
  .tw-col select{width:100%;padding:12px;border:1px solid #ddd;border-radius:10px}
  .tw-preview{margin-top:14px;border:1px solid #eee;border-radius:14px;padding:14px}
  .tw-preview img{width:100%;height:auto;display:none;border-radius:12px}
  .tw-actions{display:flex;gap:12px;align-items:center;margin-top:18px;flex-wrap:wrap}
  .tw-actions button{padding:12px 16px;border-radius:12px;border:1px solid #111;background:#111;color:#fff;cursor:pointer}
  .tw-actions button[disabled]{opacity:.5;cursor:not-allowed}
  #twResetBtn{background:#fff;color:#111}
  #twStatus{min-height:20px}
</style>

<script>
(async function () {
  const COLLECTION_BEANIES = 'beanies';
  const COLLECTION_HEADBANDS = 'headbands';

  const $ = (id) => document.getElementById(id);

  const beanieSelect = $('twBeanieSelect');
  const headbandSelect = $('twHeadbandSelect');
  const addBtn = $('twAddBtn');
  const resetBtn = $('twResetBtn');
  const statusEl = $('twStatus');

  const beanieImg = $('twBeanieImg');
  const headbandImg = $('twHeadbandImg');
  const beanieMeta = $('twBeanieMeta');
  const headbandMeta = $('twHeadbandMeta');

  let beanies = [];
  let headbands = [];

  function money(cents) {
    try { return (cents / 100).toLocaleString(undefined, { style: 'currency', currency: 'USD' }); }
    catch { return `$${(cents/100).toFixed(2)}`; }
  }

  function setStatus(msg, isError=false) {
    statusEl.textContent = msg || '';
    statusEl.style.color = isError ? 'crimson' : '';
  }

  function firstAvailableVariant(p) {
    // products.json gives variants[] with available + id
    return (p.variants || []).find(v => v.available) || null;
  }

  function buildOptions(selectEl, products) {
    selectEl.innerHTML = '<option value="">Select…</option>';
    products.forEach(p => {
      const v = firstAvailableVariant(p);
      const opt = document.createElement('option');
      opt.value = v ? String(v.id) : '';
      opt.dataset.handle = p.handle;
      opt.dataset.title = p.title;
      opt.dataset.image = (p.images && p.images[0]) ? p.images[0].src : '';
      opt.dataset.price = v ? String(v.price) : '';
      opt.disabled = !v;
      opt.textContent = v ? `${p.title} — ${money(v.price)}` : `${p.title} — Sold out`;
      selectEl.appendChild(opt);
    });
  }

  function renderPreview(selectEl, imgEl, metaEl) {
    const opt = selectEl.selectedOptions[0];
    if (!opt || !opt.value) {
      imgEl.style.display = 'none';
      imgEl.removeAttribute('src');
      metaEl.textContent = '';
      return;
    }
    const title = opt.dataset.title || '';
    const img = opt.dataset.image || '';
    const price = opt.dataset.price ? money(Number(opt.dataset.price)) : '';
    if (img) {
      imgEl.src = img;
      imgEl.style.display = 'block';
    } else {
      imgEl.style.display = 'none';
      imgEl.removeAttribute('src');
    }
    metaEl.textContent = price ? `${title} • ${price}` : title;
  }

  function updateAddState() {
    const ok = !!beanieSelect.value && !!headbandSelect.value;
    addBtn.disabled = !ok;
  }

  async function loadCollection(handle) {
    // Shopify collections endpoint that returns products + variants.
    const url = `/collections/${handle}/products.json?limit=250`;
    const res = await fetch(url, { credentials: 'same-origin' });
    if (!res.ok) throw new Error(`Failed to load /collections/${handle} (${res.status})`);
    const data = await res.json();
    return data.products || [];
  }

  async function addComboToCart(beanieVariantId, headbandVariantId) {
    const res = await fetch('/cart/add.js', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify({
        items: [
          { id: Number(beanieVariantId), quantity: 1 },
          { id: Number(headbandVariantId), quantity: 1 }
        ]
      })
    });
    if (!res.ok) {
      const text = await res.text().catch(()=>'');
      throw new Error(`Cart add failed (${res.status}). ${text}`);
    }
    return res.json();
  }

  // Init
  try {
    setStatus('Loading combos…');
    beanieSelect.innerHTML = '<option value="">Loading…</option>';
    headbandSelect.innerHTML = '<option value="">Loading…</option>';

    [beanies, headbands] = await Promise.all([
      loadCollection(COLLECTION_BEANIES),
      loadCollection(COLLECTION_HEADBANDS)
    ]);

    buildOptions(beanieSelect, beanies);
    buildOptions(headbandSelect, headbands);
    setStatus('');
  } catch (e) {
    console.error(e);
    setStatus(`Combo Viewer error: ${e.message}`, true);
  }

  beanieSelect.addEventListener('change', () => {
    renderPreview(beanieSelect, beanieImg, beanieMeta);
    updateAddState();
  });

  headbandSelect.addEventListener('change', () => {
    renderPreview(headbandSelect, headbandImg, headbandMeta);
    updateAddState();
  });

  resetBtn.addEventListener('click', () => {
    beanieSelect.value = '';
    headbandSelect.value = '';
    renderPreview(beanieSelect, beanieImg, beanieMeta);
    renderPreview(headbandSelect, headbandImg, headbandMeta);
    updateAddState();
    setStatus('');
  });

  addBtn.addEventListener('click', async () => {
    try {
      addBtn.disabled = true;
      setStatus('Adding combo to cart…');
      await addComboToCart(beanieSelect.value, headbandSelect.value);
      setStatus('Added. Redirecting to cart…');
      window.location.href="/fr/cart";
    } catch (e) {
      console.error(e);
      setStatus(e.message || 'Failed to add combo.', true);
      updateAddState();
    }
  });

  // initial previews
  renderPreview(beanieSelect, beanieImg, beanieMeta);
  renderPreview(headbandSelect, headbandImg, headbandMeta);
  updateAddState();
})();
</script>