{
  "version": 3,
  "sources": ["/cdn/shop/t/51/assets/ai3d-creator-page.js?aicp=variant-cart-r1-20260923"],
  "sourcesContent": ["/* ============================================================\n   AI3D creator page JS.\n\n   UI is new, but the request layer is a port of the realizeit-creator\n   extension contract (see docs/realizeit-api-notes.md):\n\n   - POST {proxy}/upload   FormData { image, rightsConfirmed, visitorToken? } -> { file, screeningId }\n   - POST {proxy}/create   JSON { file, screeningId, style, prompt } -> 202 { jobId, status, quota }\n     \"prompt\" is the shopper's special-instructions text \u2014 server-supported,\n     truncated to 300 chars server-side (textarea maxlength matches). The UI\n     submits exactly one selected pose or style per request. A directive card\n     (People Mini Pop) sends its look ahead of the note, and the note's\n     limit shrinks to fit.\n   - GET  {proxy}/styles                                    -> { styles: [{name,label,...}] }\n   - GET  {proxy}/status                                    -> { signedIn, quota: { remaining } }\n   - GET  {proxy}/jobs/:id (every 2.5 s, 40 min cap)        -> { progress, phase|status,\n        cleanUrl, cleanFile, modelUrl, provider, status }\n   - PUT  {proxy}/jobs/:id/client-metrics (once, at the end) -> client timings only\n   - Backend handoff 2026-10-06: errors are mapped to a popup (showApiError),\n     never shown raw; a product's custom.creator_style locks the style.\n   - GET  {proxy}/history -> { history: [{ id, status, thumbnailUrl, modelUrl,\n        style, pose, purchased, ... }], signedIn, identityKind }             (\u00A73.8)\n   - DELETE {proxy}/history/{id}                            -> { ok, remaining }   (\u00A73.9)\n   - localStorage \"realizeit:job\" keeps { jobId, style, styleLabel, uploaded, at }\n     for 45 minutes so a refresh resumes the poll (single-style runs only).\n   - Cart properties per result: Style, _clean_file, _style, Instructions,\n     _source_file, _preview_job, Base, Engraving. Do NOT rename these.\n   - Gallery view reuses the theme cart: POST /cart/add.js then dispatches\n     the theme's \"shopify:cart:lines-update\" event so the cart drawer\n     auto-opens and the cart bubble refreshes.\n   ============================================================ */\n\nconst MAX_UPLOAD_BYTES = 20 * 1024 * 1024;\n// Backend handoff 2026-10-06: poll /jobs/:id every 2\u20133 s.\nconst POLL_INTERVAL_MS = 2500;\nconst POLL_DEADLINE_MS = 40 * 60 * 1000;\nconst POLL_REQUEST_TIMEOUT_MS = 20000; // single /jobs/:id fetch \u2014 avoid a hung request stalling the round\nconst JOB_KEY = 'realizeit:job';\nconst VISITOR_TOKEN_KEY = 'realizeit:visitor-token';\n// Save your design (2026-10-27): when the card was last sent, and a send that went on to Shopify's\n// hCaptcha page (sessionStorage, read back on the page Shopify returns the shopper to). The check\n// step can include hCaptcha's own puzzle, so it gets a minute; Shopify's answer to the post, 30 s.\nconst SAVE_SENT_KEY = 'mythumi:design-saved';\nconst SAVE_SENT_MS = 30 * 24 * 60 * 60 * 1000;\nconst SAVE_PENDING_KEY = 'mythumi:design-save-pending';\nconst SAVE_PENDING_MS = 30 * 60 * 1000;\nconst SAVE_CHECK_MS = 60 * 1000;\nconst SAVE_RESPONSE_MS = 30 * 1000;\n// Upload timeouts (2026-10-27): only silence fails an upload. While the photo is being sent, no\n// progress for this long; once it is sent, no answer for this long. The server's photo check runs\n// in that second wait and can take 20-35 s (one vision model, then the fallback).\nconst UPLOAD_STALL_MS = 20 * 1000;\nconst UPLOAD_CHECK_MS = 60 * 1000;\nconst JOB_MEMORY_MS = 45 * 60 * 1000;\nconst VIEWER_TIMEOUT_MS = 20000;\nconst CART_LINES_UPDATE = 'shopify:cart:lines-update';\nconst MAX_ENGRAVING_CHARS = 15;\n// The server keeps the first 300 characters of a /create prompt.\nconst MAX_PROMPT_CHARS = 300;\n// Mini Pop runs the backend's square-head style people-block-head (in /styles\n// since 2026-10-06). Only if that style is missing does the card fall back to\n// Free Style with this look written ahead of the shopper's note; designs made\n// that way before 2026-10-08 are still recognised in the gallery by it.\nconst MINI_POP = {\n  label: 'Mini Pop',\n  directive: 'Restyle as a square-headed vinyl collectible figurine: oversized boxy head, flat solid-black round eyes, no mouth, tiny body, short limbs, matte vinyl.',\n};\n\n// PreviewStart counts an accepted new generation, never a click or history view.\n// Keep job IDs internal and publish only this allowlist to Shopify's pixel bridge.\nfunction createPreviewStartPublisher() {\n  const jobs = new Map();\n  const maxJobs = 256;\n  const maxAttempts = 20;\n  const retryMs = 500;\n\n  function eventId() {\n    if (window.crypto?.randomUUID) return window.crypto.randomUUID();\n    const bytes = new Uint8Array(16);\n    if (window.crypto?.getRandomValues) window.crypto.getRandomValues(bytes);\n    else bytes.forEach((_, index) => { bytes[index] = Math.floor(Math.random() * 256); });\n    bytes[6] = (bytes[6] & 15) | 64;\n    bytes[8] = (bytes[8] & 63) | 128;\n    const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n    return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n  }\n\n  function attempt(entry) {\n    if (entry.state !== 'pending') return;\n    entry.attempts += 1;\n    const retry = () => {\n      if (entry.attempts >= maxAttempts) entry.state = 'expired';\n      else setTimeout(() => attempt(entry), retryMs);\n    };\n    try {\n      const bridge = window.Shopify?.analytics;\n      if (typeof bridge?.publish !== 'function') { retry(); return; }\n      // Do not await analytics in the generation flow. A late bridge or a\n      // rejected publish gets bounded retries with the same random event ID.\n      Promise.resolve(bridge.publish('mythumi:preview_start', entry.payload)).then(\n        (accepted) => { if (accepted === false) retry(); else entry.state = 'published'; },\n        retry,\n      );\n    } catch (_) { retry(); }\n  }\n\n  return (jobId, context) => {\n    try {\n      if (jobId == null || !String(jobId) || !context || !['pet', 'people', 'trpg'].includes(context.creator_category)) return;\n      const key = String(jobId);\n      if (jobs.has(key)) return;\n      if (jobs.size >= maxJobs) {\n        const oldest = Array.from(jobs).find(([, entry]) => entry.state !== 'pending');\n        if (!oldest) return;\n        jobs.delete(oldest[0]);\n      }\n      const variantId = context.variant_id == null ? '' : String(context.variant_id);\n      const payload = {\n        schema_version: 1,\n        event_id: eventId(),\n        content_type: 'product',\n        content_ids: /^[1-9][0-9]{0,24}$/.test(variantId) ? [variantId] : [],\n        creator_category: context.creator_category,\n        preview_type: 'ai_2d_3d',\n      };\n      const entry = { payload, attempts: 0, state: 'pending' };\n      jobs.set(key, entry);\n      attempt(entry);\n    } catch (_) { /* A pixel failure must never interrupt generation. */ }\n  };\n}\nconst publishPreviewStart = createPreviewStartPublisher();\n\n/* Microsoft Clarity funnel events for Smart events / Funnels (2026-10-21). The\n   Clarity app embed defines window.clarity in <head>; without it these do\n   nothing. onceKey (a job id) keeps an event to one per design in this tab, so\n   a reload that resumes the same job does not count it again. */\nconst claritySent = new Set();\n// Jobs this page created or followed (resumed, recovered): their 3D counts as\n// \"made in this visit\" whichever card or object shows it.\nconst clarityJobs = new Set();\n/* Every funnel event also goes to the backend (POST {proxy}/events), which\n   logs it beside the server's own upload/create/job lines so the two can be\n   queried together and split by device and in-app browser (the backend repo's\n   docs/analytics-events.md). Clarity keeps the same names for session replay.\n   Fire-and-forget: never awaited, never retried, and it cannot throw. */\nlet analyticsProxy = null;\nlet analyticsVisitorToken = () => '';\nfunction analyticsSession() {\n  try {\n    let id = sessionStorage.getItem('mythumi:analytics-session');\n    if (!id) {\n      id = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n      sessionStorage.setItem('mythumi:analytics-session', id);\n    }\n    return id;\n  } catch (_) {\n    return null;\n  }\n}\nfunction serverEvent(name, jobId, props) {\n  try {\n    if (!analyticsProxy || typeof fetch !== 'function') return;\n    const body = JSON.stringify({\n      name,\n      jobId: jobId == null ? null : String(jobId),\n      session: analyticsSession(),\n      page: location.pathname,\n      visitorToken: analyticsVisitorToken() || undefined,\n      props: props || {},\n    });\n    fetch(`${analyticsProxy}/events`, {\n      method: 'POST',\n      keepalive: true,\n      headers: { 'Content-Type': 'application/json' },\n      body,\n    }).catch(() => {});\n  } catch (_) { /* Analytics must never interrupt the creator. */ }\n}\nfunction sizeBucket(bytes) {\n  const mb = Number(bytes) / (1024 * 1024);\n  if (!Number.isFinite(mb)) return '';\n  return mb < 2 ? '<2MB' : mb < 5 ? '2-5MB' : mb < 10 ? '5-10MB' : '>=10MB';\n}\nfunction clarityEvent(name, onceKey, props) {\n  try {\n    if (onceKey != null) {\n      const key = `mythumi:clarity:${name}:${onceKey}`;\n      if (claritySent.has(key)) return;\n      claritySent.add(key);\n      try {\n        if (sessionStorage.getItem(key)) return;\n        sessionStorage.setItem(key, '1');\n      } catch (_) { /* Storage blocked: the in-page set still dedupes. */ }\n    }\n    if (typeof window.clarity === 'function') window.clarity('event', name);\n    serverEvent(name, onceKey, props);\n  } catch (_) { /* Analytics must never interrupt the creator. */ }\n}\nfunction clarityTag(key, value) {\n  try {\n    if (value != null && value !== '' && typeof window.clarity === 'function') window.clarity('set', key, String(value));\n  } catch (_) { /* Analytics must never interrupt the creator. */ }\n}\n\nfunction escapeHtml(value) {\n  return String(value ?? '').replace(/[&<>\"']/g, (ch) => (\n    { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[ch]\n  ));\n}\n\nfunction normalizeShopifyOptionName(value) {\n  return String(value || '').trim().toLowerCase();\n}\n\nfunction normalizeShopifyOptionValue(value) {\n  return String(value || '').trim().toLowerCase();\n}\n\nfunction readShopifyProductCatalogue(root) {\n  const node = root.querySelector('[data-aicp-product-catalogue]');\n  const empty = { productId: null, selectedVariantId: null, options: [], variants: [] };\n  if (!node) return empty;\n\n  try {\n    const parsed = JSON.parse(node.textContent || '{}');\n    const options = Array.isArray(parsed.options)\n      ? parsed.options.map((option, index) => ({\n        name: String(option?.name || ''),\n        key: normalizeShopifyOptionName(option?.name),\n        position: Number(option?.position) || index + 1,\n        values: Array.isArray(option?.values) ? option.values.map((value) => String(value)) : [],\n      }))\n      : [];\n    const variants = Array.isArray(parsed.variants)\n      ? parsed.variants\n        .map((variant) => ({\n          id: Number(variant?.id) || null,\n          available: Boolean(variant?.available),\n          price: Number(variant?.price) || 0,\n          priceText: String(variant?.priceText || ''),\n          compareAtPrice: Number(variant?.compareAtPrice) || 0,\n          compareAtPriceText: String(variant?.compareAtPriceText || ''),\n          options: Array.isArray(variant?.options) ? variant.options.map((value) => String(value)) : [],\n        }))\n        .filter((variant) => variant.id)\n      : [];\n    return {\n      productId: Number(parsed.productId) || null,\n      selectedVariantId: Number(parsed.selectedVariantId) || null,\n      options,\n      variants,\n    };\n  } catch (error) {\n    console.error('[ai3d] Shopify product variant catalogue is invalid.', error);\n    return empty;\n  }\n}\n\nfunction readGiftBoxProduct(root) {\n  const node = root.querySelector('[data-aicp-gift-box-product]');\n  const empty = {\n    configured: false,\n    productId: null,\n    title: '',\n    variantId: null,\n    available: false,\n    price: 0,\n    priceText: '',\n  };\n  if (!node) return empty;\n\n  try {\n    const parsed = JSON.parse(node.textContent || '{}');\n    return {\n      configured: Boolean(parsed.configured),\n      productId: Number(parsed.productId) || null,\n      title: String(parsed.title || ''),\n      variantId: Number(parsed.variantId) || null,\n      available: Boolean(parsed.available),\n      price: Number(parsed.price) || 0,\n      priceText: String(parsed.priceText || ''),\n    };\n  } catch (error) {\n    console.error('[ai3d] Gift box product configuration is invalid.', error);\n    return empty;\n  }\n}\n\nfunction readShopifyBaseCatalogue(root) {\n  const fallbackSquareUrl = (root.dataset.squareBaseUrl || '').trim() || null;\n  const fallback = {\n    version: 1,\n    default: '',\n    bases: [\n      { id: 'round', label: 'Round display base', variantValue: 'Round display base', previewImageUrl: null, modelUrl: null, modelVersion: 'v1', viewerType: 'round' },\n      { id: 'square', label: 'Square display base', variantValue: 'Square display base', previewImageUrl: null, modelUrl: fallbackSquareUrl, modelVersion: 'v1', viewerType: 'square' },\n    ],\n  };\n  const node = root.querySelector('[data-aicp-base-catalogue]');\n  if (!node) return fallback;\n\n  try {\n    const parsed = JSON.parse(node.textContent || '{}');\n    const bases = Array.isArray(parsed.bases)\n      ? parsed.bases\n        .filter((base) => base && typeof base.id === 'string' && base.id.trim())\n        .map((base) => ({\n          id: base.id.trim(),\n          label: String(base.label || base.id),\n          variantValue: String(base.variantValue || base.label || base.id),\n          previewImageUrl: typeof base.previewImageUrl === 'string' ? base.previewImageUrl : null,\n          modelUrl: typeof base.modelUrl === 'string' && base.modelUrl.trim() ? base.modelUrl.trim() : null,\n          modelVersion: String(base.modelVersion || 'v1'),\n          viewerType: base.viewerType === 'round' ? 'round' : 'square',\n          engravable: base.engravable !== false,\n        }))\n      : [];\n    const byId = new Map(bases.map((base) => [base.id, base]));\n    if (!bases.some((base) => base.viewerType === 'square')) return fallback;\n    const defaultBase = typeof parsed.default === 'string' && byId.has(parsed.default)\n      ? parsed.default\n      : '';\n    return {\n      version: Number(parsed.version) || 1,\n      default: defaultBase,\n      bases,\n    };\n  } catch (error) {\n    console.warn('[ai3d] Shopify base catalogue is invalid; using the built-in fallback.', error);\n    return fallback;\n  }\n}\n\nfunction initCreatorPage(root) {\n  const mobileGalleryMedia = window.matchMedia('(max-width: 989px)');\n\n  const el = {\n    catTabs: [...root.querySelectorAll('.aicp-tabs__item')],\n    catSelects: [...root.querySelectorAll('[data-aicp-cat-select]')],\n    views: [...root.querySelectorAll('[data-aicp-view]')],\n    viewToggles: [...root.querySelectorAll('[data-aicp-view-toggle]')],\n    styles: root.querySelector('[data-aicp-styles]'),\n    stylesLabel: root.querySelector('[data-aicp-styles-label]'),\n    selectionStep: root.querySelector('[data-aicp-selection-step]'),\n    selectionOptions: root.querySelector('[data-aicp-selection-options]'),\n    drop: root.querySelector('[data-aicp-drop]'),\n    uploadPanel: root.querySelector('.aicp-creator-step--upload .aicp-panel__right'),\n    dropIcon: root.querySelector('[data-aicp-upload-icon]'),\n    dropTitle: root.querySelector('.aicp-upload__title'),\n    dropSub: root.querySelector('.aicp-upload__sub'),\n    dropFormats: root.querySelector('.aicp-upload__formats'),\n    file: root.querySelector('[data-aicp-file]'),\n    thumb: root.querySelector('[data-aicp-thumb]'),\n    filename: root.querySelector('[data-aicp-filename]'),\n    replacePhoto: root.querySelector('[data-aicp-upload-replace]'),\n    acc: root.querySelector('[data-aicp-acc]'),\n    notes: root.querySelector('[data-aicp-notes]'),\n    poses: root.querySelector('[data-aicp-poses]'),\n    posesRow: root.querySelector('[data-aicp-poses-row]'),\n    go: root.querySelector('[data-aicp-go]'),\n    quota: root.querySelector('[data-aicp-quota]'),\n    title: root.querySelector('[data-aicp-title]'),\n    stageMask: root.querySelector('[data-aicp-g-stage-mask]'),\n    stageMaskBar: root.querySelector('[data-aicp-g-stage-mask-bar]'),\n    stageMaskTrack: root.querySelector('[data-aicp-g-stage-mask-track]'),\n    stageMaskDesc: root.querySelector('[data-aicp-g-stage-mask-desc]'),\n    stageMaskCards: root.querySelector('[data-aicp-g-stage-mask-cards]'),\n    stageLoading: root.querySelector('[data-aicp-g-stage-loading]'),\n    stageLoadingBar: root.querySelector('[data-aicp-g-stage-loading-bar]'),\n    stageLoadingText: root.querySelector('[data-aicp-g-stage-loading-text]'),\n    stageLoadingValue: root.querySelector('[data-aicp-g-stage-loading-value]'),\n    backTop: root.querySelector('[data-aicp-back-top]'),\n  };\n\n  const proxy = (root.dataset.proxy || '/apps/realizeit').replace(/\\/$/, '');\n  // The upload tile's line from the section, restored after an upload (it shows progress meanwhile).\n  const DROP_SUB_TEXT = el.dropSub?.textContent || '';\n  // An in-memory copy as well: a browser that blocks site storage must still\n  // send the token /upload returned with the /create that follows.\n  let visitorTokenMemory = '';\n  const visitorToken = () => {\n    try {\n      return localStorage.getItem(VISITOR_TOKEN_KEY) || visitorTokenMemory;\n    } catch {\n      return visitorTokenMemory;\n    }\n  };\n  const rememberVisitor = (body) => {\n    if (!body?.visitorToken) return;\n    visitorTokenMemory = body.visitorToken;\n    try {\n      localStorage.setItem(VISITOR_TOKEN_KEY, body.visitorToken);\n    } catch {}\n  };\n  analyticsProxy = proxy;\n  analyticsVisitorToken = visitorToken;\n  const withVisitorToken = (path) => {\n    const token = visitorToken();\n    if (!token) return path;\n    const join = path.includes('?') ? '&' : '?';\n    return `${path}${join}visitorToken=${encodeURIComponent(token)}`;\n  };\n\n  /* ---------- same photo, another browser (2026-10-29) ----------\n     The backend (23ff057) answers an upload with photoMatch when another anonymous\n     visitor made designs from this photo in the last 2 hours: usually the same shopper,\n     who started in the Facebook or Instagram browser. The card under the upload tile\n     offers to bring them here. Restore asks POST /photo-restore, which re-checks the\n     stored upload itself and shares the matching designs with this visitor (the other\n     browser keeps them). The thumbnails are signed for this visitor for 15 minutes.\n     Since ae564ac (2026-11-03) a signed-in shopper gets matches too, and a restore puts\n     the designs in their account (no visitorToken in the reply), so the card asks that.\n     Declared up here: clearPhoto() hides the card and runs before the upload code. */\n  const photoMatch = {\n    card: root.querySelector('[data-aicp-photo-match]'),\n    title: root.querySelector('[data-aicp-photo-match-title]'),\n    sub: root.querySelector('[data-aicp-photo-match-sub]'),\n    thumbs: root.querySelector('[data-aicp-photo-match-thumbs]'),\n    restore: root.querySelector('[data-aicp-photo-match-restore]'),\n    dismiss: root.querySelector('[data-aicp-photo-match-dismiss]'),\n    status: root.querySelector('[data-aicp-photo-match-status]'),\n    file: null,            // the upload the card is about\n    pending: null,         // the upload a /photo-restore is out for\n    unsure: false,         // an earlier try got no answer: it may have shared them already\n    restoreLabel: 'Restore them',\n    signedIn: Boolean(root.querySelector('[data-aicp-photo-match][data-aicp-signed-in]')),\n  };\n\n  // The status line stays in the page and only its text changes (a live region shown and\n  // filled in one go may not be read out); empty, the CSS gives it no room.\n  function setPhotoMatchStatus(message) {\n    if (photoMatch.status) photoMatch.status.textContent = message || '';\n  }\n\n  function hidePhotoMatch() {\n    if (!photoMatch.card) return;\n    photoMatch.card.hidden = true;\n    photoMatch.file = null;\n    photoMatch.unsure = false;\n    if (photoMatch.thumbs) photoMatch.thumbs.textContent = '';\n    setPhotoMatchStatus('');\n  }\n\n  function showPhotoMatch(match, file) {\n    const designs = Number(match?.designs) || 0;\n    if (!photoMatch.card || !file || designs < 1) return;\n    photoMatch.file = file;\n    photoMatch.unsure = false;\n    const one = designs === 1;\n    if (photoMatch.title) photoMatch.title.textContent = one ? 'We found your design' : 'We found your designs';\n    // Shoppers in the Instagram or Facebook app rarely think of it as \"another browser\".\n    const ask = photoMatch.signedIn\n      ? (one ? 'Add it to your account?' : 'Add them to your account?')\n      : (one ? 'Bring it here?' : 'Bring them here?');\n    photoMatch.sub.textContent = one\n      ? `You already made a design with this photo, maybe in the Instagram or Facebook app. ${ask}`\n      : `You already made ${designs} designs with this photo, maybe in the Instagram or Facebook app. ${ask}`;\n    photoMatch.restoreLabel = one ? 'Restore it' : 'Restore them';\n    photoMatch.restore.textContent = photoMatch.restoreLabel;\n    photoMatch.restore.disabled = false;\n    photoMatch.thumbs.textContent = '';\n    // The backend's own links (same-origin proxy paths), up to four.\n    const urls = (Array.isArray(match.thumbnails) ? match.thumbnails : [])\n      .filter((url) => typeof url === 'string' && url.startsWith('/'))\n      .slice(0, 4);\n    urls.forEach((url) => {\n      const img = document.createElement('img');\n      img.className = 'aicp-photo-match__thumb';\n      img.alt = '';\n      img.width = 64;\n      img.height = 64;\n      img.decoding = 'async';\n      img.setAttribute('data-clarity-mask', 'True');\n      // A picture that will not load goes, and with the last one the row (and its \"+N\").\n      img.addEventListener('error', () => {\n        img.remove();\n        if (!photoMatch.thumbs.querySelector('img')) photoMatch.thumbs.hidden = true;\n      }, { once: true });\n      img.src = url;\n      photoMatch.thumbs.append(img);\n    });\n    if (urls.length && designs > urls.length) {\n      const more = document.createElement('span');\n      more.className = 'aicp-photo-match__more';\n      more.textContent = `+${designs - urls.length}`;\n      more.setAttribute('aria-hidden', 'true');\n      photoMatch.thumbs.append(more);\n    }\n    photoMatch.thumbs.hidden = !urls.length;\n    setPhotoMatchStatus('');\n    photoMatch.card.hidden = false;\n    clarityEvent('mythumi_photo_match_shown', null, { designs, signedIn: photoMatch.signedIn });\n  }\n\n  // How many designs /history now lists that this page's gallery shows and did not before\n  // (a design made on another product page stays on that page's gallery); -1 when unknown.\n  async function newPageDesigns(before) {\n    try {\n      const body = await call('/history');\n      const mine = await pageHistoryItems(body.history || body.items || []);\n      return mine.filter((item) => !before.has(String(item.id))).length;\n    } catch {\n      return -1;\n    }\n  }\n\n  async function restorePhotoMatch() {\n    const file = photoMatch.file;\n    if (!file || photoMatch.pending === file) return;\n    photoMatch.pending = file;\n    photoMatch.restore.disabled = true;\n    photoMatch.restore.textContent = 'Restoring\u2026';\n    setPhotoMatchStatus('');\n    const before = new Set(historyItems.map((item) => String(item.id)));\n    const body = { file };\n    if (visitorToken()) body.visitorToken = visitorToken();\n    let reply = null;\n    let failed = null;\n    try {\n      reply = await call('/photo-restore', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(body),\n      });\n    } catch (error) {\n      failed = error;\n    }\n    if (photoMatch.pending === file) photoMatch.pending = null;\n    // A newer photo replaced this one while the request was out: its card is the one shown now.\n    if (photoMatch.file !== file) return;\n    // Upload expired (404), the day's 20 used (429), or signed in on a backend before ae564ac\n    // (409): nothing to bring here, so the card goes and the shopper carries on making a design.\n    if (failed && [404, 409, 429].includes(failed.status)) {\n      hidePhotoMatch();\n      return;\n    }\n    if (failed) {\n      // No answer, or a gateway page instead of one: the share may have happened anyway.\n      if (!failed.status || failed.nonJson) photoMatch.unsure = true;\n      photoMatch.restore.disabled = false;\n      photoMatch.restore.textContent = photoMatch.restoreLabel;\n      setPhotoMatchStatus(\"We couldn't bring them here just now. Please try again.\");\n      return;\n    }\n    const restored = Number(reply?.restored) || 0;\n    // 0 after an unanswered try: that try may have shared them, so look before saying nothing.\n    const maybeShared = restored > 0 || photoMatch.unsure;\n    hidePhotoMatch();\n    if (!maybeShared) return;\n    if (restored) clarityEvent('mythumi_photo_restore_done', null, { restored, signedIn: photoMatch.signedIn });\n    const shown = await newPageDesigns(before);\n    if (shown > 0 || (shown < 0 && restored > 0)) {\n      // call() kept the reply's visitorToken (or, signed in, the account now has them), so the\n      // gallery's /history lists them.\n      switchView('gallery');\n      // switchView brings the stage into view on a phone; on a desktop the page would stay\n      // scrolled to where the card was, below the gallery's stage.\n      if (!mobileGalleryMedia.matches) root.scrollIntoView({ block: 'start', behavior: 'smooth' });\n      const count = shown > 0 ? shown : restored;\n      const where = photoMatch.signedIn ? ' and saved to your account' : '';\n      notify({\n        message: count === 1 ? `Your design is back in My Gallery${where}.` : `Your ${count} designs are back in My Gallery${where}.`,\n        key: 'photo-restore',\n      });\n    } else if (restored > 0) {\n      // Made on another product page: that page's gallery lists them, this one does not.\n      notify({\n        message: restored === 1\n          ? \"Your design is back. It's in My Gallery on the product page you made it on.\"\n          : `Your ${restored} designs are back. They're in My Gallery on the product pages you made them on.`,\n        key: 'photo-restore',\n      });\n    }\n  }\n\n  photoMatch.restore?.addEventListener('click', restorePhotoMatch);\n  photoMatch.dismiss?.addEventListener('click', () => {\n    const fromKeyboard = document.activeElement === photoMatch.dismiss;\n    hidePhotoMatch();\n    clarityEvent('mythumi_photo_match_dismissed', null, { signedIn: photoMatch.signedIn });\n    // The button just left the page: hand focus back to the upload control.\n    if (fromKeyboard) el.file?.focus({ preventScroll: true });\n  });\n\n  const VIEWER_URL = root.dataset.viewer;\n  const PRODUCT_CATALOGUE = readShopifyProductCatalogue(root);\n  const GIFT_BOX_PRODUCT = readGiftBoxProduct(root);\n  // 'characters' (Solo / Duo, 2026-10-20) only matters on a product that has that option (custom-ttrpg-miniature).\n  const SUPPORTED_PRODUCT_OPTIONS = new Set(['size', 'base', 'finish', 'characters']);\n  const productOptionByKey = new Map(PRODUCT_CATALOGUE.options.map((option) => [option.key, option]));\n  const unsupportedProductOptions = PRODUCT_CATALOGUE.options.filter((option) => (\n    !SUPPORTED_PRODUCT_OPTIONS.has(option.key)\n    && !(option.key === 'title' && option.values.length === 1 && option.values[0] === 'Default Title')\n  ));\n  const initialProductVariant = PRODUCT_CATALOGUE.variants.find((variant) => (\n    variant.id === PRODUCT_CATALOGUE.selectedVariantId\n  )) || PRODUCT_CATALOGUE.variants.find((variant) => variant.available) || PRODUCT_CATALOGUE.variants[0] || null;\n  const selectedProductOptions = new Map();\n  PRODUCT_CATALOGUE.options.forEach((option, index) => {\n    if (!SUPPORTED_PRODUCT_OPTIONS.has(option.key)) return;\n    const value = initialProductVariant?.options[index];\n    if (value) selectedProductOptions.set(option.key, value);\n  });\n  let selectedProductVariant = initialProductVariant;\n  // Shopify renders the operator's base settings into the section JSON above. The viewer receives\n  // the configured Shopify Files URL (or the bundled square fallback) through baseModelUrls.\n  const SHOPIFY_BASE_CATALOGUE = readShopifyBaseCatalogue(root);\n  const initialBaseOptionValue = selectedProductOptions.get('base') || '';\n  const initialVariantBase = SHOPIFY_BASE_CATALOGUE.bases.find((base) => (\n    normalizeShopifyOptionValue(base.variantValue) === normalizeShopifyOptionValue(initialBaseOptionValue)\n  ));\n  const configuredDefaultBase = initialVariantBase?.id || SHOPIFY_BASE_CATALOGUE.default;\n  // 2026-10-13 (Drew): no base unless the variant or the section names one. The\n  // section's \"No base\" tile (value \"none\") stands for that state.\n  const NO_BASE_ID = 'none';\n  root.querySelectorAll('.aicp-g-base input').forEach((radio) => {\n    radio.checked = radio.value === (configuredDefaultBase || NO_BASE_ID);\n  });\n\n  const urlParams = new URLSearchParams(location.search);\n  const requestedCategory = urlParams.get('cat');\n  // Product-fixed style (Shopify product metafield custom.creator_style, rendered\n  // by the section). Such a product always generates that one style: no style\n  // cards, no ?cat= override, and its gallery only lists designs in that style.\n  // Product style list (custom.creator_styles, rendered by the section as JSON): the\n  // product's cards come from Shopify, each its own backend style. Beats a fixed style.\n  const STYLE_LIST = (() => {\n    try {\n      const node = root.querySelector('[data-aicp-style-list]');\n      const list = node ? JSON.parse(node.textContent) : [];\n      return Array.isArray(list) ? list.filter((entry) => entry && typeof entry.name === 'string' && entry.name) : [];\n    } catch {\n      return [];\n    }\n  })();\n  const HAS_STYLE_LIST = STYLE_LIST.length > 0;\n  // Solo / Duo inside one product (2026-10-20): a product option named Characters. Style list entries carry\n  // characters \"Duo\" for the two-character styles; any other entry is the option's first value (Solo). The\n  // shopper picks Solo or Duo before generating (that filters the cards); after that the price, the variant and\n  // the order follow the design on stage. Every line below is inert on products without the option.\n  const productCharactersOption = productOptionByKey.get('characters') || null;\n  const charactersValue = (wanted) => {\n    if (!productCharactersOption) return '';\n    const values = productCharactersOption.values;\n    return values.find((value) => normalizeShopifyOptionValue(value) === normalizeShopifyOptionValue(wanted)) || values[0] || '';\n  };\n  const STYLE_CHARACTERS = new Map(STYLE_LIST.map((entry) => [entry.name, String(entry.characters || '')]));\n  const charactersForStyle = (styleName) => charactersValue(STYLE_CHARACTERS.get(styleName) || '');\n  let createCharacters = productCharactersOption ? charactersValue(selectedProductOptions.get('characters') || '') : '';\n  // syncProductVariant() relies on consts declared further down; it may run only after its first init call.\n  let variantLogicReady = false;\n  const FIXED_STYLE = HAS_STYLE_LIST ? '' : (root.dataset.aicpFixedStyle || '').trim();\n  const fixedStyle = { label: '', usesPoses: false, ready: false, error: null };\n  // Backend style names this page can generate (its cards, or the fixed style).\n  // The gallery and the resume memory only take designs made with these, so a\n  // design made on another product never shows up \u2014 or gets carted \u2014 here.\n  let pageStyleNames = new Set(FIXED_STYLE ? [FIXED_STYLE] : STYLE_LIST.map((entry) => entry.name));\n  const INITIAL_CAT = !FIXED_STYLE && !HAS_STYLE_LIST && ['pet', 'trpg', 'people'].includes(requestedCategory)\n    ? requestedCategory\n    : (root.dataset.aicpCategory || 'pet');\n  // Initial view: ?aicp_view=gallery (restore links, backend /restore/:code) opens the gallery,\n  // otherwise the create step. Not ?view=: Shopify reads that as an alternate template, and\n  // ?view=gallery rendered the plain product template without this creator.\n  const INITIAL_VIEW = urlParams.get('aicp_view') === 'gallery' ? 'gallery' : 'create';\n  // Restore links also name the design their email showed, ?aicp_job=<jobId> (backend 9e93294,\n  // 2026-10-29): the gallery opens on it instead of the newest, once (see settleRequestedJob).\n  // A shopper's own pick or a generation drops it; a design missing from this gallery leaves the newest.\n  let requestedJob = INITIAL_VIEW === 'gallery' ? (urlParams.get('aicp_job') || '').trim() : '';\n  let mobileCreateFocusRequest = 0;\n  const focusMobileCreateOnEntry = () => {\n    if (INITIAL_VIEW !== 'create' || !window.matchMedia?.('(max-width: 989px)').matches) return;\n    const request = ++mobileCreateFocusRequest;\n    const focus = () => Promise.resolve(document.fonts?.ready).then(() => {\n      let previousTop;\n      let stableFrames = 0;\n      let frames = 0;\n      const settle = () => {\n        if (request !== mobileCreateFocusRequest) return;\n        const step = root.querySelector('[data-aicp-upload-step]');\n        if (!step) return;\n        const top = step.getBoundingClientRect().top;\n        stableFrames = previousTop !== undefined && Math.abs(top - previousTop) < 0.5 ? stableFrames + 1 : 0;\n        previousTop = top;\n        frames += 1;\n        if (stableFrames >= 2 || frames >= 12) {\n          // Land on the product title when it is shown (G pages, 2026-10-08),\n          // so the shopper sees which product this is; else on the upload step.\n          const heading = root.querySelector('.aicp-create-form > .aicp-creator-character');\n          (heading && heading.offsetHeight > 1 ? heading : step).scrollIntoView({ block: 'start', behavior: 'auto' });\n          return;\n        }\n        requestAnimationFrame(settle);\n      };\n      requestAnimationFrame(settle);\n    });\n    if (document.readyState === 'complete') focus();\n    else window.addEventListener('load', focus, { once: true });\n  };\n  const state = {\n    styles: [], // zero or one style: [{ id, name, label, directive }]\n    uploaded: null,\n    screeningId: null,\n    uploadedPreview: '', // blob URL of the chosen photo \u2014 seeds the stage during generation\n    uploading: false, // an /upload POST is in flight \u2014 input disabled until it settles\n    busy: false,\n    poses: [], // zero or one pet pose: [{ category, name, label }]\n  };\n  // Style showcase (syncStyleShowcase): the poster swiper's controls once it is set up, each style's own\n  // gallery, and the style whose gallery is shown ('' = the page's own pictures).\n  let posterSwiper = null;\n  const showcaseTemplates = new Map([...root.querySelectorAll('template[data-aicp-showcase]')].map((t) => [t.dataset.aicpShowcase, t]));\n  const showcaseSets = new Map();\n  let shownShowcase = '';\n  // Second photo (syncSecondPhoto): the photo the shopper picked first, and the second one while it is in use.\n  let firstPhoto = null;\n  let secondPhoto = null;\n  // Notes hint (syncStyleNoteHint): list styles with a hint, the page's own placeholder, the hint shown now.\n  const STYLE_HINTS = new Map(STYLE_LIST.filter((entry) => entry.hint).map((entry) => [entry.name, String(entry.hint)]));\n  let notesPlaceholderBase = null;\n  let shownNoteHint = '';\n\n  /* ==========================================================================\n     AICP-TIMING (temporary) \u2014 measures how long generation takes.\n     Three numbers are printed to the console (tagged \"[AICP timing]\"):\n       1. 2D generated  : CREATE click \u2192 2D image arrives\n       2. 3D generated  : 2D image \u2192 3D model URL arrives\n       3. TOTAL         : photo upload \u2192 3D model rendered & usable on the page\n\n     TO REMOVE EVERYTHING: search for \"AICP_TIMING\" (and \"AICP-TIMING\" in\n     comments). Delete:\n       - this whole block (from \"AICP-TIMING (temporary)\" to \"END AICP-TIMING\")\n       - every line containing \"AICP-TIMING\" or \"AICP_TIMING\" below, at:\n           upload()            ~line 657-659  (3 lines, TOTAL start)\n           createFor().then    ~line 1765-1766 (per-job 2D anchor)\n           generate()          ~line 1718-1719 (2D/3D clocks start)\n           poll() 2D block     ~line 1627-1632 (2D log)\n           poll() 3D block     ~line 1656-1662 (3D log)\n           loadStageModel()    ~line 1305-1310 (TOTAL log)\n     Nothing else in the file references it, so removal is safe.\n     ========================================================================== */\n  const AICP_TIMING = {\n    uploadStart: 0,   // when the photo upload began\n    createStart: 0,   // when CREATE was clicked\n    armTotal: false,  // true only for a user-initiated run (not history clicks)\n    totalLogged: false,\n    perRun: new Map(),\n  };\n  function aicpTimingLog(label, ms) {\n    if (ms == null || !Number.isFinite(ms)) return;\n    console.log(\n      `%c[AICP timing] ${label}: ${(ms / 1000).toFixed(1)}s (${Math.round(ms)}ms)`,\n      'color:#0a8a5f;font-weight:bold'\n    );\n  }\n  function aicpTimingFor(jobId) {\n    let entry = AICP_TIMING.perRun.get(jobId);\n    if (!entry) {\n      entry = {};\n      AICP_TIMING.perRun.set(jobId, entry);\n    }\n    return entry;\n  }\n  /* ======================= END AICP-TIMING ======================= */\n\n  /* ---------- client timings \u2192 PUT /jobs/:id/client-metrics (handoff 2026-10-06 \u00A76) ----------\n     One performance.now() clock per page session: t0 = Generate clicked (before\n     /create), t2d = first cleanUrl, t3d = first modelUrl of the succeeded job,\n     trender = the model's first visible frame in the stage viewer. Sent once the\n     job is terminal and again when the render time lands; a missing stage is\n     left out (never 0). Resumed runs after a reload have no t0 and send nothing.\n     Failures never reach the shopper. */\n  // Sent at the end: after the 3D's first frame (or its load failing), straight\n  // away for a failed job, or after a fallback wait; once more if the render\n  // time only lands after that (the handoff allows adding a missing field).\n  const CLIENT_METRICS = new Map(); // jobId -> { t0, t2d, t3d, trender, terminal, sent }\n  const METRICS_FALLBACK_MS = 120000;\n\n  function metricsFor(jobId) {\n    return jobId ? CLIENT_METRICS.get(jobId) || null : null;\n  }\n\n  function sendClientMetrics(jobId) {\n    const m = metricsFor(jobId);\n    if (!m || !m.terminal) return;\n    const clientTimings = {};\n    const ms = (value) => Math.max(0, Math.min(86400000, Math.round(value)));\n    if (m.t2d) clientTimings.twoDGenerationMs = ms(m.t2d - m.t0);\n    if (m.t2d && m.t3d) clientTimings.threeDGenerationMs = ms(m.t3d - m.t2d);\n    if (m.trender) clientTimings.threeJsRenderMs = ms(m.trender - m.t0);\n    const signature = Object.keys(clientTimings).join(',');\n    if (!signature || signature === m.sent) return;\n    m.sent = signature;\n    clearTimeout(m.fallbackTimer);\n    const body = { schemaVersion: 1, clientTimings };\n    if (visitorToken()) body.visitorToken = visitorToken();\n    call(`/jobs/${encodeURIComponent(jobId)}/client-metrics`, {\n      method: 'PUT',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(body),\n    }).catch((error) => {\n      console.warn('[realizeit] client-metrics not recorded', { status: error?.status ?? null });\n    });\n  }\n\n  // \u00A73.3 /categories: a tab whose category record has poses === true renders\n  // the pose list in place of the style cards; the generation style comes from\n  // the record's `style` field (API-driven, never hardcoded).\n  let styleCategories = [];\n  let styleCategoriesLoaded = false;\n  // Poses come from GET /poses (handoff: never invent options); these names\n  // only fill in a label the response leaves out.\n  const POSE_LABELS = {\n    default: 'Default Pose',\n    sitting: 'Sitting',\n    standing: 'Standing',\n    'lying-down': 'Lying Down',\n    'play-bow': 'Play Bow',\n    walking: 'Walking',\n    running: 'Running',\n    'curling-up': 'Curling Up',\n  };\n  const poseCache = {};\n\n  /* The proxy occasionally answers 200 with an unusable body on a bad network;\n     retry a few times before giving up (retried again on the next tab switch). */\n  async function ensureStyleCategories() {\n    if (styleCategoriesLoaded) return;\n    let lastError = null;\n    for (let attempt = 0; attempt < 3 && !styleCategoriesLoaded; attempt++) {\n      try {\n        // pet pages start this read with the page (the section's inline prefetch)\n        const body = await call('/categories', attempt === 0 ? { prefetch: true } : undefined);\n        if (Array.isArray(body.categories)) {\n          styleCategories = body.categories;\n          styleCategoriesLoaded = true;\n        }\n      } catch (error) {\n        lastError = error;\n        await new Promise((resolve) => setTimeout(resolve, 600));\n      }\n    }\n    // The main pet page builds from /categories: without it there is nothing\n    // valid to send, so offer a Retry rather than a silent dead button. (TTRPG\n    // never uses it; a fixed pet page reports it from initFixedStyle.)\n    if (!styleCategoriesLoaded && lastError && INITIAL_CAT === 'pet' && !FIXED_STYLE) {\n      showApiError('options', lastError, { retry: () => { void ensureStyleCategories().then(() => syncStyleArea()); } });\n    }\n  }\n\n  function activeTabCategoryName() {\n    return INITIAL_CAT;\n  }\n\n  function categoryOfName(name) {\n    const value = (name || '').toLowerCase();\n    if (value === 'trpg' || value.startsWith('trpg')) return 'trpg';\n    // pet-chibi is only offered on People (as Soft Chibi); keep its history there.\n    if (value === 'pet-chibi') return 'people';\n    if (value === 'pet' || value.includes('pet')) return 'pet';\n    return 'people';\n  }\n\n  function activeCategoryRecord() {\n    const name = activeTabCategoryName();\n    return styleCategories.find((c) => c.category === name) || null;\n  }\n\n  function styleAreaIsPoseMode() {\n    // A fixed Shopify style never takes a pose unless it is the pet category's\n    // own built-in style (pet-special), which keeps its pose picker.\n    if (FIXED_STYLE) return fixedStyle.usesPoses;\n    if (HAS_STYLE_LIST) return false;\n    return activeTabCategoryName() === 'pet';\n  }\n\n  function hasRequiredCreatorSelection() {\n    if (FIXED_STYLE) {\n      if (!fixedStyle.ready) return false;\n      return fixedStyle.usesPoses ? state.poses.some((pose) => pose.category === 'pet') : state.styles.length > 0;\n    }\n    if (HAS_STYLE_LIST) return state.styles.length > 0;\n    if (activeTabCategoryName() === 'pet') {\n      return state.poses.some((pose) => pose.category === 'pet');\n    }\n    return state.styles.length > 0;\n  }\n\n  let selectionReady = Promise.resolve();\n  let selectionRevealSeq = 0;\n\n  function syncSelectionLabel() {\n    if (!el.stylesLabel) return;\n    const poseMode = activeTabCategoryName() === 'pet' || activeTabCategoryName() === 'trpg';\n    const label = poseMode\n      ? el.stylesLabel.dataset.aicpPoseStepLabel\n      : el.stylesLabel.dataset.aicpStyleStepLabel;\n    if (label) el.stylesLabel.textContent = label;\n    const ariaLabel = label || 'Choose one option';\n    el.styles?.setAttribute('aria-label', ariaLabel);\n    el.posesRow?.setAttribute('aria-label', ariaLabel);\n  }\n\n  // The cards are never locked (Dave, 2026-10-28): a style or pose can be chosen before the\n  // photo, and only the Create button waits for it. `hidden` only matters on templates that\n  // keep the cards out of sight until the upload; Design G pages show them from the start.\n  function hideSelectionStep() {\n    selectionRevealSeq += 1;\n    el.selectionOptions?.setAttribute('hidden', '');\n  }\n\n  function scrollSelectionIntoViewIfNeeded() {\n    if (!window.matchMedia?.('(max-width: 989px)').matches || !el.selectionStep) return;\n    // The \"we found your designs\" card sits above this step: show it, not scroll past it.\n    if (photoMatch.card && !photoMatch.card.hidden) {\n      photoMatch.card.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n      return;\n    }\n    window.requestAnimationFrame(() => {\n      window.requestAnimationFrame(() => {\n        const bounds = el.selectionStep.getBoundingClientRect();\n        if (bounds.top > window.innerHeight || bounds.bottom < 0) {\n          el.selectionStep.scrollIntoView({ block: 'start', behavior: 'smooth' });\n        }\n      });\n    });\n  }\n\n  async function revealSelectionStep() {\n    const seq = ++selectionRevealSeq;\n    await selectionReady;\n    if (seq !== selectionRevealSeq || !state.uploaded) return;\n    await syncStyleArea();\n    if (seq !== selectionRevealSeq || !state.uploaded) return;\n    syncSelectionLabel();\n    el.selectionOptions?.removeAttribute('hidden');\n    scrollSelectionIntoViewIfNeeded();\n  }\n\n  let viewerLoad = null;\n  let pollAbort = false;\n  let currentRuns = [];\n  // 3D preloads still in flight after the server job finished. This gates\n  // checkout while the final 88\u2013100% follows bytes + viewer completion.\n  let pending3dCount = 0;\n\n  /* ---------- request layer (contract) ---------- */\n\n  // The section's inline script starts the first /history and /styles reads\n  // while the page is still parsing. A caller that opts in ({ prefetch: true })\n  // takes that response once, if it is the same URL and still fresh.\n  const PREFETCH_MAX_AGE_MS = 20000;\n  function takePrefetched(url) {\n    const store = window.__aicpPrefetch;\n    const entry = store?.[url];\n    if (!entry) return null;\n    delete store[url];\n    return Date.now() - entry.at < PREFETCH_MAX_AGE_MS ? entry.response : null;\n  }\n\n  // Backend handoff 2026-10-06 (\u670D\u52A1\u7AEF\u9519\u8BEF\u524D\u7AEF\u5F39\u7A97\u5BF9\u63A5.md): not every error is JSON.\n  // The App Proxy, gateways and unknown paths can answer with HTML or nothing,\n  // so read text first and parse it safely. Errors carry status/body/quota/\n  // alternatives for the popup mapping; `error.network` marks a fetch that never\n  // got an HTTP status, `error.aborted` one this page cancelled (timeouts).\n  async function call(path, options) {\n    let response;\n    const method = String(options?.method || 'GET').toUpperCase();\n    const endpoint = String(path).split('?')[0];\n    try {\n      // The query token goes only where the handoff lists it (status, jobs,\n      // history), so it is not spread into more URLs and logs than needed.\n      const tokenInQuery = (method === 'GET' || method === 'DELETE') && /^\\/(status|jobs|history)(\\/|\\?|$)/.test(String(path));\n      const url = `${proxy}${tokenInQuery ? withVisitorToken(path) : path}`;\n      const prefetched = method === 'GET' && options?.prefetch ? takePrefetched(url) : null;\n      // A prefetch that failed outright is simply asked again.\n      response = prefetched\n        ? await prefetched.catch(() => fetch(url, options))\n        : await fetch(url, options);\n    } catch (cause) {\n      const error = new Error('Could not reach the design service. Please try again shortly.');\n      error.endpoint = endpoint;\n      error.network = true;\n      error.aborted = cause?.name === 'AbortError';\n      throw error;\n    }\n\n    let raw = '';\n    try {\n      raw = await response.text();\n    } catch {}\n    let body = null;\n    try {\n      body = raw ? JSON.parse(raw) : null;\n    } catch {\n      // Shopify / gateway HTML or an empty body: handled as a non-JSON reply below.\n    }\n    const json = body && typeof body === 'object' && !Array.isArray(body) ? body : null;\n    if (json) rememberVisitor(json);\n\n    if (!response.ok || !json) {\n      if (response.status === 401 || response.status === 403) {\n        console.error(`[realizeit] proxy rejected ${endpoint} with ${response.status}`);\n      }\n      const error = new Error(json?.error || `Request failed (${response.status})`);\n      error.endpoint = endpoint;\n      error.status = response.status;\n      error.body = json;\n      error.nonJson = !json;\n      error.quota = json?.quota;\n      error.alternatives = Array.isArray(json?.alternatives) ? json.alternatives : null;\n      throw error;\n    }\n    return json;\n  }\n\n  /* POST {proxy}/upload with progress, answering like call() (2026-10-27).\n     fetch cannot report upload progress, and the fixed 30 s timeout it had\n     also ran through the server's photo check: a phone on a slow connection\n     that was still sending, or a check that took 25 s, both read as a failed\n     upload. This one fails only on silence (UPLOAD_STALL_MS / UPLOAD_CHECK_MS)\n     and reports `progress` 0..1 while sending, then 'checking'. */\n  function postUpload(form, onProgress) {\n    return new Promise((resolve, reject) => {\n      const xhr = new XMLHttpRequest();\n      let timer = null;\n      let settled = false;\n      const fail = (fields) => {\n        if (settled) return;\n        settled = true;\n        clearTimeout(timer);\n        reject(Object.assign(new Error('Could not reach the design service. Please try again shortly.'), { endpoint: '/upload' }, fields));\n      };\n      const wait = (ms, stalled) => {\n        clearTimeout(timer);\n        timer = setTimeout(() => {\n          fail({ network: true, aborted: true, stalled });\n          xhr.abort();\n        }, ms);\n      };\n      xhr.upload.onprogress = (event) => {\n        if (!event.lengthComputable) return;\n        onProgress?.(Math.min(1, event.loaded / event.total));\n        if (event.loaded < event.total) wait(UPLOAD_STALL_MS, 'sending');\n      };\n      xhr.upload.onload = () => {\n        onProgress?.('checking');\n        wait(UPLOAD_CHECK_MS, 'checking');\n      };\n      xhr.onerror = () => fail({ network: true });\n      xhr.onload = () => {\n        if (settled) return;\n        settled = true;\n        clearTimeout(timer);\n        let json = null;\n        try {\n          const body = xhr.responseText ? JSON.parse(xhr.responseText) : null;\n          json = body && typeof body === 'object' && !Array.isArray(body) ? body : null;\n        } catch {\n          // Shopify / gateway HTML: handled as a non-JSON reply, as call() does.\n        }\n        if (json) rememberVisitor(json);\n        if (xhr.status >= 200 && xhr.status < 300 && json) {\n          resolve(json);\n          return;\n        }\n        const error = new Error(json?.error || `Request failed (${xhr.status})`);\n        Object.assign(error, {\n          endpoint: '/upload',\n          status: xhr.status,\n          body: json,\n          nonJson: !json,\n          quota: json?.quota,\n          alternatives: Array.isArray(json?.alternatives) ? json.alternatives : null,\n        });\n        reject(error);\n      };\n      xhr.open('POST', `${proxy}/upload`);\n      wait(UPLOAD_STALL_MS, 'sending');\n      xhr.send(form);\n    });\n  }\n\n  const basePreview = {\n    baseType: configuredDefaultBase,\n    engraving: '',\n    catalogue: SHOPIFY_BASE_CATALOGUE,\n    touched: false,\n  };\n  const baseCatalogueReady = Promise.resolve(SHOPIFY_BASE_CATALOGUE);\n\n  function selectedBaseModelUrls() {\n    const selected = selectedBaseConfig();\n    const round = basePreview.catalogue.bases.find((base) => base.viewerType === 'round');\n    const firstSquare = basePreview.catalogue.bases.find((base) => base.viewerType === 'square');\n    return {\n      round: round?.modelUrl || null,\n      square: (selected?.viewerType === 'square' ? selected : firstSquare)?.modelUrl || null,\n    };\n  }\n\n  function selectedBaseConfig() {\n    return basePreview.catalogue.bases.find((base) => base.id === basePreview.baseType) || null;\n  }\n\n  // 2026-10-14 (Drew): a base can come without a name (the round black base). While\n  // one is picked, the name, dates and memorial fields are greyed out, the 3D shows\n  // no lettering and none of them reach the order. Typed text is kept for a switch\n  // back to an engravable base. \"No base\" counts as engravable: typing a name there\n  // picks the first engravable base (ensureBaseForEngraving).\n  function engravableBase(base) {\n    return !base || base.engravable !== false;\n  }\n\n  function activeEngraving() {\n    return engravableBase(selectedBaseConfig()) ? basePreview.engraving : '';\n  }\n\n  function syncEngravingAvailability() {\n    const off = !engravableBase(selectedBaseConfig());\n    g.baseControl?.classList.toggle('is-engraving-off', off);\n    if (g.engravingOff) g.engravingOff.hidden = !off;\n    [g.name, root.querySelector('[data-ai3d-g-dates]'), root.querySelector('[data-ai3d-g-memorial]')]\n      .forEach((field) => { if (field) field.disabled = off; });\n  }\n\n  function selectedBaseViewerKey() {\n    const base = selectedBaseConfig();\n    return base ? `${base.viewerType}:${base.modelUrl || ''}` : 'none';\n  }\n\n  /* ---------- small helpers ---------- */\n\n  function say() {}\n\n  /* ---------- error popup (backend handoff 2026-10-06) ----------\n     One small dialog for errors a shopper can act on. Messages follow the\n     handoff table; server text (error / alternatives) is only ever set with\n     textContent, never innerHTML, and HTML/5xx bodies are never shown. */\n  const popup = { el: null, lastKey: '', lastAt: 0, returnFocus: null };\n  const LOGIN_URL = root.dataset.aicpLoginUrl || '/account/login';\n\n  function buildPopup() {\n    if (popup.el) return popup.el;\n    const wrap = document.createElement('div');\n    wrap.className = 'aicp-popup';\n    wrap.hidden = true;\n    wrap.innerHTML = `\n      <div class=\"aicp-popup__backdrop\" data-aicp-popup-dismiss></div>\n      <div class=\"aicp-popup__panel\" role=\"alertdialog\" aria-modal=\"true\" aria-labelledby=\"aicp-popup-msg\" aria-describedby=\"aicp-popup-hint\">\n        <p class=\"aicp-popup__msg\" id=\"aicp-popup-msg\"></p>\n        <p class=\"aicp-popup__hint\" id=\"aicp-popup-hint\" hidden></p>\n        <ul class=\"aicp-popup__alts\" hidden></ul>\n        <div class=\"aicp-popup__actions\"></div>\n      </div>`;\n    wrap.querySelector('[data-aicp-popup-dismiss]').addEventListener('click', closePopup);\n    // While open: Escape closes it wherever focus is, and Tab stays on its buttons.\n    document.addEventListener('keydown', (event) => {\n      if (wrap.hidden) return;\n      if (event.key === 'Escape') {\n        closePopup();\n        return;\n      }\n      if (event.key !== 'Tab') return;\n      const buttons = [...wrap.querySelectorAll('.aicp-popup__btn')];\n      if (!buttons.length) return;\n      const first = buttons[0];\n      const last = buttons[buttons.length - 1];\n      if (!wrap.contains(document.activeElement)) {\n        event.preventDefault();\n        first.focus();\n      } else if (event.shiftKey && document.activeElement === first) {\n        event.preventDefault();\n        last.focus();\n      } else if (!event.shiftKey && document.activeElement === last) {\n        event.preventDefault();\n        first.focus();\n      }\n    });\n    // On <body>, not inside the section: the page wrapper's stacking context\n    // must not let the sticky header cover the dialog.\n    document.body.append(wrap);\n    popup.el = wrap;\n    return wrap;\n  }\n\n  function closePopup() {\n    if (!popup.el || popup.el.hidden) return;\n    popup.el.hidden = true;\n    root.classList.remove('aicp--popup-open');\n    popup.returnFocus?.focus?.({ preventScroll: true });\n    popup.returnFocus = null;\n  }\n\n  // actions: [{ label, run, primary }]; run may be omitted for a plain Close.\n  function notify({ message, hint = '', alternatives = null, actions = [], key = '' }) {\n    const now = Date.now();\n    const dedupeKey = key || message;\n    // The same failure within a few seconds (double click, retried poll) shows once.\n    if (dedupeKey === popup.lastKey && now - popup.lastAt < 4000 && popup.el && !popup.el.hidden) return;\n    popup.lastKey = dedupeKey;\n    popup.lastAt = now;\n    // A popup replacing an open one keeps the original focus to return to.\n    const wasOpen = Boolean(popup.el && !popup.el.hidden);\n    const wrap = buildPopup();\n    wrap.querySelector('.aicp-popup__msg').textContent = message;\n    const hintEl = wrap.querySelector('.aicp-popup__hint');\n    hintEl.textContent = hint;\n    hintEl.hidden = !hint;\n    const altsEl = wrap.querySelector('.aicp-popup__alts');\n    altsEl.textContent = '';\n    const alts = (alternatives || []).filter((text) => typeof text === 'string' && text.trim()).slice(0, 4);\n    alts.forEach((text) => {\n      const li = document.createElement('li');\n      li.textContent = text.trim();\n      altsEl.append(li);\n    });\n    altsEl.hidden = !alts.length;\n    const actionsEl = wrap.querySelector('.aicp-popup__actions');\n    actionsEl.textContent = '';\n    const list = actions.length ? actions : [{ label: 'Close', primary: true }];\n    if (!list.some((action) => !action.run)) list.push({ label: 'Close' });\n    list.forEach((action) => {\n      const button = document.createElement('button');\n      button.type = 'button';\n      button.className = `aicp-popup__btn${action.primary ? ' aicp-popup__btn--primary' : ''}`;\n      button.textContent = action.label;\n      button.addEventListener('click', () => {\n        closePopup();\n        action.run?.();\n      });\n      actionsEl.append(button);\n    });\n    if (!wasOpen) {\n      const active = document.activeElement;\n      popup.returnFocus = active && active !== document.body ? active : el.go;\n    }\n    wrap.hidden = false;\n    root.classList.add('aicp--popup-open');\n    (actionsEl.querySelector('.aicp-popup__btn--primary') || actionsEl.querySelector('button'))?.focus({ preventScroll: true });\n  }\n\n  const CONNECT_MESSAGE = \"We couldn't connect to the design service. Please refresh or try again later.\";\n\n  // /create answers that leave it open whether the job exists: 503, a timeout\n  // or lost connection, a non-JSON 5xx, or a 2xx whose body is unreadable.\n  function isUnknownCreateOutcome(error) {\n    const status = error?.status;\n    return status === 503\n      || Boolean(error?.aborted)\n      || (Boolean(error?.network) && !status)\n      || (Boolean(error?.nonJson) && (status >= 500 || (status >= 200 && status < 300)));\n  }\n\n  // The last /create sent, and the one whose outcome is unknown (see above).\n  let recoveringUnknownRun = false;\n  let lastCreateAttempt = null; // { style, styleLabel, at, file, note, pose }\n  let unknownRun = null;\n\n  // The job an unknown outcome may have created: same style, made since the\n  // attempt (two minutes of clock slack), still in the studio \u2014 or, with\n  // `succeeded`, already done. A failed job is never taken for it.\n  function unknownRunJob(pending, items, { succeeded = false, failed = false } = {}) {\n    return items.find((item) => {\n      const created = typeof item.createdAt === 'number' ? item.createdAt : Date.parse(item.createdAt || '');\n      if (!item?.id || pending.knownIds?.includes(String(item.id)) || item.style !== pending.style || !(created >= pending.at - 120000)) return false;\n      if (succeeded) return item.status === 'succeeded';\n      if (failed) return item.status === 'failed';\n      return item.status !== 'succeeded' && item.status !== 'failed';\n    }) || null;\n  }\n\n  // Bounded like the other reads: a stalled proxy must not hold the page.\n  async function readHistoryItems() {\n    const controller = new AbortController();\n    const timer = setTimeout(() => controller.abort(), 10000);\n    try {\n      const body = await call('/history', { signal: controller.signal });\n      const items = body.history || body.items || [];\n      return Array.isArray(items) ? items : [];\n    } finally {\n      clearTimeout(timer);\n    }\n  }\n\n  // Is the shopper about to send the same request as the unknown one? A new\n  // photo, note, pose or style is a new design, never a stand-in for the old.\n  function sameAsUnknownRun(pending, poseMode) {\n    const note = el.notes ? el.notes.value.trim().slice(0, noteLimit()).trim() : '';\n    const record = poseMode ? activeCategoryRecord() : null;\n    const style = poseMode ? record?.style : state.styles[0]?.name;\n    const pose = poseMode ? (state.poses.find((entry) => entry.category === record?.category)?.name || null) : null;\n    const directive = poseMode ? '' : (state.styles[0]?.directive || '');\n    return pending.file === state.uploaded && pending.note === note && (pending.pose || null) === pose\n      && pending.style === style && (pending.directive || '') === directive;\n  }\n\n  // Follow a recovered job like a remembered run, with the upload, note and\n  // pose it was made from, so the finished design can go in the cart.\n  function followRecoveredJob(match, pending) {\n    publishPreviewStart(match.id, pending.previewAnalytics);\n    unknownRun = null;\n    const miniPop = miniPopHistory(match);\n    const saved = {\n      jobId: match.id,\n      style: pending.style,\n      styleLabel: pending.styleLabel,\n      uploaded: match.sourceFile || pending.file || state.uploaded,\n      instructions: miniPop.instructions !== undefined ? miniPop.instructions : (match.instructions ?? pending.note ?? ''),\n      pose: match.pose || pending.pose || null,\n    };\n    rememberJob(saved.jobId, saved.style, saved.styleLabel, saved.uploaded, { instructions: saved.instructions, pose: saved.pose });\n    // Like generate(): the followed run takes the stage from any older design\n    // (its 2D/3D go there and its mask ends with it), and the gallery shows it.\n    clearStage();\n    stageOwnedByGeneration = true;\n    resume(saved);\n    switchView('gallery');\n  }\n\n  // \"Open gallery\" after an unknown outcome: look for that job in /history a\n  // few times (it can appear a little late) and follow it if it is still in\n  // the studio. /create is never re-sent from here.\n  async function recoverUnknownRun() {\n    const pending = unknownRun;\n    if (!pending || state.busy) {\n      switchView('gallery');\n      return;\n    }\n    // While the job is looked for, an empty gallery does not bounce to Create.\n    recoveringUnknownRun = true;\n    setRecoveryStatus(true);\n    let found = false;\n    try {\n      found = await findUnknownRun(pending);\n    } finally {\n      recoveringUnknownRun = false;\n      setRecoveryStatus(false);\n      // Nothing found and nothing to show: back to Create, as an empty gallery does.\n      if (!found && !state.busy && !galleryStage.item && !historyItems.length && root.classList.contains('aicp--gallery')) switchView('create');\n    }\n  }\n\n  // An empty stage while the job is looked for says so, instead of a blank box.\n  function setRecoveryStatus(on) {\n    if (!g.stageStatus) return;\n    if (on && !galleryStage.item) {\n      setStageStatusText('Checking your gallery for your design\u2026');\n      g.stageStatus.hidden = false;\n      g.stageStatus.dataset.aicpRecovery = '1';\n    } else if (!on && g.stageStatus.dataset.aicpRecovery) {\n      delete g.stageStatus.dataset.aicpRecovery;\n      if (stageStatusText() === 'Checking your gallery for your design\u2026') {\n        setStageStatusText('');\n        g.stageStatus.hidden = true;\n      }\n    }\n  }\n\n  async function findUnknownRun(pending) {\n    switchView('gallery');\n    let reads = 0;\n    for (let attempt = 0; attempt < 4; attempt += 1) {\n      if (attempt) await new Promise((resolve) => setTimeout(resolve, 5000));\n      if (state.busy || unknownRun !== pending) return;\n      let items;\n      try {\n        items = await readHistoryItems();\n        reads += 1;\n      } catch (error) {\n        logApiError('history', error);\n        continue;\n      }\n      // A run started meanwhile owns the page; leave it alone.\n      if (state.busy || unknownRun !== pending) return;\n      const match = unknownRunJob(pending, items);\n      if (match) {\n        setRecoveryStatus(false);\n        followRecoveredJob(match, pending);\n        return true;\n      }\n      // Already finished: it is in the gallery like any other design.\n      const completedJob = unknownRunJob(pending, items, { succeeded: true });\n      if (completedJob) {\n        publishPreviewStart(completedJob.id, pending.previewAnalytics);\n        unknownRun = null;\n        loadGalleryHistory();\n        return true;\n      }\n      // It did start, and failed: say so (its card is in the gallery).\n      const failedJob = unknownRunJob(pending, items, { failed: true });\n      if (failedJob) {\n        publishPreviewStart(failedJob.id, pending.previewAnalytics);\n        unknownRun = null;\n        loadGalleryHistory();\n        showJobFailed(failedJob);\n        return true;\n      }\n    }\n    if (state.busy || unknownRun !== pending) return;\n    notify(reads\n      ? {\n        message: \"We don't see this design in your gallery yet. If it isn't there in a minute or two, it didn't start and you can try again.\",\n        key: 'unknown-run-missing',\n      }\n      : { message: \"We couldn't check your gallery right now. Please look again in a minute before trying again.\", key: 'unknown-run-unchecked' });\n  }\n\n  function logApiError(context, error) {\n    // Endpoint + status only: never the visitorToken, photo, prompt or full URL.\n    console.warn(`[realizeit] ${context} failed`, { endpoint: error?.endpoint || '', status: error?.status ?? null, network: Boolean(error?.network) });\n  }\n\n  // Maps a failed request to the handoff's popup table. `context` is the\n  // shopper action that failed: 'upload' | 'create' | 'options'.\n  function showApiError(context, error, { retry = null } = {}) {\n    logApiError(context, error);\n    const status = error?.status;\n    // The photo picker lives in the Create view: go there first.\n    const reupload = { label: 'Upload again', primary: true, run: () => { switchView('create'); el.file?.click(); } };\n    const retryAction = retry ? { label: 'Retry', primary: true, run: retry } : null;\n    if (context === 'upload') {\n      if (status === 400) return notify({ message: 'Please choose a PNG, JPG, or WebP photo under 20 MB and confirm you can use it.', actions: [reupload] });\n      if (status === 413) return notify({ message: 'That photo is too large. Please choose one under 20 MB.', actions: [reupload] });\n      // Upload-time content check (backend 2026-10-09): the photo was declined; the\n      // style and note stay, the shopper picks another photo. Server text is plain text.\n      if (status === 422) {\n        const serverText = typeof error.body?.error === 'string' ? error.body.error.trim() : '';\n        return notify({\n          message: serverText || \"We can't use that photo. Please choose a different one.\",\n          hint: error.alternatives?.length ? 'You could try:' : '',\n          alternatives: error.alternatives,\n          actions: [reupload],\n        });\n      }\n      if (status === 429) return notify({ message: 'Uploads are busy right now. Please try again shortly.', actions: retryAction ? [retryAction] : [] });\n      // A dropped connection or a stall. Not \"refresh\": that loses the photo,\n      // and Retry sends the same one again.\n      return notify({\n        message: \"Your photo didn't finish uploading. Please check your connection and try again.\",\n        actions: retryAction ? [retryAction, { ...reupload, primary: false, label: 'Choose another photo' }] : [reupload],\n      });\n    }\n    if (context === 'create') {\n      // The job may or may not exist. Never resend /create automatically.\n      if (isUnknownCreateOutcome(error)) {\n        return notify({\n          message: \"We couldn't confirm whether your design started. Please check your gallery before trying again.\",\n          actions: [{ label: 'Open gallery', primary: true, run: () => { void recoverUnknownRun(); } }],\n        });\n      }\n      // An HTML or empty 4xx (e.g. the proxy answering an unknown path) is not\n      // the app's own answer: draw no photo or style conclusion from its status.\n      if (error?.nonJson) return notify({ message: CONNECT_MESSAGE });\n      if (status === 400) {\n        if (!state.uploaded) return notify({ message: 'Please upload a photo before creating your design.', actions: [reupload] });\n        return notify({ message: 'This design option is unavailable. Please refresh and try again.', actions: [{ label: 'Refresh', primary: true, run: () => location.reload() }] });\n      }\n      if (status === 404 || status === 409) return notify({ message: \"We couldn't match that photo. Please upload it again.\", actions: [reupload] });\n      if (status === 422) {\n        const serverText = typeof error.body?.error === 'string' ? error.body.error.trim() : '';\n        return notify({\n          message: serverText || \"We can't use that photo or request. Please try a different one.\",\n          hint: error.alternatives?.length ? 'You could try:' : '',\n          alternatives: error.alternatives,\n          actions: [reupload],\n        });\n      }\n      if (status === 429) {\n        const quota = error.quota;\n        if (quota && typeof quota.remaining === 'number' && quota.remaining === 0) {\n          if (quota.kind === 'anonymous') {\n            return notify({ message: \"You've used today's free designs. Sign in for more.\", actions: [{ label: 'Sign in', primary: true, run: () => { location.href = loginUrlBack(); } }] });\n          }\n          return notify({ message: \"You've used today's free designs. Please try again tomorrow.\" });\n        }\n        if (quota && typeof quota.remaining === 'number') {\n          return notify({ message: 'The studio is busy. Please try again in a few minutes.', actions: retryAction ? [retryAction] : [] });\n        }\n        return notify({ message: 'Our image check is temporarily unavailable. Please try again in a few minutes.', actions: retryAction ? [retryAction] : [] });\n      }\n      return notify({ message: CONNECT_MESSAGE });\n    }\n    if (context === 'options') {\n      return notify({ message: \"Design options couldn't load. Please try again.\", actions: [{ label: 'Retry', primary: true, run: retry || (() => location.reload()) }] });\n    }\n    return notify({ message: CONNECT_MESSAGE });\n  }\n\n  function loginUrlBack() {\n    // /account/login keeps return_url (Shopify passes it on as return_to), so\n    // the shopper comes back to this product after signing in.\n    const back = encodeURIComponent(`${location.pathname}${location.search}`);\n    const join = LOGIN_URL.includes('?') ? '&' : '?';\n    return `${LOGIN_URL}${join}return_url=${back}`;\n  }\n\n  // A finished job that failed is an HTTP 200 with status \"failed\".\n  function showJobFailed(job) {\n    const serverText = typeof job?.error === 'string' ? job.error.trim() : '';\n    const refused = job?.reason === 'content_refused';\n    notify({\n      message: serverText || 'Something went wrong building your figurine. Please try again.',\n      hint: refused ? 'Please try a different photo.' : '',\n      actions: refused ? [{ label: 'Upload again', primary: true, run: () => { switchView('create'); el.file?.click(); } }] : [],\n      key: `job-failed-${job?.id || ''}`,\n    });\n  }\n\n  // The page shows pose / style cards the shopper can pick from (a fixed style without poses has none).\n  function hasCardChoice() {\n    return !FIXED_STYLE || (fixedStyle.ready && fixedStyle.usesPoses);\n  }\n\n  function refreshCta() {\n    // Pet requires a chosen pose; People and TRPG require a chosen style. Dave, 2026-10-28: the\n    // button is not greyed out while the shopper can act on it. With no photo it opens the photo\n    // picker, with no pose / style it brings the cards into view (generate()); it is only grey\n    // while a photo uploads, while a design is being made, or when nothing can be chosen.\n    el.go.disabled = state.busy || state.uploading\n      || (Boolean(state.uploaded) && !hasRequiredCreatorSelection() && !hasCardChoice());\n    // Drew, 2026-09-30: while the previous design (2D + 3D) is still being made,\n    // the button says so instead of only going grey.\n    const runBusy = Boolean(state.busy);\n    const goLabel = runBusy ? el.go.dataset.aicpGoBusyLabel : el.go.dataset.aicpGoLabel;\n    if (goLabel && el.go.textContent.trim() !== goLabel) el.go.textContent = goLabel;\n    el.go.classList.toggle('is-busy', runBusy);\n    root.querySelectorAll('[data-aicp-go-busy-note]').forEach((note) => { note.hidden = !runBusy; });\n    root.querySelectorAll('[data-aicp-go-note]').forEach((note) => { note.hidden = runBusy; });\n    el.styles.querySelectorAll('.aicp-style').forEach((card) => {\n      card.disabled = state.busy;\n    });\n    // Pose cards render into posesRow (outside el.styles) \u2014 disable them too.\n    root.querySelectorAll('.aicp-pose').forEach((card) => {\n      card.disabled = state.busy;\n    });\n    if (el.file) el.file.disabled = state.busy || state.uploading;\n    // The upload tile shows the same state (Drew, 2026-10-12: on phones the CTA\n    // is below the fold, so a tap on the tile looked like nothing happened).\n    el.drop?.classList.toggle('is-busy', runBusy);\n    root.querySelectorAll('[data-aicp-upload-busy]').forEach((badge) => { badge.hidden = !runBusy; });\n    // The \"choose a pose first\" line goes once a card is chosen.\n    const selectionHint = el.selectionStep?.querySelector('[data-aicp-selection-hint]');\n    if (selectionHint && hasRequiredCreatorSelection()) selectionHint.hidden = true;\n    syncStyleShowcase();\n    syncStyleNoteHint();\n    syncSecondPhoto();\n  }\n\n  /* Generate tapped with a photo but no pose / style (the button stays live, Dave\n     2026-10-28): bring the cards into view, pulse them and say why nothing started.\n     With the cards already on screen the tap used to show no sign at all (Drew,\n     2026-10-29). refreshCta hides the line once a card is chosen. */\n  let selectionNudgeTimer = 0;\n  function nudgeSelection() {\n    const step = el.selectionStep;\n    if (!step) return;\n    let hint = step.querySelector('[data-aicp-selection-hint]');\n    if (!hint) {\n      // No role=alert: the step is already an aria-live region, which reads the line once.\n      hint = document.createElement('p');\n      hint.className = 'aicp-selection-hint';\n      hint.setAttribute('data-aicp-selection-hint', '');\n      const heading = step.querySelector('.aicp-styles-heading');\n      if (heading) heading.after(hint);\n      else step.prepend(hint);\n    }\n    // Same word as the heading above the cards (syncSelectionLabel): Pose on pet and TTRPG pages.\n    const category = activeTabCategoryName();\n    const word = category === 'pet' || category === 'trpg' ? 'pose' : 'style';\n    hint.textContent = `Choose a ${word} first, then tap Generate.`;\n    hint.hidden = false;\n    step.classList.remove('is-nudged');\n    void step.offsetWidth; // restart the pulse on a second tap\n    step.classList.add('is-nudged');\n    clearTimeout(selectionNudgeTimer);\n    selectionNudgeTimer = setTimeout(() => step.classList.remove('is-nudged'), 1600);\n    step.scrollIntoView({ block: 'center', behavior: 'smooth' });\n  }\n\n  const STAGE_MASK_CARD_SETS = ['pet', 'ttrpg'];\n  const STAGE_MASK_CARD_SOURCES = {\n    pet: [\n      new URL('ai3d-mask-card-pet-beagle.png?stage-card-v=2', import.meta.url).href,\n      new URL('ai3d-mask-card-pet-bernese.png?stage-card-v=2', import.meta.url).href,\n    ],\n    ttrpg: [\n      new URL('ai3d-mask-card-ttrpg-dragon.png?stage-card-v=2', import.meta.url).href,\n      new URL('ai3d-mask-card-ttrpg-golem.png?stage-card-v=2', import.meta.url).href,\n    ],\n  };\n  let stageMaskCardSet = 'pet';\n  let stageMaskCardsTimer = 0;\n  let stageMaskCardsSwapTimer = 0;\n\n  // Lazy (2026-10-21): the mask only shows while a design is being made, so its pictures load when\n  // it first shows instead of competing with the first poster slide and the card pictures.\n  function hydrateStageMaskCards() {\n    const cards = el.stageMaskCards?.querySelectorAll('.aicp-g-stage__mask-card');\n    if (!cards || cards.length !== 2) return;\n    cards.forEach((card, index) => {\n      card.replaceChildren();\n      Object.entries(STAGE_MASK_CARD_SOURCES).forEach(([set, sources]) => {\n        const image = new Image(150, 150);\n        image.className = `aicp-g-stage__mask-card-image aicp-g-stage__mask-card-image--${set}`;\n        image.loading = 'lazy';\n        image.src = sources[index];\n        image.alt = '';\n        card.append(image);\n      });\n    });\n  }\n\n  hydrateStageMaskCards();\n\n  function cycleStageMaskCards() {\n    if (!el.stageMaskCards || el.stageMask?.hidden) return;\n    if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {\n      stageMaskCardsTimer = -1;\n      return;\n    }\n    // Swap only after both cards have fully faded out (88% of the cycle),\n    // leaving the next pair ready before the following expansion begins.\n    stageMaskCardsSwapTimer = window.setTimeout(() => {\n      const choices = STAGE_MASK_CARD_SETS.filter((set) => set !== stageMaskCardSet);\n      stageMaskCardSet = choices[Math.floor(Math.random() * choices.length)] || stageMaskCardSet;\n      el.stageMaskCards.dataset.cardSet = stageMaskCardSet;\n    }, 4800);\n    stageMaskCardsTimer = window.setTimeout(cycleStageMaskCards, 5400);\n  }\n\n  function stopStageMaskCards() {\n    if (stageMaskCardsTimer > 0) window.clearTimeout(stageMaskCardsTimer);\n    if (stageMaskCardsSwapTimer > 0) window.clearTimeout(stageMaskCardsSwapTimer);\n    stageMaskCardsTimer = 0;\n    stageMaskCardsSwapTimer = 0;\n  }\n\n  /* Mask over the 2D stage while generating: title + one-line phase\n     description + progress bar. show=true starts the 2D phase; pct (0-100)\n     and desc drive the bar/label; show=false clears it. */\n  function setStageMask(show, pct, desc) {\n    if (!el.stageMask) return;\n    el.stageMask.hidden = !show;\n    if (!show) {\n      stopStageMaskCards();\n      return;\n    }\n    if (!stageMaskCardsTimer) cycleStageMaskCards();\n    if (el.stageMaskDesc) {\n      el.stageMaskDesc.textContent = desc || 'Turning your photo into a design \u2014 this usually takes a minute.';\n    }\n    if (el.stageMaskBar) {\n      el.stageMaskBar.style.width = `${Math.max(0, Math.min(100, Math.round(pct || 0)))}%`;\n    }\n    if (el.stageMaskTrack) {\n      el.stageMaskTrack.setAttribute('aria-valuenow', String(Math.round(pct || 0)));\n    }\n  }\n\n  /* Compact, non-blocking progress shown above the visible 2D result while its\n     3D model is generated, downloaded and decoded: the frame's has-stage-progress\n     makes room for it under the 2D/3D pills and fits the 2D below, so nothing\n     covers the design (Drew 2026-10-30, after JuJuBit; it sat over the bottom of\n     the 2D before).\n     `spinner`: a design made earlier only loads a finished file, so it gets a grey\n     spinner and one line of text, no bar or percentage (Drew, 2026-10-29). */\n  function setStage3DProgress(show, pct, desc, { spinner = false } = {}) {\n    if (!el.stageLoading) return;\n    el.stageLoading.hidden = !show;\n    el.stageLoading.parentElement?.classList.toggle('has-stage-progress', Boolean(show));\n    el.stageLoading.classList.toggle('is-spinner', Boolean(show && spinner));\n    if (!show) {\n      el.stageLoadingBar?.style.setProperty('--aicp-stage-progress', '0%');\n      return;\n    }\n    if (spinner) {\n      if (el.stageLoadingText) el.stageLoadingText.textContent = desc || 'Loading your model\u2026';\n      return;\n    }\n    const percent = Math.max(0, Math.min(100, Math.round(pct || 0)));\n    el.stageLoadingBar?.style.setProperty('--aicp-stage-progress', `${percent}%`);\n    el.stageLoadingBar?.setAttribute('aria-valuenow', String(percent));\n    el.stageLoadingBar?.setAttribute('aria-valuetext', `${percent}%`);\n    if (el.stageLoadingText) {\n      el.stageLoadingText.textContent = desc || 'Building your 3D model\u2026';\n    }\n    if (el.stageLoadingValue) el.stageLoadingValue.textContent = `${percent}%`;\n  }\n\n  /* The synchronous 2D renderer only exposes a phase marker, so its bar uses a\n     time-based estimate. The 3D bar eases toward 88% on a curve sized to the 3D\n     step since backend 396a881 dropped Meshy's remesh (measured 2026-10-29: about\n     70\u2013120 s): ~48% at 30 s, ~70% at 60 s, ~79% at 90 s, ~84% at 120 s. Provider\n     progress may push it further, never past 88%. The final 88\u2013100% is the real\n     GLB download, decode and first completed render. (It used to reach 55% in\n     30 s and then crawl to 70% over two minutes, sized to the ~3-minute remesh\n     build, so the bar looked stuck and then jumped.) */\n  let stageGenerationProgress = null;\n  const THREE_D_ESTIMATE_CEILING = 88;\n  const THREE_D_ESTIMATE_TAU_MS = 38000;\n\n  function normalizedJobProgress(value) {\n    const numeric = Number(value);\n    if (!Number.isFinite(numeric)) return 0;\n    return Math.max(0, Math.min(100, numeric));\n  }\n\n  function generationPhasePercent(kind, jobProgress) {\n    const raw = normalizedJobProgress(jobProgress);\n    if (kind === '2d') return Math.min(92, (raw / 8) * 92);\n    return Math.max(0, Math.min(88, ((raw - 8) / 92) * 88));\n  }\n\n  function stopStageGenerationProgress() {\n    if (stageGenerationProgress) cancelAnimationFrame(stageGenerationProgress.frame);\n    stageGenerationProgress = null;\n    setStageMask(false);\n    setStage3DProgress(false);\n  }\n\n  function startStageGenerationProgress(kind, jobProgress = 0, description = '') {\n    stopStageGenerationProgress();\n    const startedAt = performance.now();\n    const state = {\n      kind,\n      description,\n      serverPercent: generationPhasePercent(kind, jobProgress),\n      frame: 0,\n      lastPaint: 0,\n    };\n    stageGenerationProgress = state;\n\n    const advance = (now) => {\n      if (stageGenerationProgress !== state) return;\n      if (now - state.lastPaint >= 200) {\n        const elapsed = now - startedAt;\n        const ceiling = kind === '2d' ? 92 : THREE_D_ESTIMATE_CEILING;\n        const estimate = kind === '2d'\n          ? ceiling * (1 - Math.exp(-elapsed / 25000))\n          : ceiling * (1 - Math.exp(-elapsed / THREE_D_ESTIMATE_TAU_MS));\n        const percent = Math.max(state.serverPercent, estimate);\n        if (kind === '2d') {\n          setStage3DProgress(false);\n          setStageMask(true, Math.min(ceiling, percent), state.description);\n        } else {\n          setStageMask(false);\n          setStage3DProgress(true, Math.min(88, percent), state.description);\n        }\n        state.lastPaint = now;\n      }\n      state.frame = requestAnimationFrame(advance);\n    };\n    state.frame = requestAnimationFrame(advance);\n  }\n\n  function updateStageGenerationProgress(kind, jobProgress, description) {\n    if (!stageGenerationProgress || stageGenerationProgress.kind !== kind) {\n      startStageGenerationProgress(kind, jobProgress, description);\n      return;\n    }\n    stageGenerationProgress.serverPercent = Math.max(\n      stageGenerationProgress.serverPercent,\n      generationPhasePercent(kind, jobProgress),\n    );\n    if (description) stageGenerationProgress.description = description;\n  }\n\n  function showQuota(quota) {\n    if (!quota || typeof quota.remaining !== 'number') return;\n    el.quota.hidden = false;\n    el.quota.textContent = quota.remaining === 1\n      ? '1 free design left today'\n      : `${quota.remaining} free designs left today`;\n  }\n\n  function rememberJob(jobId, style, styleLabel, uploaded, { instructions = '', pose = null } = {}) {\n    try {\n      localStorage.setItem(JOB_KEY, JSON.stringify({ jobId, style, styleLabel, uploaded, instructions, pose, at: Date.now() }));\n    } catch {}\n  }\n\n  function forgetJob() {\n    try {\n      localStorage.removeItem(JOB_KEY);\n    } catch {}\n  }\n\n  function rememberedJob() {\n    try {\n      const saved = JSON.parse(localStorage.getItem(JOB_KEY) || 'null');\n      if (!saved?.jobId) return null;\n      if (Date.now() - saved.at > JOB_MEMORY_MS) {\n        forgetJob();\n        return null;\n      }\n      return saved;\n    } catch {\n      return null;\n    }\n  }\n\n  /* ---------- category tabs (PET / TRPG / PEOPLE) ----------\n     The /styles API carries no category field, so cards are classified by\n     their style name: \"trpg\" \u2192 TRPG, names containing \"pet\" \u2192 PET,\n     everything else \u2192 PEOPLE. Update categoryOf if the backend ever adds a\n     real category field. */\n\n  function categoryOf(style) {\n    const declaredCategory = String(style.category || style.categoryName || '').toLowerCase();\n    if (['pet', 'trpg', 'ttrpg', 'people'].includes(declaredCategory)) {\n      return declaredCategory === 'ttrpg' ? 'trpg' : declaredCategory;\n    }\n    const name = (style.name || '').toLowerCase();\n    if (name === 'trpg' || name.includes('trpg') || name.includes('ttrpg')) return 'trpg';\n    if (name.includes('pet')) return 'pet';\n    return 'people';\n  }\n\n  function activeCategory() {\n    return INITIAL_CAT;\n  }\n\n  const characterMode = root.querySelector('[data-aicp-character-mode]');\n  const characterModeLabel = root.querySelector('[data-aicp-character-mode-label]');\n  const characterModeSoloIcon = characterMode?.querySelector('.aicp-styles-heading__mode-icon--solo');\n  const characterModeDuoIcon = characterMode?.querySelector('.aicp-styles-heading__mode-icon--duo');\n  if (characterMode) {\n    characterMode.addEventListener('click', () => {\n      const duo = characterMode.getAttribute('aria-pressed') !== 'true';\n      const soloLabel = characterMode.dataset.aicpSoloLabel || 'Solo';\n      const duoLabel = characterMode.dataset.aicpDuoLabel || 'Duo';\n      characterMode.classList.toggle('is-duo', duo);\n      characterMode.setAttribute('aria-pressed', String(duo));\n      characterMode.setAttribute('aria-label', duo ? `Choose ${soloLabel}` : `Choose ${duoLabel}`);\n      if (characterModeSoloIcon) characterModeSoloIcon.hidden = duo;\n      if (characterModeDuoIcon) characterModeDuoIcon.hidden = !duo;\n      if (characterModeLabel) characterModeLabel.textContent = duo ? duoLabel : soloLabel;\n    });\n  }\n\n  // Solo / Duo choice above the upload step (rendered only when the product has a Characters option).\n  // Picking one filters the style cards and swaps the upload hint and the notes placeholder; while no design\n  // is on stage it also moves the selected variant (price, ?variant=) to that mode.\n  const charsChoiceButtons = [...root.querySelectorAll('[data-aicp-chars-choice]')];\n  const galleryCharsButtons = [...root.querySelectorAll('[data-aicp-gallery-chars]')];\n  // Pill in the upload canvas (Drew 2026-10-20, replaces the two tiles): one tap switches Solo <-> Duo; the\n  // kicker \"from\" price follows the mode.\n  const charsPill = root.querySelector('[data-aicp-chars-toggle]');\n  const charsPillLabel = charsPill?.querySelector('[data-aicp-chars-pill-label]');\n  const charsKickerPrice = root.querySelector('[data-aicp-kicker-duo]');\n  const charsDefaultUploadTitle = el.dropTitle?.textContent || '';\n  const charsDefaultUploadSub = el.dropSub?.textContent || '';\n  const charsDefaultNotesPlaceholder = el.notes?.getAttribute('placeholder') || '';\n  const sameCharacters = (a, b) => normalizeShopifyOptionValue(a) === normalizeShopifyOptionValue(b);\n  function markCharacters(buttons, value, attr) {\n    buttons.forEach((button) => {\n      const active = sameCharacters(button.getAttribute(attr), value);\n      button.classList.toggle('is-active', active);\n      button.setAttribute('aria-pressed', String(active));\n    });\n  }\n  function setCreateCharacters(value, { syncVariant = true } = {}) {\n    if (!productCharactersOption) return;\n    createCharacters = charactersValue(value);\n    markCharacters(charsChoiceButtons, createCharacters, 'data-aicp-chars-choice');\n    const pillDuo = !!charsPill && sameCharacters(createCharacters, charsPill.dataset.duo);\n    if (charsPill) {\n      charsPill.classList.toggle('is-duo', pillDuo);\n      charsPill.setAttribute('aria-pressed', String(pillDuo));\n      if (charsPillLabel) charsPillLabel.textContent = createCharacters;\n    }\n    if (charsKickerPrice) charsKickerPrice.textContent = pillDuo ? charsKickerPrice.dataset.aicpKickerDuo : charsKickerPrice.dataset.aicpKickerSolo;\n    const chosen = charsChoiceButtons.find((button) => sameCharacters(button.dataset.aicpCharsChoice, createCharacters))\n      || (pillDuo ? charsPill : null);\n    if (el.dropTitle) el.dropTitle.textContent = chosen?.dataset.aicpUploadTitle || charsDefaultUploadTitle;\n    if (el.dropSub && !state.uploaded && !state.uploading) el.dropSub.textContent = chosen?.dataset.aicpUploadSub || charsDefaultUploadSub;\n    if (el.notes) el.notes.setAttribute('placeholder', chosen?.dataset.aicpNotesPlaceholder || charsDefaultNotesPlaceholder);\n    applyCharactersFilter();\n    if (syncVariant && variantLogicReady && !galleryStage.item) {\n      selectedProductOptions.set('characters', createCharacters);\n      syncProductVariant();\n    }\n  }\n  if (productCharactersOption) {\n    charsChoiceButtons.forEach((button) => button.addEventListener('click', () => {\n      if (state.busy) return;\n      setCreateCharacters(button.dataset.aicpCharsChoice || '');\n    }));\n    charsPill?.addEventListener('click', (event) => {\n      event.preventDefault();\n      if (state.busy) return;\n      setCreateCharacters(sameCharacters(createCharacters, charsPill.dataset.duo) ? charsPill.dataset.solo : charsPill.dataset.duo);\n    });\n    // Order panel tiles: show which mode the staged design was made in; the other tile goes back to Create\n    // in that mode (a Solo design cannot be bought as Duo, or the other way round).\n    galleryCharsButtons.forEach((button) => button.addEventListener('click', () => {\n      const value = button.dataset.aicpGalleryChars || '';\n      const staged = galleryStage.item?.style ? charactersForStyle(galleryStage.item.style) : '';\n      if (staged && sameCharacters(value, staged)) return;\n      switchView('create');\n      setCreateCharacters(value);\n    }));\n    setCreateCharacters(createCharacters, { syncVariant: false });\n  }\n  // Linked Solo / Duo (2026-10-21, Mini Me: two products, see the section): the pill and the order-panel tile are\n  // links to the other product. Like the pill above they do nothing while a design is being made, since leaving the\n  // page would hide its progress.\n  root.querySelectorAll('.aicp-chars-pill--link, .aicp-g-char--link').forEach((link) => link.addEventListener('click', (event) => {\n    if (!state.busy && !state.uploading) return;\n    event.preventDefault();\n    notify({\n      message: 'Your last design is still being made. You can switch between Solo and Duo as soon as its 3D model is ready.',\n      actions: [{ label: 'See progress', primary: true, run: () => switchView('gallery') }],\n      key: 'chars-busy',\n    });\n  }));\n\n  function applyCategoryFilter() {\n    const category = activeCategory();\n    if (!category) return;\n    let visible = 0;\n    el.styles.querySelectorAll('.aicp-style').forEach((card) => {\n      const show = card.dataset.cat === category;\n      card.hidden = !show;\n      if (show) visible += 1;\n    });\n    let empty = el.styles.querySelector('[data-aicp-styles-empty]');\n    if (visible === 0) {\n      if (!empty) {\n        empty = document.createElement('p');\n        empty.className = 'aicp-muted';\n        empty.dataset.aicpStylesEmpty = '';\n        empty.textContent = 'No styles in this category yet.';\n        el.styles.append(empty);\n      }\n      empty.hidden = false;\n    } else if (empty) {\n      empty.hidden = true;\n    }\n  }\n\n  /* Navigate to a category. A merchant-set real URL wins (the tabs and the PC\n     select both carry data-url); otherwise fall back to a same-page refresh\n     with ?cat= so per-category URLs work without any further config. */\n  function goToCategory(cat, url) {\n    if (url && url !== '#' && !/^#/.test(url)) {\n      location.href = url;\n      return;\n    }\n    const params = new URLSearchParams(location.search);\n    params.set('cat', cat);\n    params.delete('view'); // landing back in the create step\n    params.delete('aicp_view');\n    params.delete('aicp_job');\n    location.search = params.toString();\n  }\n\n  el.catTabs.forEach((tab) => {\n    tab.addEventListener('click', (event) => {\n      const href = tab.getAttribute('href');\n      // Merchant-set real link \u2014 let the browser navigate normally.\n      if (href && href !== '#' && !/^#/.test(href)) return;\n      event.preventDefault();\n      goToCategory(tab.dataset.aicpCat, href);\n    });\n  });\n\n  /* PC category pickers (create column + gallery column). Custom controls:\n     a button toggles a listbox; choosing an option navigates, so the page\n     reloads with the new ?cat= and both pickers stay in sync. */\n  function closeCatSelect(picker) {\n    picker.classList.remove('is-open');\n    picker.querySelector('[data-aicp-cat-toggle]')?.setAttribute('aria-expanded', 'false');\n    const list = picker.querySelector('[data-aicp-cat-list]');\n    if (list) list.hidden = true;\n  }\n\n  function openCatSelect(picker) {\n    // Only one panel at a time (create + gallery both exist in the DOM).\n    el.catSelects.forEach((other) => {\n      if (other !== picker) closeCatSelect(other);\n    });\n    picker.classList.add('is-open');\n    picker.querySelector('[data-aicp-cat-toggle]')?.setAttribute('aria-expanded', 'true');\n    const list = picker.querySelector('[data-aicp-cat-list]');\n    if (list) list.hidden = false;\n  }\n\n  el.catSelects.forEach((picker) => {\n    const toggle = picker.querySelector('[data-aicp-cat-toggle]');\n    const list = picker.querySelector('[data-aicp-cat-list]');\n    const valueEl = picker.querySelector('[data-aicp-cat-value]');\n    if (!toggle || !list || !valueEl) return;\n\n    // Reflect the current selection in the closed button.\n    const syncLabel = () => {\n      const current = list.querySelector('.aicp-cat-select__option[aria-selected=\"true\"]')\n        || list.querySelector('.aicp-cat-select__option');\n      if (current) valueEl.textContent = current.dataset.label || current.textContent.trim();\n    };\n    syncLabel();\n\n    toggle.addEventListener('click', () => {\n      if (picker.classList.contains('is-open')) closeCatSelect(picker);\n      else openCatSelect(picker);\n    });\n\n    list.querySelectorAll('.aicp-cat-select__option').forEach((option) => {\n      option.addEventListener('click', () => {\n        closeCatSelect(picker);\n        goToCategory(option.dataset.value, option.dataset.url);\n      });\n    });\n  });\n\n  // Click outside / Escape closes any open picker.\n  document.addEventListener('click', (event) => {\n    el.catSelects.forEach((picker) => {\n      if (!picker.contains(event.target)) closeCatSelect(picker);\n    });\n  });\n  document.addEventListener('keydown', (event) => {\n    if (event.key === 'Escape') el.catSelects.forEach(closeCatSelect);\n  });\n\n  /* ---------- styles (single-select) ---------- */\n\n  // `optimistic`: the product's Shopify list drawn at boot, before /styles answers (2026-10-21).\n  function renderStyles(styles, { optimistic = false } = {}) {\n    // The cards already here (drawn with the page, or by an earlier call) hand their pictures\n    // to the new cards, so taking them over never makes a picture blink.\n    const pictures = new Map();\n    el.styles.querySelectorAll('.aicp-style__img > img').forEach((img) => {\n      const src = img.getAttribute('src');\n      if (src && !pictures.has(src)) pictures.set(src, img);\n    });\n    el.styles.textContent = '';\n    el.styles.dataset.category = activeCategory();\n    const category = activeCategory();\n    if (HAS_STYLE_LIST) {\n      // Cards from the product's Shopify list, in that order. A name the backend\n      // does not offer is dropped rather than quietly generating another style.\n      const available = new Set(styles.map((style) => style.name));\n      styles = STYLE_LIST\n        .filter((entry) => optimistic || available.has(entry.name))\n        .map((entry) => ({ id: entry.name, name: entry.name, label: entry.label || entry.name, category: entry.category || category, image: entry.image || '', characters: entry.characters || '' }));\n    } else if (category === 'trpg') {\n    // The API exposes one valid `trpg` generation style, while the storefront\n    // offers its seven pose thumbnails as choices. Keep their UI identities\n    // separate, but submit the server-supported style value for every choice.\n      styles = [\n        ['trpg-default', 'TRPG'],\n        ['trpg-neutral-stance', 'Neutral Stance'],\n        ['trpg-battle-ready', 'Battle Ready'],\n        ['trpg-casting-spell', 'Casting Spell'],\n        ['trpg-ranged-aim', 'Ranged Aim'],\n        ['trpg-dynamic-attack', 'Dynamic Attack'],\n        ['trpg-victory-triumph', 'Victory Triumph'],\n      ].map(([id, label]) => ({ id, name: 'trpg', label, category: 'trpg' }));\n    } else if (category === 'people') {\n      // Each card names its exact backend style. Since 2026-10-06 /styles also\n      // lists every Shopify-configured style (keychains, Halloween looks\u2026), so\n      // name matching by pattern picked e.g. keychain-soft-curls-chibi-head for\n      // the Soft Chibi card. An exact name that is missing drops the card rather\n      // than silently generating another style (no first-style fallback).\n      // Soft Chibi is the backend's pet-chibi (tested on a person 2026-09-30).\n      const available = new Set(styles.map((style) => style.name));\n      const serverStyleFor = (name) => (available.has(name) ? name : '');\n      styles = [\n        ['people-classic-realism', 'Classic Realism', 'anime-1-7', 'realism'],\n        ['people-minix-style', 'MINIX Style', 'minix-style', 'minix'],\n        ['people-classic-chibi', 'Classic Chibi', 'classic-chibi', 'classic-chibi'],\n        ['people-soft-chibi', 'Soft Chibi', 'soft-chibi', 'pet-chibi'],\n        ['people-mini-pop', MINI_POP.label, 'funko-pop', 'people-block-head', MINI_POP.directive],\n        ['people-free-style', 'Free Style', 'free-style', 'human-prototype'],\n        ['people-anime-1-7', 'Anime 1:7', 'anime-1-7-alt', 'anime-1-7'],\n      ].map(([id, label, imageKey, serverName, directive]) => {\n        // A card with a directive runs Free Style plus that look.\n        const name = serverName ? serverStyleFor(serverName) : '';\n        const fallback = !name && directive ? serverStyleFor('human-prototype') : '';\n        return {\n          id,\n          name: name || fallback,\n          label,\n          imageKey,\n          category: 'people',\n          directive: fallback ? directive : '',\n        };\n      }).filter((style) => style.name);\n    }\n    if (!FIXED_STYLE) pageStyleNames = new Set(styles.map((style) => style.name).filter(Boolean));\n    if (!styles.length) {\n      const p = document.createElement('p');\n      p.className = 'aicp-muted';\n      p.textContent = 'No styles available right now.';\n      el.styles.append(p);\n      return;\n    }\n    // Keep artwork maps category-specific: the People artwork must not alter\n    // the existing PET-only creation flow until People is deliberately enabled.\n    const readStyleImages = (selector) => {\n      const imageMapEl = root.querySelector(selector);\n      if (!imageMapEl) return {};\n      try { return JSON.parse(imageMapEl.textContent) || {}; } catch { return {}; }\n    };\n    const styleImages = category === 'people'\n      ? readStyleImages('[data-aicp-people-style-images]')\n      : category === 'trpg'\n        ? readStyleImages('[data-aicp-trpg-style-images]')\n        : readStyleImages('[data-aicp-pet-style-images]');\n    const styleImageFor = (style, index) => {\n      const direct = styleImages[style.imageKey || style.name];\n      if (direct) return direct;\n      if (category === 'trpg') return styleImages[index % styleImages.length] || '';\n      if (category !== 'people') return '';\n      const name = `${style.name || ''} ${style.label || ''}`.toLowerCase();\n      const key = [\n        ['soft', 'soft-chibi'],\n        ['minix', 'minix-style'],\n        ['free', 'free-style'],\n        ['funko', 'funko-pop'],\n        ['anime', 'anime-1-7'],\n        ['classic', 'classic-chibi'],\n      ].find(([token]) => name.includes(token))?.[1];\n      return key ? styleImages[key] || '' : '';\n    };\n    // Hide styles with no artwork anywhere (API image nor theme asset) \u2014 blank\n    // cards look broken. They reappear automatically once an image exists.\n    styles = styles.filter((s, index) => {\n      const hasApiImage = !!(s.image || s.imageUrl || s.image_url || s.thumbnail || s.preview);\n      return hasApiImage || styleImageFor(s, index);\n    });\n    if (!styles.length) {\n      const p = document.createElement('p');\n      p.className = 'aicp-muted';\n      p.textContent = 'No styles available right now.';\n      el.styles.append(p);\n      return;\n    }\n    for (const [styleIndex, style] of styles.entries()) {\n      const displayLabel = style.label || style.name;\n      const button = document.createElement('button');\n      button.type = 'button';\n      button.className = 'aicp-style';\n      button.dataset.style = style.id || style.name;\n      // The server name lets history designs find their card label; a\n      // directive card shares Free Style's name, so it stays out of that.\n      if (style.id && !style.directive) button.dataset.styleName = style.name;\n      button.dataset.cat = categoryOf(style);\n\n      const frame = document.createElement('span');\n      frame.className = 'aicp-style__img';\n      const src = style.image || style.imageUrl || style.image_url || style.thumbnail || style.preview || styleImageFor(style, styleIndex);\n      if (src) {\n        const img = pictures.get(src) || document.createElement('img');\n        pictures.delete(src);\n        // A card of the other Solo / Duo mode starts hidden: its picture waits until the card shows\n        // (2026-10-21; on a slow phone the 7 Duo pictures cost ~100 KB right after the page loads).\n        const otherMode = productCharactersOption\n          && normalizeShopifyOptionValue(charactersForStyle(style.name)) !== normalizeShopifyOptionValue(createCharacters);\n        img.loading = otherMode ? 'lazy' : 'eager';\n        if (img.getAttribute('src') !== src) img.src = src;\n        img.alt = '';\n        frame.append(img);\n      } else {\n        const ph = document.createElement('span');\n        ph.className = 'aicp-ph';\n        frame.append(ph);\n      }\n\n      const check = document.createElement('span');\n      check.className = 'aicp-style__check';\n      if (category === 'people' && root.dataset.aicpPeopleStyleCheckUrl) {\n        const checkImage = document.createElement('img');\n        checkImage.src = root.dataset.aicpPeopleStyleCheckUrl;\n        checkImage.alt = '';\n        check.append(checkImage);\n      } else {\n        check.innerHTML = '<svg viewBox=\"0 0 16 16\" width=\"10\" height=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m3 8.4 3.2 3.2L13 5\"/></svg>';\n      }\n\n      button.append(frame, check);\n\n      // Label sits OUTSIDE the card (below it) per the new design \u2014 the whole\n      // card+label unit stays clickable via a wrapper.\n      const name = document.createElement('span');\n      name.className = 'aicp-style__name';\n      name.textContent = displayLabel;\n\n      const unit = document.createElement('div');\n      unit.className = 'aicp-style-unit';\n      if (productCharactersOption) unit.dataset.characters = charactersForStyle(style.name);\n      unit.append(button, name);\n\n      // A redraw keeps the card that is chosen (the Create page preselects one at boot).\n      const chosen = state.styles[0]?.id === (style.id || style.name);\n      button.classList.toggle('is-selected', chosen);\n      button.setAttribute('role', 'radio');\n      button.setAttribute('aria-checked', String(chosen));\n      button.addEventListener('click', () => {\n        // a card of the other Solo/Duo mode is hidden and must not be picked (e.g. by the arrow keys)\n        if (state.busy || (productCharactersOption && unit.hidden)) return;\n        // A required radio choice: selecting a new card replaces the old one;\n        // selecting the current card intentionally keeps it selected.\n        const styleId = style.id || style.name;\n        state.styles = [{ id: styleId, name: style.name, label: displayLabel, directive: style.directive || '' }];\n        el.styles.querySelectorAll('.aicp-style').forEach((other) => {\n          const selected = state.styles[0]?.id === other.dataset.style;\n          other.classList.toggle('is-selected', selected);\n          other.setAttribute('aria-checked', String(selected));\n        });\n        if (el.notes) el.notes.maxLength = noteLimit();\n        refreshCta();\n      });\n      el.styles.append(unit);\n    }\n    // a chosen style that the redraw dropped (the backend no longer offers it) is no longer chosen\n    if (state.styles.length && !FIXED_STYLE && !el.styles.querySelector('.aicp-style.is-selected')) {\n      state.styles = [];\n      refreshCta();\n    }\n    applyCharactersFilter();\n  }\n\n  // Solo / Duo: show only the cards of the chosen mode; a selection that gets hidden is dropped, and a mode\n  // with a single card selects it. No-op on products without a Characters option.\n  function applyCharactersFilter() {\n    if (!productCharactersOption || !el.styles) return;\n    const units = [...el.styles.querySelectorAll('.aicp-style-unit')];\n    if (!units.length) return;\n    const visible = units.filter((unit) => {\n      const show = normalizeShopifyOptionValue(unit.dataset.characters || '') === normalizeShopifyOptionValue(createCharacters);\n      unit.hidden = !show;\n      return show;\n    });\n    const selectedId = state.styles[0]?.id;\n    if (selectedId && !visible.some((unit) => unit.querySelector('.aicp-style')?.dataset.style === selectedId)) {\n      state.styles = [];\n      el.styles.querySelectorAll('.aicp-style').forEach((card) => {\n        card.classList.remove('is-selected');\n        card.setAttribute('aria-checked', 'false');\n      });\n    }\n    if (!state.styles.length && visible.length === 1) visible[0].querySelector('.aicp-style')?.click();\n    refreshCta();\n  }\n\n  /* ---------- pose picker (\u00A73.3/3.4) ----------\n     A selected style supports poses when /categories lists its style with\n     poses === true. Single-select; \"default\" is returned by /poses and means\n     \"no pose\". The chosen pose rides on /create only for pose-capable styles. */\n\n  /* PET tab (poses === true): the pose list takes the style row's place \u2014\n     text-only, single-select, \"default\" means no pose. The create style is\n     the category record's own `style` field. */\n\n  let styleAreaSyncSeq = 0;\n\n  async function syncStyleArea() {\n    const seq = ++styleAreaSyncSeq;\n    const category = activeTabCategoryName();\n    const poseMode = styleAreaIsPoseMode();\n    if (seq !== styleAreaSyncSeq) return;\n    if (el.styles) el.styles.hidden = poseMode;\n    if (!poseMode) {\n      if (el.poses) el.poses.hidden = true;\n      if (el.posesRow) el.posesRow.textContent = '';\n      state.poses = [];\n      syncSelectionLabel();\n      refreshCta();\n      return;\n    }\n    if (el.poses) el.poses.hidden = false;\n    await renderPoseOptions(category);\n    if (seq !== styleAreaSyncSeq) return;\n    syncSelectionLabel();\n    refreshCta();\n  }\n\n  async function renderPoseOptions(category) {\n    if (!el.posesRow) return;\n    // Pose artwork exported from Figma (node 169:7871), mapped by pose name.\n    let poseImages = {};\n    const imageMapEl = root.querySelector('[data-aicp-pose-images]');\n    if (imageMapEl) {\n      try { poseImages = JSON.parse(imageMapEl.textContent) || {}; } catch {}\n    }\n    let poses = poseCache[category];\n    if (!poses) {\n      try {\n        // pet pages start this read with the page (the section's inline prefetch)\n        const body = await call(`/poses?category=${encodeURIComponent(category)}`, { prefetch: true });\n        poses = (Array.isArray(body.poses) ? body.poses : [])\n          .filter((pose) => pose?.name)\n          .map((pose) => ({ ...pose, label: pose.label || POSE_LABELS[pose.name] || pose.name }));\n        poseCache[category] = poses;\n      } catch (error) {\n        // Unknown category (404): no pose options. Anything else is a load\n        // failure \u2014 say so with a Retry instead of leaving the CTA locked.\n        poses = [];\n        if (error?.status === 404) poseCache[category] = poses;\n        else showApiError('options', error, { retry: () => { void syncStyleArea(); } });\n      }\n    }\n    // The cards already here (the pet poses come with the page since 2026-10-21) stay until the\n    // list is known; their picture frames move into the new cards, so nothing blinks. Cleared and\n    // rebuilt in one go (no await in between), so overlapping calls never double the row.\n    const frames = new Map();\n    el.posesRow.querySelectorAll('.aicp-pose').forEach((card) => {\n      const frame = card.querySelector('.aicp-style__img');\n      if (frame && card.dataset.poseName && !frames.has(card.dataset.poseName)) frames.set(card.dataset.poseName, frame);\n    });\n    el.posesRow.textContent = '';\n    if (!poses.length) {\n      if (el.poses) el.poses.hidden = true;\n      state.poses = [];\n      return;\n    }\n\n    // Preserve the one selected pose when the list is re-rendered.\n    const chosen = state.poses.filter((p) => p.category === category).map((p) => p.name);\n    for (const [poseIndex, pose] of poses.entries()) {\n      // Pose cards mirror the style-card layout: reserved image slot (artwork\n      // comes later) + label.\n      const card = document.createElement('button');\n      card.type = 'button';\n      card.className = 'aicp-style aicp-pose' + (chosen.includes(pose.name) ? ' is-selected' : '');\n      card.dataset.poseName = pose.name;\n      card.dataset.poseCategory = category;\n      card.setAttribute('role', 'radio');\n      card.setAttribute('aria-checked', String(chosen.includes(pose.name)));\n\n      const poseSrc = poseImages[pose.name]\n        || (category === 'ttrpg' ? poseImages.ttrpg?.[poseIndex] : '');\n      let frame = frames.get(pose.name);\n      frames.delete(pose.name);\n      if (!frame) {\n        frame = document.createElement('span');\n        frame.className = 'aicp-style__img';\n        const ph = document.createElement('span');\n        ph.className = 'aicp-ph';\n        if (poseSrc) {\n          if (category === 'pet' && pose.name !== 'default') {\n            // The reference art is a 3 \u00D7 3 sprite. A background crop is immune\n            // to the host theme's image width/height overrides.\n            frame.classList.add('aicp-pose__sprite');\n            frame.style.backgroundImage = `url(\"${poseSrc}\")`;\n          } else {\n            const img = document.createElement('img');\n            img.src = poseSrc;\n            img.alt = pose.label || pose.name;\n            img.loading = 'eager';\n            frame.append(img);\n          }\n        } else {\n          frame.append(ph);\n        }\n      }\n\n      const check = document.createElement('span');\n      check.className = 'aicp-style__check';\n      check.innerHTML = '<svg viewBox=\"0 0 16 16\" width=\"10\" height=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m3 8.4 3.2 3.2L13 5\"/></svg>';\n\n      card.append(frame, check);\n\n      // Label outside the card (below it) per the new design.\n      const name = document.createElement('span');\n      name.className = 'aicp-style__name';\n      name.textContent = pose.label || pose.name;\n\n      const unit = document.createElement('div');\n      unit.className = 'aicp-style-unit';\n      unit.append(card, name);\n\n      card.addEventListener('click', () => {\n        if (state.busy) return;\n        state.poses = [{ category, name: pose.name, label: pose.label || pose.name }];\n        el.posesRow.querySelectorAll('.aicp-pose').forEach((other) => {\n          const selected = other.dataset.poseName === pose.name;\n          other.classList.toggle('is-selected', selected);\n          other.setAttribute('aria-checked', String(selected));\n        });\n        refreshCta();\n      });\n      el.posesRow.append(unit);\n    }\n    // Drop poses from other categories / stale entries when re-rendering this tab.\n    state.poses = state.poses.filter((p) => p.category === category);\n  }\n\n  /* ---------- upload event: POST /upload (FormData, field \"image\") ---------- */\n\n  function showThumb(file) {\n    // NOTE: the cloud icon is an <svg>; SVGElement has no `hidden` property,\n    // so the attribute must be toggled explicitly.\n    if (el.dropIcon) el.dropIcon.setAttribute('hidden', '');\n    if (el.thumb && file) {\n      if (state.uploadedPreview) URL.revokeObjectURL(state.uploadedPreview);\n      state.uploadedPreview = URL.createObjectURL(file);\n      el.thumb.src = state.uploadedPreview;\n      el.thumb.hidden = false;\n    }\n    if (el.filename) {\n      el.filename.textContent = file ? file.name : '';\n      el.filename.hidden = !file;\n    }\n    if (el.dropTitle) el.dropTitle.hidden = Boolean(file);\n    if (el.dropSub) el.dropSub.hidden = Boolean(file);\n    if (el.dropFormats) el.dropFormats.hidden = Boolean(file);\n    if (el.replacePhoto) el.replacePhoto.hidden = true;\n    el.drop?.classList.remove('is-uploaded');\n    el.uploadPanel?.classList.remove('is-uploaded');\n  }\n\n  function clearPhoto() {\n    state.uploaded = null;\n    state.screeningId = null;\n    hidePhotoMatch();\n    if (el.file) el.file.value = '';\n    if (state.uploadedPreview) {\n      URL.revokeObjectURL(state.uploadedPreview);\n      state.uploadedPreview = '';\n    }\n    if (el.thumb) {\n      el.thumb.removeAttribute('src');\n      el.thumb.hidden = true;\n    }\n    if (el.filename) {\n      el.filename.textContent = '';\n      el.filename.hidden = true;\n    }\n    if (el.dropIcon) el.dropIcon.removeAttribute('hidden');\n    if (el.dropTitle) el.dropTitle.hidden = false;\n    if (el.dropSub) {\n      // Back to the section's own line: a failed upload must not leave \"Uploading your photo\u2026\".\n      el.dropSub.textContent = DROP_SUB_TEXT;\n      el.dropSub.hidden = false;\n    }\n    if (el.dropFormats) el.dropFormats.hidden = false;\n    if (el.replacePhoto) el.replacePhoto.hidden = true;\n    el.drop?.classList.remove('is-uploaded');\n    el.uploadPanel?.classList.remove('is-uploaded');\n    hideSelectionStep();\n    say('');\n    refreshCta();\n  }\n\n  // Phone photos are 3\u20135 MB and 4032 px or more on the long side, while the\n  // studio's 2D step never makes anything over 1536 px (measured 2026-10-14).\n  // A 2048 px JPEG keeps all of that at a fifth to an eighth of the bytes, so\n  // the upload fits its 30 s window on a weak connection. Small photos go as\n  // they are, and any failure here sends the original instead.\n  const UPLOAD_MAX_SIDE = 2048;\n  const UPLOAD_SHRINK_OVER_BYTES = 2 * 1024 * 1024;\n  const UPLOAD_JPEG_QUALITY = 0.9;\n  const UPLOAD_SHRINK_TIMEOUT_MS = 8000;\n\n  function loadPhoto(file) {\n    return new Promise((resolve, reject) => {\n      const url = URL.createObjectURL(file);\n      const img = new Image();\n      img.onload = () => { URL.revokeObjectURL(url); resolve(img); };\n      img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('The photo could not be decoded.')); };\n      img.src = url;\n    });\n  }\n\n  // The picker is accept=\"image/*\" so Android opens the gallery and camera\n  // rather than a file browser. It can then hand over a HEIC, or a JPEG with\n  // no type or no extension; anything the browser can decode is redrawn as a\n  // JPEG below, so what reaches /upload is always one the studio takes.\n  function isUploadReady(file) {\n    return /^image\\/(png|jpe?g|webp)$/i.test(file.type || '')\n      && /\\.(png|jpe?g|webp)$/i.test(file.name || '');\n  }\n\n  function unreadablePhoto() {\n    const error = new Error('That photo format could not be read.');\n    error.status = 400;\n    error.unreadable = true;\n    return error;\n  }\n\n  async function shrinkPhotoNow(file) {\n    const ready = isUploadReady(file);\n    // naturalWidth/Height follow the EXIF orientation and drawImage paints the\n    // photo upright, so the JPEG needs no orientation tag (and carries no EXIF).\n    let img;\n    try {\n      img = await loadPhoto(file);\n    } catch (error) {\n      // A supported photo that will not decode still goes as it is; one this\n      // browser cannot read (HEIC on Android Chrome) would only be refused.\n      if (ready) throw error;\n      throw unreadablePhoto();\n    }\n    const width = img.naturalWidth;\n    const height = img.naturalHeight;\n    const longSide = Math.max(width, height);\n    if (!longSide && !ready) throw unreadablePhoto();\n    if (!longSide || (ready && longSide <= UPLOAD_MAX_SIDE && file.size <= UPLOAD_SHRINK_OVER_BYTES)) return file;\n    const scale = Math.min(1, UPLOAD_MAX_SIDE / longSide);\n    const targetWidth = Math.max(1, Math.round(width * scale));\n    const targetHeight = Math.max(1, Math.round(height * scale));\n    const canvases = [];\n    const canvasOf = (w, h) => {\n      const canvas = document.createElement('canvas');\n      canvas.width = w;\n      canvas.height = h;\n      canvases.push(canvas);\n      return canvas;\n    };\n    try {\n      // Halve first while the photo is over twice the target: one big bilinear\n      // step would alias, and it keeps every canvas under iOS's area limit.\n      let source = img;\n      let sourceWidth = width;\n      let sourceHeight = height;\n      while (sourceWidth > targetWidth * 2) {\n        const step = canvasOf(Math.round(sourceWidth / 2), Math.round(sourceHeight / 2));\n        step.getContext('2d').drawImage(source, 0, 0, step.width, step.height);\n        source = step;\n        sourceWidth = step.width;\n        sourceHeight = step.height;\n      }\n      const canvas = canvasOf(targetWidth, targetHeight);\n      const ctx = canvas.getContext('2d');\n      // A transparent PNG would turn black in a JPEG; white is the studio's own backdrop.\n      ctx.fillStyle = '#fff';\n      ctx.fillRect(0, 0, targetWidth, targetHeight);\n      ctx.imageSmoothingQuality = 'high';\n      ctx.drawImage(source, 0, 0, targetWidth, targetHeight);\n      const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', UPLOAD_JPEG_QUALITY));\n      // A larger JPEG is still the one to send when the original is not one /upload takes.\n      if (!blob) {\n        if (ready) return file;\n        throw unreadablePhoto();\n      }\n      if (ready && blob.size >= file.size) return file;\n      const name = `${String(file.name || 'photo').replace(/\\.[^.]+$/, '')}.jpg`;\n      return new File([blob], name, { type: 'image/jpeg', lastModified: file.lastModified });\n    } finally {\n      // iOS keeps canvas memory until the size is zeroed.\n      canvases.forEach((canvas) => { canvas.width = 0; canvas.height = 0; });\n    }\n  }\n\n  // How the last shrinkPhoto() ended, for the upload events: converted, kept\n  // (already fine), timeout or error (both send the original instead).\n  let lastShrink = '';\n\n  async function shrinkPhoto(file) {\n    let timer;\n    lastShrink = '';\n    try {\n      const photo = await Promise.race([\n        shrinkPhotoNow(file),\n        new Promise((resolve) => { timer = setTimeout(() => { lastShrink = 'timeout'; resolve(file); }, UPLOAD_SHRINK_TIMEOUT_MS); }),\n      ]);\n      if (!lastShrink) lastShrink = photo === file ? 'kept' : 'converted';\n      return photo;\n    } catch (error) {\n      lastShrink = error?.unreadable ? 'unreadable' : 'error';\n      if (error?.unreadable) throw error;\n      return file;\n    } finally {\n      clearTimeout(timer);\n    }\n  }\n\n  // Why an upload failed, in the terms the doc's funnel asks about.\n  function uploadFailureReason(error) {\n    if (error?.unreadable) return 'unreadable';\n    if (error?.stalled) return `timeout_${error.stalled}`;\n    if (error?.aborted) return 'timeout';\n    if (error?.network) return 'network';\n    if (error?.status === 413) return 'too_large';\n    if (error?.status) return error.nonJson ? `http_${error.status}_nonjson` : `http_${error.status}`;\n    return 'other';\n  }\n\n  let uploadSeq = 0; // token: only the most recent pick may claim state.uploaded\n\n  async function upload(file) {\n    if (!file) return;\n    if (state.busy) {\n      notify({\n        message: 'Your last design is still being made. You can upload the next photo as soon as its 3D model is ready.',\n        actions: [{ label: 'See progress', primary: true, run: () => switchView('gallery') }],\n        key: 'upload-busy',\n      });\n      return;\n    }\n    if (state.uploading) return;\n    // The 20 MB limit is checked after shrinkPhoto(): a big phone photo gets under it.\n    // Only a declared non-image (a video, a document) is refused up front: an\n    // empty type or a HEIC gets its chance to be decoded and converted.\n    const fileFacts = {\n      fileType: file.type || 'none',\n      hasExt: /\\.[a-z0-9]{2,5}$/i.test(file.name || ''),\n      sizeBucket: sizeBucket(file.size),\n    };\n    clarityEvent('mythumi_upload_start', null, fileFacts);\n    if (file.type && !/^image\\//i.test(file.type)) {\n      clarityEvent('mythumi_upload_failed', null, { ...fileFacts, reason: 'not_image' });\n      notify({ message: 'Please choose a PNG, JPG, or WebP photo under 20 MB.', actions: [{ label: 'Choose another photo', primary: true, run: () => el.file?.click() }], key: 'upload-400' });\n      return;\n    }\n\n    const seq = ++uploadSeq;\n    state.uploaded = null;\n    state.screeningId = null;\n    state.uploading = true;\n    hidePhotoMatch();\n    hideSelectionStep();\n    AICP_TIMING.uploadStart = Date.now(); // AICP-TIMING: TOTAL starts at upload\n    AICP_TIMING.armTotal = false;         // AICP-TIMING: re-armed on CREATE\n    AICP_TIMING.totalLogged = false;      // AICP-TIMING\n    refreshCta();\n    showThumb(file);\n    // The proxy can hang on a bad network: show progress, and give up on\n    // silence (postUpload) rather than leaving the CTA disabled forever.\n    const showUploadStep = (text) => {\n      if (seq !== uploadSeq || !el.dropSub) return;\n      el.dropSub.textContent = text;\n      el.dropSub.hidden = false;\n    };\n    showUploadStep('Preparing your photo\u2026');\n    say('Uploading your photo\u2026');\n\n    try {\n      const photo = await shrinkPhoto(file);\n      if (photo.size > MAX_UPLOAD_BYTES) {\n        const tooLarge = new Error('That photo is too large.');\n        tooLarge.status = 413;\n        throw tooLarge;\n      }\n      const form = new FormData();\n      form.append('image', photo);\n      form.append('rightsConfirmed', 'true');\n      if (visitorToken()) form.append('visitorToken', visitorToken());\n      showUploadStep('Uploading your photo\u2026 0%');\n      const result = await postUpload(form, (progress) => {\n        showUploadStep(progress === 'checking'\n          ? 'Checking your photo\u2026'\n          : `Uploading your photo\u2026 ${Math.round(progress * 100)}%`);\n      });\n      // A newer pick superseded this request \u2014 drop the stale result instead\n      // of letting an out-of-order response win over the visible thumbnail.\n      if (seq !== uploadSeq) return;\n      if (!result.file) {\n        throw new Error('The photo upload could not be saved. Please add your photo again.');\n      }\n      state.uploaded = result.file;\n      state.screeningId = result.screeningId || null;\n      if (seq !== uploadSeq) return;\n      clarityEvent('mythumi_photo_uploaded', null, {\n        ...fileFacts,\n        shrink: lastShrink,\n        sentBucket: sizeBucket(photo.size),\n        ms: Date.now() - AICP_TIMING.uploadStart,\n      });\n      if (el.dropSub) {\n        el.dropSub.textContent = '';\n        el.dropSub.hidden = true;\n      }\n      if (el.replacePhoto) el.replacePhoto.hidden = false;\n      el.drop?.classList.add('is-uploaded');\n      el.uploadPanel?.classList.add('is-uploaded');\n      // Designs this photo already made in another browser (photoMatch, 2026-10-29).\n      showPhotoMatch(result.photoMatch, result.file);\n      // A fixed style that could not load (popup closed) gets another try now.\n      if (FIXED_STYLE && fixedStyle.error && !fixedStyle.ready) {\n        void (INITIAL_CAT === 'pet' && !styleCategoriesLoaded ? ensureStyleCategories() : Promise.resolve())\n          .then(() => initFixedStyle())\n          .then(() => syncStyleArea());\n      }\n      void revealSelectionStep();\n      say('');\n    } catch (error) {\n      if (seq !== uploadSeq) return;\n      clarityTag('mythumi_upload_fail_reason', uploadFailureReason(error));\n      clarityEvent('mythumi_upload_failed', null, {\n        ...fileFacts,\n        reason: uploadFailureReason(error),\n        shrink: lastShrink,\n        ms: Date.now() - AICP_TIMING.uploadStart,\n      });\n      clearPhoto();\n      if (error?.unreadable) {\n        notify({\n          message: \"This photo's format can't be opened in this browser. Please choose a JPG or PNG photo.\",\n          actions: [{ label: 'Choose another photo', primary: true, run: () => el.file?.click() }],\n          key: 'upload-unreadable',\n        });\n        return;\n      }\n      // 429 keeps the picked file for a manual retry; the rest re-pick a photo.\n      showApiError('upload', error, { retry: () => upload(file) });\n    } finally {\n      state.uploading = false;\n      refreshCta();\n    }\n    refreshCta();\n  }\n\n  el.file?.addEventListener('change', () => {\n    const file = el.file.files?.[0];\n    // Reset immediately so picking the SAME file again re-fires `change`.\n    el.file.value = '';\n    if (uploadWouldStart(file)) { firstPhoto = file; secondPhoto = null; }\n    upload(file);\n  });\n\n  /* Second photo (2026-11-03, the Business page, section setting g_second_photo_styles). Dave: a realtor wants the\n     house AND their client in the piece, which is two photos. For the listed styles, once a first photo is in, a\n     second one can be added: both go side by side into one picture (first left, second right) and that picture is\n     uploaded, so the backend still gets one photo (the styles' prompts say the image may be two photos). Remove,\n     or picking a style that is not listed, uploads the first photo on its own again. Runs from refreshCta. */\n  // The checks upload() makes before it takes a photo: the two photos above only follow photos it really took\n  // (a drop while busy or a non-image is refused there, and must not replace the first photo here).\n  function uploadWouldStart(file) {\n    return Boolean(file) && !state.busy && !state.uploading && !(file.type && !/^image\\//i.test(file.type));\n  }\n  function secondPhotoUi() {\n    return root.querySelector('[data-aicp-second]');\n  }\n  function syncSecondPhoto() {\n    const ui = secondPhotoUi();\n    if (!ui) return;\n    const listed = new Set(String(ui.dataset.aicpSecondStyles || '').split(',').map((s) => s.trim()).filter(Boolean));\n    const allowed = listed.has(state.styles[0]?.name || '');\n    // A failed or expired upload empties the tile (clearPhoto): no photo there, no second-photo row either.\n    const hasPhoto = Boolean(firstPhoto) && Boolean(state.uploaded || state.uploading);\n    if (!hasPhoto && !state.uploading) secondPhoto = null;\n    const active = document.activeElement;\n    ui.hidden = !allowed || !hasPhoto || Boolean(state.busy);\n    const add = ui.querySelector('[data-aicp-second-add]');\n    const remove = ui.querySelector('[data-aicp-second-remove]');\n    if (add) { add.hidden = Boolean(secondPhoto); add.disabled = Boolean(state.uploading); }\n    if (remove) { remove.hidden = !secondPhoto; remove.disabled = Boolean(state.uploading); }\n    // keyboard users: the button they used is swapped for the other one, so move focus to it\n    if (active && ui.contains(active) && active.hidden) [add, remove].find((b) => b && !b.hidden)?.focus();\n    if (!allowed && secondPhoto && state.uploaded && !state.uploading && !state.busy) {\n      secondPhoto = null;\n      upload(firstPhoto);\n    }\n  }\n  async function sideBySide(a, b) {\n    // loadPhoto draws the photo upright (EXIF) and decodes what this browser can, HEIC on iPhones included.\n    const pics = await Promise.all([loadPhoto(a), loadPhoto(b)]);\n    const cell = 1024;\n    const canvas = document.createElement('canvas');\n    canvas.width = cell * 2;\n    canvas.height = cell;\n    const ctx = canvas.getContext('2d');\n    ctx.fillStyle = '#ffffff';\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    pics.forEach((pic, i) => {\n      const w = pic.naturalWidth || pic.width;\n      const h = pic.naturalHeight || pic.height;\n      const s = Math.min(cell / w, cell / h);\n      ctx.drawImage(pic, i * cell + (cell - w * s) / 2, (cell - h * s) / 2, w * s, h * s);\n    });\n    const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9));\n    if (!blob) throw new Error('no picture');\n    return new File([blob], 'two-photos.jpg', { type: 'image/jpeg' });\n  }\n  (() => {\n    const ui = secondPhotoUi();\n    if (!ui) return;\n    const input = ui.querySelector('[data-aicp-second-input]');\n    ui.querySelector('[data-aicp-second-add]')?.addEventListener('click', () => input?.click());\n    input?.addEventListener('change', async () => {\n      const file = input.files?.[0];\n      input.value = '';\n      const base = firstPhoto;\n      if (!file || !base || state.busy || state.uploading) return;\n      if (file.type && !/^image\\//i.test(file.type)) {\n        notify({ message: 'Please choose a PNG, JPG, or WebP photo under 20 MB.', key: 'second-photo' });\n        return;\n      }\n      try {\n        const both = await sideBySide(base, file);\n        // the picture took a moment: the shopper may have started a design or picked another first photo meanwhile\n        if (state.busy || state.uploading || firstPhoto !== base) return;\n        secondPhoto = file;\n        upload(both);\n      } catch {\n        notify({ message: 'That photo could not be read. Please try a JPG or PNG.', key: 'second-photo' });\n      }\n    });\n    ui.querySelector('[data-aicp-second-remove]')?.addEventListener('click', () => {\n      if (!firstPhoto || state.busy || state.uploading) return;\n      secondPhoto = null;\n      upload(firstPhoto);\n    });\n  })();\n  if (el.drop) {\n    ['dragenter', 'dragover'].forEach((type) => {\n      el.drop.addEventListener(type, (event) => {\n        event.preventDefault();\n        // A busy tile must not light up as if it would take the photo.\n        if (!state.busy) el.drop.classList.add('is-drag');\n      });\n    });\n    ['dragleave', 'drop'].forEach((type) => {\n      el.drop.addEventListener(type, (event) => {\n        event.preventDefault();\n        el.drop.classList.remove('is-drag');\n      });\n    });\n    el.drop.addEventListener('drop', (event) => {\n      const file = [...(event.dataTransfer?.files || [])].find((f) => f.type.startsWith('image/'));\n      if (file) {\n        if (uploadWouldStart(file)) { firstPhoto = file; secondPhoto = null; }\n        upload(file);\n      }\n    });\n    // While the last design is still being made the file input is disabled, so\n    // a tap on the tile would do nothing. The tile promises progress instead.\n    el.drop.addEventListener('click', (event) => {\n      if (!state.busy) return;\n      event.preventDefault();\n      switchView('gallery');\n    });\n  }\n\n  /* ---------- instructions accordion ---------- */\n\n  el.acc?.addEventListener('click', () => {\n    const open = el.acc.getAttribute('aria-expanded') === 'true';\n    el.acc.setAttribute('aria-expanded', String(!open));\n    el.notes.hidden = open;\n    if (el.acc.querySelector('.aicp-instructions__plus')) {\n      el.acc.querySelector('.aicp-instructions__plus').textContent = open ? '+' : '\u2212';\n    }\n  });\n\n  /* ---------- view toggle (Create / Gallery) ---------- */\n\n  let galleryScrollRequest = 0;\n  function scrollToStableGalleryStage() {\n    const request = ++galleryScrollRequest;\n    if (!mobileGalleryMedia.matches) return;\n    const stage = root.querySelector('.aicp-g-stage');\n    if (!stage) return;\n\n    Promise.resolve(document.fonts?.ready).then(() => {\n      let previousTop;\n      let stableFrames = 0;\n      let frames = 0;\n      const settle = () => {\n        if (request !== galleryScrollRequest || !root.classList.contains('aicp--gallery')) return;\n        const top = stage.getBoundingClientRect().top;\n        stableFrames = previousTop !== undefined && Math.abs(top - previousTop) < 0.5 ? stableFrames + 1 : 0;\n        previousTop = top;\n        frames += 1;\n        if (stableFrames >= 2 || frames >= 12) {\n          stage.scrollIntoView({ block: 'start', behavior: 'auto' });\n          return;\n        }\n        requestAnimationFrame(settle);\n      };\n      requestAnimationFrame(settle);\n    });\n  }\n\n  function switchView(target) {\n    el.viewToggles.forEach((other) => {\n      const active = other.dataset.aicpViewToggle === target;\n      other.classList.toggle('is-active', active);\n      other.setAttribute('aria-pressed', String(active));\n    });\n    el.views.forEach((view) => {\n      view.hidden = view.dataset.aicpView !== target;\n    });\n    // Gallery uses its own heading beside the purchase controls.\n    root.classList.toggle('aicp--gallery', target === 'gallery');\n    if (target === 'gallery') {\n      // Signed thumbnail/model URLs are short-lived (\u00A73.8): re-pull on open.\n      // Opening the gallery lands on the first creation \u2014 the re-pull always\n      // ends in syncHistoryVisibility, which honours pendingGalleryDefault.\n      // state.busy (a live job) is the signal that the stage is taken; the\n      // lingering stageOwnedByGeneration flag must not block a later visit.\n      pendingGalleryDefault = !state.busy;\n      loadGalleryHistory();\n      scrollToStableGalleryStage();\n    } else {\n      galleryScrollRequest += 1;\n    }\n  }\n\n  el.viewToggles.forEach((toggle) => {\n    toggle.addEventListener('click', () => {\n      if (toggle.disabled || toggle.classList.contains('is-locked')) return;\n      switchView(toggle.dataset.aicpViewToggle);\n    });\n  });\n\n  // The header's CREATE always opens Create your own (header setting, Drew 2026-10-29). On that\n  // page itself, open the Create view in place: a reload would drop the photo and the choices.\n  document.addEventListener('click', (event) => {\n    const link = event.target.closest?.('a.header-actions__create');\n    if (!link || event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n    let url;\n    try { url = new URL(link.href, location.href); } catch { return; }\n    if (url.origin !== location.origin || url.pathname !== location.pathname) return;\n    event.preventDefault();\n    switchView('create');\n    // the product title when it is shown (as on entry, focusMobileCreateOnEntry), else the upload step\n    const heading = root.querySelector('.aicp-create-form > .aicp-creator-character');\n    const target = heading && heading.offsetHeight > 1 ? heading : root.querySelector('[data-aicp-upload-step]') || root;\n    target.scrollIntoView({ block: 'start', behavior: 'smooth' });\n  });\n\n  /* ---------- gallery view (static order UI) ---------- */\n\n  /* Every stage notice goes through here: the text lands in the notice card's\n     inner <p>, an optional small detail line (the raw error, for support) goes\n     under it, and the Retry button shows only when the caller gives it an\n     action. The card itself is shown/hidden by its callers (g.stageStatus.hidden). */\n  let stageRetryAction = null;\n\n  function setStageStatusText(text, { detail = '', retry = null, failure = '', retryLabel = 'Retry', quiet = false } = {}) {\n    if (!g.stageStatus) return;\n    stageRetryAction = typeof retry === 'function' ? retry : null;\n    if (g.stageRetry) {\n      g.stageRetry.hidden = !stageRetryAction;\n      g.stageRetry.textContent = retryLabel;\n    }\n    // Every notice with Retry is a 3D download or 3D viewer failure (new or gallery design),\n    // except the quiet one that holds the 3D back after a crash (showThreeDPaused).\n    if (stageRetryAction && !quiet) {\n      clarityTag('mythumi_3d_fail_type', failure || 'unknown');\n      clarityEvent('mythumi_3d_view_failed', galleryStage.item?.id, { failure: failure || 'unknown', detail: String(detail || '').slice(0, 140) });\n    }\n    if (!g.stageStatusText) {\n      // Section markup without the inner <p> (old section still cached): plain text.\n      g.stageStatus.textContent = text || '';\n      return;\n    }\n    g.stageStatusText.textContent = text || '';\n    if (detail) {\n      const small = document.createElement('small');\n      small.className = 'aicp-g-stage__status-detail';\n      small.textContent = detail;\n      g.stageStatusText.append(small);\n    }\n  }\n\n  function stageStatusText() {\n    return (g.stageStatusText || g.stageStatus)?.textContent || '';\n  }\n\n  function hideStageStatus() {\n    stageRetryAction = null;\n    if (g.stageRetry) g.stageRetry.hidden = true;\n    if (g.stageStatus) g.stageStatus.hidden = true;\n  }\n\n  const g = {\n    stagePills: [...root.querySelectorAll('[data-aicp-gstage]')],\n    stageFrame: root.querySelector('[data-aicp-g-stage-frame]'),\n    stageViewer: root.querySelector('[data-aicp-g-stage-viewer]'),\n    stageStatus: root.querySelector('[data-aicp-g-stage-status]'),\n    stageStatusText: root.querySelector('[data-aicp-g-stage-status-text]'),\n    stageRetry: root.querySelector('[data-aicp-g-stage-retry]'),\n    stageLoading: el.stageLoading,\n    stageCaption: root.querySelector('[data-aicp-g-stage-caption]'),\n    historyRail: root.querySelector('[data-aicp-g-history-rail]'),\n    historyLabel: root.querySelector('[data-aicp-g-history-label]'),\n    historySection: root.querySelector('[data-aicp-g-history]'),\n    trash: [...root.querySelectorAll('[data-aicp-g-trash]')],\n    name: root.querySelector('[data-aicp-g-name]'),\n    nameCount: root.querySelector('[data-aicp-g-name-count]'),\n    engravingOff: root.querySelector('[data-aicp-g-engraving-off]'),\n    sizeControl: root.querySelector('[data-aicp-option-control=\"size\"]'),\n    sizeGrid: root.querySelector('[data-aicp-g-size-grid]'),\n    sizeButtons: [...root.querySelectorAll('[data-aicp-g-size]')],\n    priceNow: root.querySelector('[data-aicp-g-price-now]'),\n    priceWas: root.querySelector('[data-aicp-g-price-was]'),\n    customSize: root.querySelector('[data-aicp-g-custom-size]'),\n    baseControl: root.querySelector('[data-aicp-option-control=\"base\"]'),\n    finishControl: root.querySelector('[data-aicp-option-control=\"finish\"]'),\n    finish: [...root.querySelectorAll('button[data-aicp-g-finish]')],\n    finishLine: root.querySelector('[data-aicp-g-finish-line]'),\n    giftRow: root.querySelector('[data-aicp-g-gift-row]'),\n    gift: root.querySelector('[data-aicp-g-gift]'),\n    qtyVal: root.querySelector('[data-aicp-g-qty-val]'),\n    qtyMinus: root.querySelector('[data-aicp-g-qty-minus]'),\n    qtyPlus: root.querySelector('[data-aicp-g-qty-plus]'),\n    cartButtons: {\n      cart: root.querySelector('[data-aicp-g-cart=\"cart\"]'),\n      buy: root.querySelector('[data-aicp-g-cart=\"buy\"]'),\n    },\n    actions: root.querySelector('.aicp-g-actions'),\n    checkoutStatus: root.querySelector('[data-aicp-g-checkout-status]'),\n  };\n\n  /* ---------- history (server-backed, JuJuBit-style) ----------\n     Contract per docs/storefront-proxy-api.md \u00A73.8/3.9:\n     GET    {proxy}/history -> { history: [{ id, status, thumbnailUrl, modelUrl,\n              style, pose, purchased, createdAt, ... }], signedIn, identityKind }\n     DELETE {proxy}/history/{id} -> { ok: true, remaining }\n     - thumbnailUrl may be null (in-progress/failed or expired CDN link) \u2192 placeholder.\n     - modelUrl is freshly signed per response; refresh by re-pulling /history.\n     - DELETE response \"remaining\" drives the empty/lock state \u2014 no second request.\n     Identity (anonymous-IP vs signed-in customer) is resolved server-side by the\n     app proxy; the frontend never handles login state. */\n\n  function styleLabelFor(name) {\n    if (!name) return '';\n    if (FIXED_STYLE && name === FIXED_STYLE && fixedStyle.label) return fixedStyle.label;\n    const escapeSelector = window.CSS?.escape ? window.CSS.escape : (value) => value.replace(/\"/g, '\\\\\"');\n    // The label sits beside its card button inside the tile, not inside it.\n    const safe = escapeSelector(name);\n    const card = el.styles.querySelector(`[data-style=\"${safe}\"], [data-style-name=\"${safe}\"]`);\n    const label = card?.closest('.aicp-style-unit')?.querySelector('.aicp-style__name');\n    if (label) return label.textContent.trim();\n    // Generation names can come from /categories instead of /styles (pet tab\n    // sends record.style, e.g. pet-special) \u2014 resolve those to their label too.\n    const record = styleCategories.find((c) => c.style === name);\n    if (record) return record.styleLabel || name;\n    return name;\n  }\n\n  function setGalleryLocked(lock) {\n    const chips = el.viewToggles.filter((toggle) => toggle.dataset.aicpViewToggle === 'gallery');\n    const locked = Boolean(lock);\n    chips.forEach((chip) => {\n      chip.classList.toggle('is-locked', locked);\n      chip.disabled = locked;\n      if (locked) {\n        chip.setAttribute('aria-disabled', 'true');\n        chip.title = 'Create a figurine first to unlock the gallery';\n      } else {\n        chip.removeAttribute('aria-disabled');\n        chip.removeAttribute('title');\n      }\n    });\n  }\n\n  function updateHistoryCount(count) {\n    if (!g.historyLabel) return;\n    const base = g.historyLabel.dataset.baseLabel\n      || (g.historyLabel.dataset.baseLabel = g.historyLabel.textContent.trim().replace(/\\s*\\(\\d+\\)\\s*$/, ''));\n    g.historyLabel.textContent = `${base} (${count})`;\n  }\n\n  /* Show the history section only when there are 2+ items: a single design\n     is already on the stage, and an empty state locks the gallery.\n     The auto-load yields to an in-flight generation \u2014 the stage belongs to\n     the new run until the shopper explicitly clicks a history card. */\n  let historyItems = [];   // filtered to the active category\n  let historyTotal = 0;    // all creations regardless of category\n  let stageOwnedByGeneration = false;\n  // Set when the shopper opens the gallery: the next history paint lands on the\n  // first creation. Cleared once honoured (or abandoned if a job owns the stage).\n  let pendingGalleryDefault = false;\n\n  // The design a restore link asked for (requestedJob), once it is in the rail. It is shown on\n  // every paint until settled: rails come from the saved cache and from /history, in either order\n  // around the gallery's own landing (switchView arms pendingGalleryDefault after /styles).\n  function showRequestedJob(cards, scroll = false) {\n    if (!requestedJob || state.busy) return false;\n    const card = cards.find((c) => c.dataset.id === requestedJob);\n    const item = historyItems.find((entry) => String(entry.id) === requestedJob);\n    if (!card || !item) return false;\n    selectHistoryCard(card);\n    revealHistoryItem(item, scroll);\n    return true;\n  }\n\n  // Done with the restore link's request: forget it, and take it out of the address so a reload\n  // (or a return from login or checkout) lands on the newest design as usual.\n  function dropRequestedJob() {\n    if (!requestedJob) return;\n    requestedJob = '';\n    try {\n      const url = new URL(location.href);\n      url.searchParams.delete('aicp_job');\n      history.replaceState(history.state, '', url);\n    } catch {}\n  }\n\n  // Settled once the gallery's own landing has run and /history's rail is in (cached items carry\n  // fromHistory too; only fromCache tells them apart): shown by then, or not in this gallery.\n  let galleryLandingDone = false;\n  function settleRequestedJob() {\n    if (galleryLandingDone && historyItems.some((entry) => !entry.fromCache)) dropRequestedJob();\n  }\n\n  /* Land the gallery on the first creation. Returns false when there is\n     nothing to show yet or an in-flight generation owns the stage. */\n  function showFirstHistory(scroll = false) {\n    if (state.busy) return false;\n    if (g.historyRail && showRequestedJob([...g.historyRail.querySelectorAll('.aicp-g-history__card')], scroll)) return true;\n    const card = g.historyRail?.querySelector('.aicp-g-history__card');\n    const item = historyItems[0];\n    if (!card || !item) return false;\n    selectHistoryCard(card);\n    revealHistoryItem(item, scroll);\n    return true;\n  }\n\n  function syncHistoryVisibility() {\n    const cards = g.historyRail ? [...g.historyRail.querySelectorAll('.aicp-g-history__card')] : [];\n    if (g.historySection) g.historySection.hidden = cards.length <= 1;\n    setGalleryLocked(historyItems.length === 0 && !state.busy && !galleryStage.item);\n    updateHistoryCount(cards.length);\n    if (!cards.length) return;\n\n    // Freshly opened gallery \u2192 first creation. A running job wins over this.\n    if (pendingGalleryDefault) {\n      if (state.busy) {\n        pendingGalleryDefault = false;\n        dropRequestedJob(); // a generation has the stage: the restore link's design waits in the rail\n        return;\n      }\n      if (showFirstHistory()) {\n        pendingGalleryDefault = false;\n        galleryLandingDone = true;\n        settleRequestedJob();\n      }\n      return;\n    }\n    // A generation owns the stage: never yank it away on a background re-pull,\n    // but keep its card highlighted when the rail was just rebuilt.\n    if (stageOwnedByGeneration || state.busy) {\n      dropRequestedJob();\n      const ownId = galleryStage.item?.id;\n      const ownCard = ownId ? cards.find((card) => card.dataset.id === String(ownId)) : null;\n      if (ownCard) selectHistoryCard(ownCard);\n      return;\n    }\n    // The restore link's design, on a paint before the gallery's landing or on /history's rail\n    // after a landing that only had the cache.\n    const shownRequested = showRequestedJob(cards);\n    settleRequestedJob();\n    if (shownRequested) return;\n    // Keep the design already on the stage selected across a re-pull.\n    const shownId = galleryStage.item?.id;\n    const match = shownId ? cards.find((card) => card.dataset.id === String(shownId)) : null;\n    const fresh = shownId ? historyItems.find((item) => String(item.id) === String(shownId)) : null;\n    if (fresh && fresh !== galleryStage.item && ((fresh.cleanFile && !galleryStage.item.cleanFile) || (fresh.modelUrl && !galleryStage.item.modelUrl))) {\n      revealHistoryItem(fresh, false, false);\n      if (match) selectHistoryCard(match);\n    } else if (match) selectHistoryCard(match);\n    else if (!galleryStage.item) showFirstHistory();\n  }\n\n  function showOnStage(item, scroll = true) {\n    if (!g.stageFrame) return;\n    // A notice (and its Retry) belongs to the design it was shown for.\n    if (galleryStage.item !== item) hideStageStatus();\n    galleryStage.item = item;\n    // A picture of this design already on the page is not downloaded again.\n    const src = pictureFor(item, 'stage');\n    if (src) {\n      let img = g.stageFrame.querySelector('.aicp-g-stage__img');\n      if (!img) {\n        img = document.createElement('img');\n        img.className = 'aicp-g-stage__img';\n        img.alt = '';\n        // Shoppers' designs stay out of Clarity recordings.\n        img.dataset.clarityMask = 'True';\n        g.stageFrame.prepend(img);\n      }\n      rememberPicture(item, 'stage', src);\n      img.src = src;\n    }\n    // Keep showing the available 2D image while the GLB downloads and decodes.\n    // A previously mounted item can enter 3D immediately; every other item is\n    // preloaded below and switches only after viewer.loadModel() resolves.\n    const modelReady = stageModelIsReady(item);\n    galleryStage.pill = modelReady ? '3d' : '2d';\n    stageSetPill(galleryStage.pill);\n    if (item.modelUrl && !modelReady) void preloadStage3D(item);\n    if (g.stageCaption) {\n      const style = item.styleLabel || styleLabelFor(item.style) || item.style;\n      if (style) g.stageCaption.textContent = `STYLE: ${String(style).toUpperCase()}`;\n    }\n    if (scroll) {\n      g.stageFrame.closest('.aicp-g-stage')?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n    }\n    syncCharactersToDesign(item);\n    syncCheckoutGate();\n  }\n\n  // Solo / Duo: the variant follows the design on stage (made with a Duo style -> Duo variant and price).\n  function syncCharactersToDesign(item) {\n    if (!productCharactersOption || !item?.style) return;\n    const need = charactersForStyle(item.style);\n    markCharacters(galleryCharsButtons, need, 'data-aicp-gallery-chars');\n    if (!variantLogicReady || sameCharacters(selectedProductOptions.get('characters'), need)) return;\n    selectedProductOptions.set('characters', need);\n    syncProductVariant();\n  }\n\n  function clearStage() {\n    stopStageGenerationProgress();\n    galleryStage.item = null;\n    galleryStage.pill = '2d';\n    galleryStage.loadedModelUrl = null;\n    g.stagePills.forEach((p) => {\n      p.classList.toggle('is-active', p.dataset.aicpGstage === '2d');\n      if (p.dataset.aicpGstage === '3d') p.disabled = true;\n    });\n    if (g.stageViewer) g.stageViewer.hidden = true;\n    hideStageStatus();\n    setStage3DProgress(false);\n    setStageMask(false);\n    if (!g.stageFrame) return;\n    // Keep the viewer, loading indicator and progress mask mounted. Removing\n    // all frame children here detached the stage-only progress UI, so a later\n    // generation could update its stale references without painting anything.\n    g.stageFrame.querySelectorAll(':scope > .aicp-g-stage__img, :scope > .aicp-tile__placeholder')\n      .forEach((node) => node.remove());\n    const ph = document.createElement('span');\n    ph.className = 'aicp-tile__placeholder';\n    ph.innerHTML = '<svg viewBox=\"0 0 16 16\" width=\"34\" height=\"34\" fill=\"#b3ab9c\" aria-hidden=\"true\"><path d=\"M2 2h12v12H2zm2 8.2 2.4-3 2 2.4 1.8-2.1L13 11H4z\"/><circle cx=\"6\" cy=\"6\" r=\"1.3\"/></svg>';\n    g.stageFrame.append(ph);\n    if (g.stageCaption) {\n      g.stageCaption.textContent = g.stageCaption.dataset.defaultCaption\n        || (g.stageCaption.dataset.defaultCaption = g.stageCaption.textContent);\n    }\n    syncCheckoutGate();\n  }\n\n  function revealHistoryItem(item, scroll = true, fromUser = true) {\n    // The card of the design this page is still building (Drew, 2026-10-09: after\n    // looking at older designs mid-run, clicking back showed only the 2D with no\n    // sign that the 3D was still coming): hand the stage back to that run, with\n    // its 3D progress, so the model lands on it the moment it arrives.\n    const liveRun = fromUser && item?.id && item.status !== 'failed'\n      ? currentRuns.find((run) => run.jobId && String(run.jobId) === String(item.id) && run.displayItem && !run.done && !run.failed)\n      : null;\n    if (liveRun) {\n      stageOwnedByGeneration = true;\n      showOnStage(liveRun.displayItem, scroll);\n      galleryStage.pill = '2d';\n      stageSetPill('2d');\n      startStageGenerationProgress('3d', liveRun.pct || 0, 'Building your 3D model \u2014 sculpting and texturing\u2026');\n      return;\n    }\n    if (fromUser) {\n      stageOwnedByGeneration = false; // explicit click takes the stage back\n      // The generation mask would otherwise cover the design being viewed.\n      stopStageGenerationProgress();\n      setStageMask(false);\n    }\n    if (item.status !== 'failed') {\n      showOnStage(item, scroll);\n      return;\n    }\n    // Failed run: keep whatever 2D survived (via /jobs/:id) and say why the 3D is missing.\n    showOnStage({ style: item.style, styleLabel: item.styleLabel || styleLabelFor(item.style) }, false);\n    if (g.stageStatus) {\n      g.stageStatus.hidden = false;\n      setStageStatusText('Checking this design\u2026');\n    }\n    if (!item.id) {\n      if (g.stageStatus) {\n        g.stageStatus.hidden = false;\n        setStageStatusText('This design failed to generate. Please create a new one.');\n      }\n      return;\n    }\n    call(`/jobs/${encodeURIComponent(item.id)}`)\n      .then((job) => {\n        if (job.cleanUrl) {\n          showOnStage({ thumbnailUrl: job.cleanUrl, style: item.style, styleLabel: item.styleLabel || styleLabelFor(item.style) }, false);\n          if (g.stageStatus) {\n            g.stageStatus.hidden = false;\n            setStageStatusText('The 2D design survived, but the 3D build failed. Please create it again.');\n          }\n        } else if (g.stageStatus) {\n          g.stageStatus.hidden = false;\n          setStageStatusText(job.error || 'This design failed to generate. Please create a new one.');\n        }\n      })\n      .catch(() => {\n        if (g.stageStatus) {\n          g.stageStatus.hidden = false;\n          setStageStatusText('This design failed to generate. Please create a new one.');\n        }\n      });\n  }\n\n  /* Deletion confirm dialog (native <dialog>, one instance per page).\n     Resolves true only when the shopper presses \"Remove\". */\n  let confirmDialog = null;\n\n  function confirmRemove() {\n    return new Promise((resolve) => {\n      if (!confirmDialog) {\n        confirmDialog = document.createElement('dialog');\n        confirmDialog.className = 'aicp-confirm';\n        confirmDialog.innerHTML = `\n          <p class=\"aicp-confirm__title\">Remove this design?</p>\n          <p class=\"aicp-confirm__text\">It will be removed from your creations. This cannot be undone.</p>\n          <div class=\"aicp-confirm__actions\">\n            <button type=\"button\" class=\"aicp-confirm__btn\" data-aicp-confirm-cancel>Cancel</button>\n            <button type=\"button\" class=\"aicp-confirm__btn aicp-confirm__btn--primary\" data-aicp-confirm-ok>Remove</button>\n          </div>`;\n        root.append(confirmDialog);\n        // Click on the backdrop (the dialog element itself) cancels.\n        confirmDialog.addEventListener('click', (event) => {\n          if (event.target === confirmDialog) confirmDialog.close('cancel');\n        });\n      }\n      confirmDialog.querySelector('[data-aicp-confirm-cancel]').onclick = () => confirmDialog.close('cancel');\n      confirmDialog.querySelector('[data-aicp-confirm-ok]').onclick = () => confirmDialog.close('ok');\n      confirmDialog.addEventListener('close', () => {\n        resolve(confirmDialog.returnValue === 'ok');\n      }, { once: true });\n      confirmDialog.showModal();\n    });\n  }\n\n  /* Highlight the clicked history card (single selection). The selected card\n     is what the fixed Add To Cart / Buy Sculpture bar acts on. */\n  function selectHistoryCard(card) {\n    if (!card) return;\n    g.historyRail?.querySelectorAll('.aicp-g-history__card').forEach((c) => {\n      c.classList.toggle('is-selected', c === card);\n    });\n  }\n\n  /* Card pictures (2026-10-21). /history signs every picture URL afresh on\n     each pull and the bucket says `max-age=0`, so a rebuilt rail (My Gallery,\n     a finished design, a delete) used to download every picture again. A\n     picture this page already shows keeps its URL while that signature is\n     valid, so the rebuilt card (and the stage) paint it from memory.\n     `cardUrl` is a card-size copy of the 2D (proposed backend field): cards\n     take it when /history has one; the stage keeps the full 2D. */\n  const shownImages = new Map(); // `${kind}:${design id}` -> URL already shown on this page\n\n  // A signed GCS URL carries its own expiry: X-Goog-Date + X-Goog-Expires.\n  function signedUrlFresh(url, marginMs = 60000) {\n    try {\n      const params = new URL(url, window.location.href).searchParams;\n      const signed = (params.get('X-Goog-Date') || '').match(/^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/);\n      const expires = Number(params.get('X-Goog-Expires'));\n      if (!signed || !expires) return true; // not a signed URL: nothing expires\n      const at = Date.UTC(+signed[1], +signed[2] - 1, +signed[3], +signed[4], +signed[5], +signed[6]);\n      return at + expires * 1000 - marginMs > Date.now();\n    } catch {\n      return false;\n    }\n  }\n\n  function freshPicture(item, kind) {\n    return (kind === 'card' && item?.cardUrl) || item?.cleanUrl || item?.thumbnailUrl || '';\n  }\n\n  function pictureFor(item, kind = 'card') {\n    if (!item) return '';\n    // Without a card-size copy, the card and the stage show the same file.\n    const kinds = item.cardUrl ? [kind] : [kind, kind === 'card' ? 'stage' : 'card'];\n    for (const key of kinds) {\n      const shown = item.id ? shownImages.get(`${key}:${item.id}`) : null;\n      if (shown && signedUrlFresh(shown)) return shown;\n    }\n    return freshPicture(item, kind);\n  }\n\n  function rememberPicture(item, kind, url) {\n    if (item?.id && url) shownImages.set(`${kind}:${item.id}`, url);\n  }\n\n  /* The last rail this page showed, kept while its signed links live (15 min).\n     A revisit paints those cards at once instead of waiting for /history, and\n     the browser only re-checks pictures it already holds (GCS answers 304, no\n     bytes). The /history answer then replaces it as usual. */\n  const RAIL_CACHE_KEY = `realizeit:gallery-rail:${window.location.pathname.replace(/^.*\\/products\\//, '/products/')}`;\n  const RAIL_CACHE_MAX_ITEMS = 24;\n\n  // Whose rail it is: the signed-in customer (hashed in the section) and the visitor token.\n  function railCacheOwner() {\n    return `${root.dataset.aicpViewer || ''}|${visitorToken() || ''}`;\n  }\n\n  function saveRailCache(items) {\n    try {\n      if (!items.length) {\n        localStorage.removeItem(RAIL_CACHE_KEY);\n        return;\n      }\n      localStorage.setItem(RAIL_CACHE_KEY, JSON.stringify({\n        owner: railCacheOwner(),\n        items: items.slice(0, RAIL_CACHE_MAX_ITEMS).map(({ fromCache, ...item }) => item),\n      }));\n    } catch {\n      // Storage full or blocked: the next visit simply waits for /history.\n    }\n  }\n\n  function readRailCache() {\n    try {\n      const cached = JSON.parse(localStorage.getItem(RAIL_CACHE_KEY) || 'null');\n      if (!cached || cached.owner !== railCacheOwner() || !Array.isArray(cached.items) || !cached.items.length) return null;\n      // Only while every link in it is still signed (two minutes to spare).\n      const links = cached.items.flatMap((item) => [item.cardUrl, item.cleanUrl, item.thumbnailUrl, item.modelUrl]).filter(Boolean);\n      return links.every((url) => signedUrlFresh(url, 120000)) ? cached.items : null;\n    } catch {\n      return null;\n    }\n  }\n\n  // Runs once, right after the page has set itself up and before any reply.\n  function paintCachedRail() {\n    if (historyItems.length || state.busy) return;\n    const items = readRailCache();\n    if (items) renderHistoryItems(items.map((item) => ({ ...item, fromCache: true })));\n  }\n\n  // Cards past the first few sit off-screen in the scrolling rail: they load\n  // when scrolled to, so a long history does not hold up the visible ones.\n  const EAGER_CARDS = 6;\n\n  function setCardPicture(card, item, src, { lazy = false } = {}) {\n    const img = document.createElement('img');\n    img.alt = item.styleLabel || styleLabelFor(item.style) || 'Your figurine';\n    img.dataset.clarityMask = 'True';\n    img.loading = lazy ? 'lazy' : 'eager';\n    img.decoding = 'async';\n    // The cards are the first thing the gallery shows: ahead of the 3D viewer and models.\n    if (!lazy) img.fetchPriority = 'high';\n    const fresh = freshPicture(item, 'card');\n    // Remembered as soon as it is asked for, so a rail rebuilt while this\n    // picture is still arriving asks for the same URL instead of a new one.\n    rememberPicture(item, 'card', src);\n    card.classList.add('is-loading');\n    img.addEventListener('load', () => card.classList.remove('is-loading'));\n    img.onerror = () => {\n      // A reused URL the browser no longer holds: take the one /history just signed.\n      if (fresh && img.src !== fresh) {\n        rememberPicture(item, 'card', fresh);\n        img.src = fresh;\n        return;\n      }\n      card.classList.remove('is-loading');\n      img.remove();\n      card.title = 'Preview expired \u2014 reload the page to refresh';\n    };\n    img.src = src;\n    card.prepend(img);\n    card.title = 'View this design';\n  }\n\n  // /history has no picture for runs in the studio, failed ones and expired\n  // links: ask /jobs/:id for its 2D. All such cards at once, after the rail is\n  // already on screen (this used to hold every later card, one request each).\n  function fillCardFromJob(card, item) {\n    call(`/jobs/${encodeURIComponent(item.id)}`)\n      .then((job) => {\n        if (!job.cleanUrl) return;\n        item.cleanUrl = job.cleanUrl;\n        if (job.cardUrl) item.cardUrl = job.cardUrl;\n        if (job.modelUrl && !item.modelUrl) item.modelUrl = job.modelUrl;\n        if (!card.isConnected) return;\n        card.querySelector('.aicp-ph')?.remove();\n        setCardPicture(card, item, freshPicture(item, 'card'));\n        // The stage landed on this design before its 2D was known.\n        if (galleryStage.item === item && !stageOwnedByGeneration && !state.busy && item.status !== 'failed') {\n          showOnStage(item, false);\n        }\n      })\n      .catch(() => {\n        // job \u4E0D\u53EF\u67E5\uFF08\u8FC7\u671F/404\uFF09\u2014 \u8D70\u5360\u4F4D\n      });\n  }\n\n  // Resolves once the rail's pictures have arrived (or after capMs): the 3D\n  // viewer script and the GLB (a Duo is ~90 MB) wait for it on the first\n  // landing, so they do not share the line with the cards.\n  const FIRST_CARDS_CAP_MS = 2500;\n  let firstCardsPainted = null;\n  function railPicturesSettled(capMs) {\n    const pending = g.historyRail\n      ? [...g.historyRail.querySelectorAll('.aicp-g-history__card > img')].filter((img) => !img.complete && img.loading !== 'lazy')\n      : [];\n    if (!pending.length) return Promise.resolve();\n    return new Promise((resolve) => {\n      let left = pending.length;\n      const timer = setTimeout(resolve, capMs);\n      const done = () => {\n        left -= 1;\n        if (left > 0) return;\n        clearTimeout(timer);\n        resolve();\n      };\n      pending.forEach((img) => {\n        img.addEventListener('load', done, { once: true });\n        img.addEventListener('error', done, { once: true });\n      });\n    });\n  }\n\n  function renderHistoryItems(items) {\n    if (!g.historyRail) return;\n    historyItems = items.slice();\n    g.historyRail.textContent = '';\n    historyItems.forEach((item, index) => {\n      const card = document.createElement('div');\n      card.className = 'aicp-g-history__card';\n      if (item.id) card.dataset.id = String(item.id);\n      const failed = item.status === 'failed';\n      if (failed) card.classList.add('is-failed');\n\n      const src = pictureFor(item);\n      if (src) {\n        setCardPicture(card, item, src, { lazy: index >= EAGER_CARDS });\n      } else {\n        const ph = document.createElement('span');\n        ph.className = 'aicp-ph';\n        card.append(ph);\n        card.title = failed\n          ? 'Generation failed \u2014 click for details'\n          : 'Still in the studio\u2026';\n        if (item.id) fillCardFromJob(card, item);\n      }\n      // Routed through revealHistoryItem so a click also takes the stage back\n      // from an in-flight generation and clears its mask. Failed runs keep\n      // their 2D sometimes (3D submission died): it surfaces the reason and\n      // the surviving 2D instead of doing nothing.\n      card.addEventListener('click', () => {\n        dropRequestedJob(); // the shopper's own pick wins over the restore link's\n        selectHistoryCard(card);\n        revealHistoryItem(item);\n      });\n\n      const trash = document.createElement('button');\n      trash.type = 'button';\n      trash.className = 'aicp-g-history__trash';\n      trash.setAttribute('aria-label', 'Remove from history');\n      trash.innerHTML = '<svg viewBox=\"0 0 16 16\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" aria-hidden=\"true\"><path d=\"M2.5 4h11M6.5 4V2.8h3V4M4 4l.7 9.2c0 .5.4.8.9.8h4.8c.5 0 .9-.3.9-.8L12 4M6.6 6.8v4.6M9.4 6.8v4.6\"/></svg>';\n      trash.addEventListener('click', async (event) => {\n        event.stopPropagation();\n        if (!item.id) {\n          card.remove();\n          syncHistoryVisibility();\n          return;\n        }\n        // Server-backed entries are gone for good \u2014 confirm first.\n        if (!(await confirmRemove())) return;\n        try {\n          // \"remaining\" is the authoritative post-delete TOTAL across all\n          // categories \u2014 it only drives the empty/lock state. The visible\n          // label always reflects this category's rail (set by sync).\n          const result = await call(`/history/${encodeURIComponent(item.id)}`, { method: 'DELETE' });\n          card.remove();\n          historyItems = historyItems.filter((entry) => entry !== item);\n          saveRailCache(historyItems);\n          if (typeof result.remaining === 'number') {\n            historyTotal = result.remaining;\n            if (result.remaining === 0) clearStage();\n          }\n          syncHistoryVisibility();\n        } catch (error) {\n          // 404: the design is gone, or was never this visitor's \u2014 re-read the\n          // gallery rather than keep a card that cannot be removed.\n          if (error?.status === 404) loadGalleryHistory();\n          else logApiError('history-delete', error);\n        }\n      });\n      card.append(trash);\n      g.historyRail.append(card);\n    });\n    // The first rail of the visit holds back the 3D (see preloadStage3D).\n    if (!firstCardsPainted && historyItems.length) firstCardsPainted = railPicturesSettled(FIRST_CARDS_CAP_MS);\n    // Selection + default landing are decided in syncHistoryVisibility, which\n    // knows whether a generation owns the stage and which design is on it.\n    syncHistoryVisibility();\n  }\n\n  // A Mini Pop design comes back from /history as Free Style with the look\n  // ahead of the note: restore its card label and the shopper's own note.\n  function miniPopHistory(item) {\n    const instructions = item.instructions || '';\n    if (!instructions.startsWith(MINI_POP.directive)) return {};\n    return {\n      styleLabel: MINI_POP.label,\n      instructions: instructions.slice(MINI_POP.directive.length).trim(),\n    };\n  }\n\n  let galleryHistoryLoading = false;\n  let galleryHistoryAgain = false; // a re-pull asked for while one was in flight\n\n  // Styles configured in Shopify carry a `category` in /styles; built-in ones\n  // do not. Read once, for pages that filter their gallery by category.\n  let shopifyStylesPromise = null;\n  function shopifyStyleNames() {\n    if (!shopifyStylesPromise) {\n      shopifyStylesPromise = call('/styles', { prefetch: true })\n        .then((body) => new Set((body.styles || []).filter((style) => style?.category).map((style) => style.name)))\n        .catch(() => {\n          shopifyStylesPromise = null;\n          return null;\n        });\n    }\n    return shopifyStylesPromise;\n  }\n\n  // The designs of /history this page's gallery shows (also asked by the photo-match restore).\n  async function pageHistoryItems(all) {\n    const category = activeTabCategoryName() || INITIAL_CAT;\n    // Pages with known styles keep only their own designs; the pet page (pose\n    // mode, style from /categories) still filters by category.\n    const ownStyles = pageStyleNames.size ? pageStyleNames : null;\n    // A design of a Shopify-configured style (e.g. an exotic pet product's)\n    // stays on its own product page, priced and labelled there.\n    const otherProducts = ownStyles ? null : await shopifyStyleNames();\n    // Since 2026-10-09 the backend lists its built-in styles from Shopify too, so the\n    // page's own category style (pet-special) also carries a category: never drop it.\n    const ownCategoryStyle = activeCategoryRecord()?.style || '';\n    return (Array.isArray(all) ? all : [])\n      .filter((item) => (ownStyles\n        ? ownStyles.has(item.style)\n        : categoryOfName(item.style) === category && (item.style === ownCategoryStyle || !otherProducts?.has(item.style))))\n      // A historical preview can outlive its original uploaded image. Keep\n      // that distinction local to the UI: cleanFile + id still make the\n      // design buildable, while a missing sourceFile only omits the original\n      // photo from the archive handoff.\n      .map((item) => ({ ...item, ...miniPopHistory(item), fromHistory: true }));\n  }\n\n  async function loadGalleryHistory() {\n    if (galleryHistoryLoading) {\n      galleryHistoryAgain = true;\n      return;\n    }\n    galleryHistoryLoading = true;\n    galleryHistoryAgain = false;\n    try {\n      const controller = new AbortController();\n      const timer = setTimeout(() => controller.abort(), 15000);\n      let body;\n      try {\n        body = await call('/history', { signal: controller.signal, prefetch: true });\n      } finally {\n        clearTimeout(timer);\n      }\n      const all = body.history || body.items || [];\n      historyTotal = Array.isArray(all) ? all.length : 0;\n      const filtered = await pageHistoryItems(all);\n      saveRailCache(filtered);\n      // A design painted from the saved rail that /history no longer lists\n      // (removed on another device) does not stay on the stage.\n      const shownId = galleryStage.item?.fromCache ? String(galleryStage.item.id) : null;\n      if (shownId && !state.busy && !filtered.some((item) => String(item.id) === shownId)) clearStage();\n      if (filtered.length) {\n        await renderHistoryItems(filtered);\n      } else {\n        // No matching creations: leave Gallery locked and do not render a demo\n        // image or a pretend history card.\n        historyItems = [];\n        if (g.historyRail) g.historyRail.textContent = '';\n        syncHistoryVisibility();\n        if (!state.busy && !recoveringUnknownRun && !galleryStage.item && root.classList.contains('aicp--gallery')) switchView('create');\n      }\n    } catch {\n      // If the real history cannot be read, do not expose a gallery of demo\n      // art; cards already shown from an earlier read stay.\n      if (!historyItems.length && g.historyRail) g.historyRail.textContent = '';\n      syncHistoryVisibility();\n      if (!state.busy && !recoveringUnknownRun && !galleryStage.item && !historyItems.length && root.classList.contains('aicp--gallery')) switchView('create');\n    } finally {\n      galleryHistoryLoading = false;\n      if (galleryHistoryAgain) {\n        galleryHistoryAgain = false;\n        loadGalleryHistory();\n      }\n    }\n  }\n\n  // Keep every Gallery control unavailable until the real category history\n  // resolves. A generation may still open the view programmatically to show\n  // its in-progress stage.\n  setGalleryLocked(true);\n  // People/TRPG pages know their own styles only after /styles: their first\n  // history pull waits for that (see selectionReady below).\n  if (FIXED_STYLE || INITIAL_CAT === 'pet') loadGalleryHistory();\n  // After this setup has finished (everything the stage needs is defined).\n  Promise.resolve().then(paintCachedRail);\n\n  /* Remaining free-design count without waiting for a create (\u00A73.1). */\n  call('/status')\n    .then((body) => {\n      if (body.quota && typeof body.quota.remaining === 'number') showQuota(body.quota);\n    })\n    .catch(() => {\n      // Status is a nice-to-have; the quota also arrives with every create response.\n    });\n\n  /* Gallery stage \u2014 2D shows the thumbnail, 3D lazily spins up the WebGL viewer. */\n  const galleryStage = { item: null, pill: '2d', viewer: null, legacyBaseModelKey: null, baseViewerKey: null };\n\n  /* Downloaded GLB blobs, keyed by the STABLE history id (not modelUrl \u2014 the\n     proxy re-signs that on every /history pull, so URL-keyed caching never hit\n     and every view switch re-downloaded). Bounded so a long session cannot\n     grow without limit; the oldest entry's blob URL is revoked on eviction. */\n  const MODEL_CACHE_MAX = 4;\n  // A GLB's byte stream is measurable, but Meshopt decompression happens inside the loader and\n  // exposes no incremental callback. Reserve the final 2% for that decode/mount step: the bar\n  // never reaches 100% until the viewer has actually rendered the model.\n  const MODEL_READY_PROGRESS = 88;\n  const MODEL_DOWNLOAD_PROGRESS_END = 98;\n  const MODEL_COMPLETE_HOLD_MS = 400;\n  // Download guards (Feishu/iOS report, 2026-10-12): a GLB whose bytes stop\n  // arriving is aborted and fetched again instead of sitting at 88% for ever,\n  // and a dropped connection is retried before the shopper sees an error.\n  const MODEL_DOWNLOAD_STALL_MS = 30000;\n  const MODEL_DOWNLOAD_RETRIES = 2;\n  const MODEL_DOWNLOAD_RETRY_DELAY_MS = 1500;\n  // The proxy streams /model-assets without a Content-Length, so the bar eases\n  // toward 98% as bytes arrive instead of standing still: 8 MB reads ~94%.\n  const MODEL_UNKNOWN_SIZE_SCALE = 8 * 1024 * 1024;\n  // Chrome says \"Failed to fetch\"; Safari/WebKit \u2014 and the WebViews inside\n  // Feishu, WeChat and the like \u2014 say \"Load failed\", \"The network connection\n  // was lost\", \"The request timed out\" or \"cancelled\" for the same thing.\n  const NETWORK_ERROR_RE = /Failed to fetch|NetworkError|ERR_|Load failed|network connection|timed out|cancell?ed|abort|Network request failed/i;\n  const modelBlobCache = new Map(); // id -> { blobUrl, provider }\n\n  function cacheModelBlob(id, blobUrl, provider) {\n    if (!id) return;\n    const existing = modelBlobCache.get(id);\n    if (existing && existing.blobUrl !== blobUrl) URL.revokeObjectURL(existing.blobUrl);\n    // Refresh insertion order so the Map's first key is the least recently used.\n    modelBlobCache.delete(id);\n    modelBlobCache.set(id, { blobUrl, provider });\n    while (modelBlobCache.size > MODEL_CACHE_MAX) {\n      const oldestKey = modelBlobCache.keys().next().value;\n      const oldest = modelBlobCache.get(oldestKey);\n      modelBlobCache.delete(oldestKey);\n      if (oldest?.blobUrl) URL.revokeObjectURL(oldest.blobUrl);\n    }\n  }\n\n  /* Drop one entry (e.g. its signed URL turned out to be expired). */\n  function dropCachedModel(id) {\n    if (!id) return;\n    const entry = modelBlobCache.get(id);\n    if (!entry) return;\n    modelBlobCache.delete(id);\n    if (entry.blobUrl) URL.revokeObjectURL(entry.blobUrl);\n  }\n\n  /* The provider build owns 0\u201388%. Once a model URL exists, the remaining\n     88\u2013100% is driven by the actual GLB byte stream and viewer completion.\n     A design made earlier (gallery history, restore link) has no build to follow:\n     it shows the grey spinner and \"Loading your model\u2026\", no percentage and no\n     \"downloading\" (Drew, 2026-10-29). */\n  function createModelStageProgress(item) {\n    let stopped = false;\n    // madeEarlier: a remembered run that was already finished when the page opened (resume).\n    const madeEarlier = item?.fromHistory === true || item?.madeEarlier === true;\n    // The bar only ever moves forward within one load: a re-fetch after a\n    // dropped connection restarts its byte count, not the shopper's bar.\n    let highest = 0;\n    let attempt = 0;\n\n    function isCurrentItem() {\n      return stageShows(item);\n    }\n\n    function show(percent, description) {\n      if (stopped || !isCurrentItem()) return;\n      highest = Math.max(highest, percent);\n      if (madeEarlier) {\n        setStage3DProgress(true, 0, attempt ? 'Loading your model\u2026 (retrying)' : 'Loading your model\u2026', { spinner: true });\n        return;\n      }\n      setStage3DProgress(true, highest, description);\n    }\n\n    return {\n      start() {\n        show(MODEL_READY_PROGRESS, 'Downloading your 3D model\u2026');\n      },\n      downloading(progress) {\n        const info = typeof progress === 'number' ? { percent: progress } : (progress || {});\n        if (info.retrying) {\n          attempt = info.retrying;\n          show(MODEL_READY_PROGRESS, `Connection interrupted \u2014 retrying the 3D model download (${attempt + 1}/${MODEL_DOWNLOAD_RETRIES + 1})\u2026`);\n          return;\n        }\n        const span = MODEL_DOWNLOAD_PROGRESS_END - MODEL_READY_PROGRESS;\n        let displayPercent;\n        const text = attempt\n          ? `Retrying the 3D model download (${attempt + 1}/${MODEL_DOWNLOAD_RETRIES + 1})\u2026`\n          : 'Downloading your 3D model\u2026';\n        if (typeof info.percent === 'number') {\n          const downloaded = Math.max(0, Math.min(100, info.percent));\n          displayPercent = MODEL_READY_PROGRESS + (span * downloaded / 100);\n        } else {\n          // Size unknown: ease toward the end as bytes arrive. No byte count in the\n          // text (Drew, 2026-10-12: just \"downloading\").\n          const received = Math.max(0, Number(info.received) || 0);\n          displayPercent = MODEL_READY_PROGRESS + span * (1 - Math.exp(-received / MODEL_UNKNOWN_SIZE_SCALE));\n        }\n        show(displayPercent, text);\n      },\n      decoding() {\n        show(MODEL_DOWNLOAD_PROGRESS_END, 'Decoding your 3D model\u2026');\n      },\n      complete() {\n        show(100, 'Your 3D model is ready.');\n      },\n      stop() {\n        stopped = true;\n      },\n    };\n  }\n\n  function clearModelCache() {\n    modelBlobCache.forEach((entry) => {\n      if (entry?.blobUrl) URL.revokeObjectURL(entry.blobUrl);\n    });\n    modelBlobCache.clear();\n  }\n\n  // Release cached blobs when the page goes away (bfcache-safe: only on real unload).\n  window.addEventListener('pagehide', clearModelCache);\n\n  /* ---------- idle auto-rotate (Plan A: spin \u2192 pause \u2192 spin) ----------\n     The viewer only exposes setAutoRotate(bool) \u2014 no speed or direction \u2014 so a\n     true \"reverse then return\" is impossible without patching the bundle. This\n     cycles rotation on/off instead: slow spin, brief pause, spin again.\n     The viewer's own autoRotateDelay is set to 0 (see ensureStageViewer) so this\n     controller owns the state; otherwise the viewer's resume timer would fight\n     the pause phase. Tune the three durations below. */\n  const IDLE_ROTATE = {\n    spinMs: 8000,      // how long each rotation burst lasts\n    pauseMs: 1500,     // the pause between bursts\n    resumeAfterMs: 2500, // quiet time after the shopper drags before resuming\n  };\n  const idleRotate = { timer: null, paused: false, idleTimer: null, dragging: false };\n\n  function idleRotateEligible() {\n    return Boolean(\n      galleryStage.viewer &&\n      galleryStage.loadedModelUrl &&\n      galleryStage.pill === '3d' &&\n      !activeEngraving() &&\n      g.stageViewer && !g.stageViewer.hidden &&\n      document.visibilityState !== 'hidden' &&\n      !window.matchMedia('(prefers-reduced-motion: reduce)').matches\n    );\n  }\n\n  /* Some viewer bundle versions may not expose setAutoRotate \u2014 degrade to no\n     rotation instead of throwing. */\n  function setViewerAutoRotate(on) {\n    const viewer = galleryStage.viewer;\n    if (viewer && typeof viewer.setAutoRotate === 'function') viewer.setAutoRotate(on);\n  }\n\n  function runIdleRotateCycle() {\n    clearTimeout(idleRotate.timer);\n    if (!idleRotateEligible() || idleRotate.paused) return;\n    setViewerAutoRotate(true);\n    idleRotate.timer = setTimeout(() => {\n      setViewerAutoRotate(false);\n      idleRotate.timer = setTimeout(runIdleRotateCycle, IDLE_ROTATE.pauseMs);\n    }, IDLE_ROTATE.spinMs);\n  }\n\n  /* Called whenever the stage/pill changes: start the cycle only when a model is\n     actually on screen, stop it otherwise (2D view, empty stage, tab hidden). */\n  function syncIdleRotate() {\n    clearTimeout(idleRotate.timer);\n    idleRotate.timer = null;\n    clearTimeout(idleRotate.idleTimer);\n    idleRotate.idleTimer = null;\n    if (idleRotateEligible() && !idleRotate.paused) runIdleRotateCycle();\n    else setViewerAutoRotate(false);\n  }\n\n  // Pause while the shopper is dragging, resume after a short quiet period.\n  if (g.stageViewer) {\n    g.stageViewer.addEventListener('pointerdown', () => {\n      idleRotate.dragging = true;\n      idleRotate.paused = true;\n      clearTimeout(idleRotate.timer);\n      clearTimeout(idleRotate.idleTimer);\n      setViewerAutoRotate(false);\n    });\n    const resume = () => {\n      if (!idleRotate.dragging) return;\n      idleRotate.dragging = false;\n      clearTimeout(idleRotate.idleTimer);\n      idleRotate.idleTimer = setTimeout(() => {\n        idleRotate.paused = false;\n        syncIdleRotate();\n      }, IDLE_ROTATE.resumeAfterMs);\n    };\n    g.stageViewer.addEventListener('pointerup', resume);\n    g.stageViewer.addEventListener('pointercancel', resume);\n    g.stageViewer.addEventListener('pointerleave', resume);\n  }\n\n  // Stop while the tab is hidden so we do not animate off-screen.\n  document.addEventListener('visibilitychange', () => {\n    if (document.visibilityState === 'hidden') {\n      clearTimeout(idleRotate.timer);\n      idleRotate.timer = null;\n      setViewerAutoRotate(false);\n    } else {\n      syncIdleRotate();\n    }\n  });\n\n  function stageSetPill(pill) {\n    galleryStage.pill = pill;\n    g.stagePills.forEach((p) => p.classList.toggle('is-active', p.dataset.aicpGstage === pill));\n    renderStageView();\n  }\n\n  function stageModelKey(item) {\n    return item?.id || item?.modelUrl || null;\n  }\n\n  /* The stage still shows this design: compared by design key, because a\n     /history re-pull replaces the item objects while the design on stage\n     stays the same. */\n  function stageShows(item) {\n    const shown = galleryStage.item;\n    if (!shown || !item) return false;\n    if (shown === item) return true;\n    const key = stageModelKey(item);\n    return Boolean(key) && stageModelKey(shown) === key;\n  }\n\n  function stageModelIsReady(item) {\n    const key = stageModelKey(item);\n    return Boolean(key && galleryStage.loadedModelUrl === key);\n  }\n\n  /* The shopper must remain on the 2D preview until GLTFLoader has downloaded,\n     Meshopt-decoded and mounted the scene. Only then is the 3D pill enabled and\n     the automatic switch allowed. */\n  function syncPillState() {\n    const item = galleryStage.item;\n    const pill3d = g.stagePills.find((p) => p.dataset.aicpGstage === '3d');\n    if (pill3d) pill3d.disabled = !item?.modelUrl || !stageModelIsReady(item);\n  }\n\n  function renderStageView() {\n    const item = galleryStage.item;\n    if (!item) return;\n    syncPillState();\n    // Rotation only belongs on a live 3D preview; re-evaluate on every switch.\n    syncIdleRotate();\n    const img = g.stageFrame.querySelector('img');\n    if (galleryStage.pill === '3d') {\n      // Veil (not hide) the 2D image: display:none would remove the frame's\n      // only in-flow child and collapse the stage, since the 3D viewer is\n      // absolutely positioned.\n      if (img) img.classList.add('is-veiled');\n      if (!item.modelUrl) {\n        g.stageViewer.hidden = true;\n        g.stageStatus.hidden = false;\n        setStageStatusText('This design has no 3D model yet.');\n        return;\n      }\n      hideStageStatus();\n      renderStage3D(item);\n    } else {\n      g.stageViewer.hidden = true;\n      // A failed 3D keeps its notice (with Retry) under the 2D photo; the 3D\n      // pill is disabled, so the notice is the shopper's only way back to it.\n      if (!stageRetryAction) g.stageStatus.hidden = true;\n      if (img) {\n        img.classList.remove('is-veiled');\n        const src = pictureFor(item, 'stage');\n        if (src && img.src !== src) img.src = src;\n      }\n    }\n  }\n\n  async function renderStage3D(item) {\n    const ready = await ensureStageViewer();\n    if (!ready) {\n      g.stageStatus.hidden = false;\n      setStageStatusText('The 3D viewer could not be loaded. Please try again shortly.', { retry: () => retryStageModel(item), failure: 'viewer_load' });\n      return;\n    }\n    const ok = await loadStageModel(item);\n    if (ok) {\n      g.stageViewer.hidden = false;\n      galleryStage.viewer.resize();\n    }\n    // Viewer visibility is now settled \u2014 start/stop idle rotation accordingly.\n    syncIdleRotate();\n  }\n\n  /* Apply the independent base and engraving state. setBaseType only rebuilds\n     the base object around the already-loaded figurine; it never starts a new\n     model request. */\n  function syncViewerOptions({ snapToFront = false } = {}) {\n    const viewer = galleryStage.viewer;\n    if (!viewer) return;\n    const selectedBase = selectedBaseConfig();\n    if (!selectedBase) {\n      if (typeof viewer.setBaseType === 'function') viewer.setBaseType('none');\n      else if (typeof viewer.setBase === 'function') viewer.setBase(false);\n      if (typeof viewer.setEngraving === 'function') viewer.setEngraving('');\n      galleryStage.baseViewerKey = 'none';\n      galleryStage.legacyBaseModelKey = null;\n      syncIdleRotate();\n      return;\n    }\n    if (typeof viewer.setBaseModelUrl === 'function') {\n      const modelUrls = selectedBaseModelUrls();\n      viewer.setBaseModelUrl(selectedBase.viewerType, modelUrls[selectedBase.viewerType]);\n      galleryStage.baseViewerKey = selectedBaseViewerKey();\n    }\n    if (typeof viewer.setBaseType === 'function') {\n      viewer.setBaseType(selectedBase.viewerType);\n    } else if (typeof viewer.setBase === 'function') {\n      // The currently published creator-33 bundle only exposes setBase(bool).\n      // Keep its one legacy pedestal visible until the newly built bundle is\n      // deployed; it cannot distinguish the new round/square choices.\n      // Calling creator-33's setter repeatedly is not idempotent: it reapplies\n      // its camera lift every time. Apply it once, only after this model loaded.\n      const modelKey = galleryStage.loadedModelUrl;\n      if (modelKey && galleryStage.legacyBaseModelKey !== modelKey) {\n        viewer.setBase(true);\n        galleryStage.legacyBaseModelKey = modelKey;\n      }\n    } else {\n      return;\n    }\n    if (typeof viewer.setEngraving === 'function') viewer.setEngraving(activeEngraving());\n    if (activeEngraving()) {\n      setViewerAutoRotate(false);\n      if (snapToFront && typeof viewer.resetView === 'function') viewer.resetView();\n    }\n    syncIdleRotate();\n  }\n\n  async function ensureStageViewer() {\n    if (!g.stageViewer) return false;\n    const [ready] = await Promise.all([loadViewerBundle(), baseCatalogueReady]);\n    if (!ready) return false;\n    if (!galleryStage.viewer) {\n      try {\n      galleryStage.viewer = window.RealizeItViewer.createViewer(g.stageViewer, {\n        grid: false,\n        pan: false,\n        zoom: false,\n        mipmaps: false,\n        touchAction: 'pan-y',\n        // 0 = never auto-resume on its own; the idle controller drives rotation\n        // so its spin/pause cycle is not overridden by the viewer's timer.\n        autoRotateDelay: 0,\n        // People/pet used the viewer's full RoomEnvironment (1.0). Measured 2026-10-12 against the\n        // job's own 2D design, that lifted the figure ~23% and drained ~25% of its saturation (the\n        // \"washed-out\" Mini Me). 0.6 / 0.9 puts the 3D on the 2D's brightness.\n        // People and TTRPG: ACES still greyed light skin (cheek colourfulness about halved). Neutral\n        // tone mapping at 0.8 / 1.0 brings the shown skin closer to the 2D: median dE 11.1 -> 6.0 on\n        // 13 people runs, 15.5 -> 8.3 on 10 TTRPG runs (code/mythumi-3d-likeness-20261027, 10-27/28).\n        // Pets were not measured, so they keep ACES 0.6 / 0.9.\n        toneMapping: INITIAL_CAT === 'people' || INITIAL_CAT === 'trpg' ? 'neutral' : 'aces',\n        environmentIntensity: INITIAL_CAT === 'people' || INITIAL_CAT === 'trpg' ? 0.8 : 0.6,\n        toneMappingExposure: INITIAL_CAT === 'people' || INITIAL_CAT === 'trpg' ? 1.0 : 0.9,\n        cameraDistanceMultiplier: INITIAL_CAT === 'trpg' ? 0.9 : 1.45,\n        maxFrameCoverage: INITIAL_CAT === 'trpg' ? 1.08 : 0.98,\n        baseModelUrls: selectedBaseModelUrls(),\n      });\n      } catch (error) {\n        // No WebGL context (an in-app browser with graphics off, a lost GPU\u2026):\n        // the 2D stays usable and the notice offers a retry.\n        console.error('[realizeit] 3D viewer could not start:', error);\n        galleryStage.viewer = null;\n        return false;\n      }\n      galleryStage.baseViewerKey = selectedBaseViewerKey();\n      watchStageViewerContext(galleryStage.viewer);\n      syncViewerOptions({ snapToFront: Boolean(activeEngraving()) });\n    }\n    return true;\n  }\n\n  /* iOS takes a page's WebGL context back when the page goes to the background\n     or runs short of memory (a shopper who leaves the app while the 3D builds),\n     and hands it back later. three.js re-uploads meshes and textures then, but\n     the viewer's only light is an environment map rendered on the GPU, and that\n     comes back empty: the figure turns into a flat black silhouette (reproduced\n     2026-10-28 with WEBGL_lose_context). Build the viewer again instead; the\n     model comes from the blob cache. */\n  function watchStageViewerContext(viewer) {\n    const canvas = g.stageViewer?.querySelector('canvas');\n    if (!canvas) return;\n    canvas.addEventListener('webglcontextrestored', () => {\n      if (galleryStage.viewer === viewer) void refreshStageViewerForBase();\n    }, { once: true });\n  }\n\n  /* Retry button: the design is still on stage, so fetch its model again; the\n     stage switches to 3D by itself when the model lands. */\n  function retryStageModel(item) {\n    if (!item?.modelUrl || !stageShows(item)) return;\n    hideStageStatus();\n    if (galleryStage.pill === '3d') void renderStage3D(item);\n    else void preloadStage3D(item);\n  }\n\n  // A new viewer for the design on stage: after a base change, and after a lost\n  // WebGL context comes back (watchStageViewerContext).\n  async function refreshStageViewerForBase() {\n    if (!galleryStage.viewer) return;\n    const item = galleryStage.item;\n    galleryStage.viewer.dispose();\n    galleryStage.viewer = null;\n    galleryStage.loadedModelUrl = null;\n    galleryStage.legacyBaseModelKey = null;\n    galleryStage.baseViewerKey = null;\n    if (galleryStage.pill === '3d' && item?.modelUrl) await renderStage3D(item);\n  }\n\n  /* Meshy's metallic-roughness map leaves the figure satin (roughness ~0.44);\n     under the viewer's room environment that reads as a white sheen even at\n     0.6 intensity. Measured 2026-10-12 on a real Mini Me against its own 2D:\n     saturation 0.55 as delivered vs 0.61 in the 2D; a matte 0.85 material at\n     the same light gives 0.59 with the brightness unchanged. People/pet models\n     are therefore made matte here, in the GLB's JSON chunk, before the viewer\n     sees them: the metallic-roughness texture reference is dropped and the\n     factors set (metallicFactor must be 0 \u2014 glTF defaults it to 1). Only the\n     JSON chunk changes, so Meshopt buffers are untouched. Any surprise returns\n     the bytes as delivered. TTRPG keeps Meshy's materials (its 0.5 light was\n     tuned with them). To undo, empty MATTE_CATEGORIES. */\n  const MATTE_ROUGHNESS = 0.85;\n  const MATTE_CATEGORIES = ['people', 'pet'];\n  const GLB_MAGIC = 0x46546c67; // 'glTF'\n  const GLB_JSON_CHUNK = 0x4e4f534a; // 'JSON'\n\n  /* Pet head keychain (2026-10-14): these styles are generated without their\n     keyring (a ring drawn into the 2D makes Meshy build a flat pendant), so\n     the ring is added to the downloaded GLB in the browser instead; see\n     assets/ai3d-keyring.js. The module, and the Meshopt decoder it needs to\n     find the crown, load only for these designs. Any failure keeps the model\n     as delivered. To undo, empty KEYRING_STYLES. */\n  const KEYRING_STYLES = new Set(['keychain-pet-head']);\n  const KEYRING_MODULE_URL = root.dataset.aicpKeyringModule || '';\n\n  async function addKeyring(blob) {\n    if (!KEYRING_MODULE_URL) return blob;\n    try {\n      const { addKeyringToGlb } = await import(KEYRING_MODULE_URL);\n      const out = await addKeyringToGlb(await blob.arrayBuffer());\n      return out ? new Blob([out], { type: 'model/gltf-binary' }) : blob;\n    } catch (error) {\n      console.warn('[realizeit] model kept without its keyring:', error);\n      return blob;\n    }\n  }\n\n  async function prepareModelBlob(blob, { keyring = false } = {}) {\n    const matte = await matteModelBlob(blob);\n    return keyring ? addKeyring(matte) : matte;\n  }\n\n  async function matteModelBlob(blob) {\n    if (!MATTE_CATEGORIES.includes(INITIAL_CAT)) return blob;\n    try {\n      const buffer = await blob.arrayBuffer();\n      const view = new DataView(buffer);\n      if (buffer.byteLength < 20 || view.getUint32(0, true) !== GLB_MAGIC) return blob;\n      const jsonLength = view.getUint32(12, true);\n      if (view.getUint32(16, true) !== GLB_JSON_CHUNK || 20 + jsonLength > buffer.byteLength) return blob;\n      const json = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer, 20, jsonLength)));\n      let changed = false;\n      (Array.isArray(json.materials) ? json.materials : []).forEach((material) => {\n        const pbr = material && material.pbrMetallicRoughness;\n        if (!pbr) return;\n        if (pbr.metallicRoughnessTexture) {\n          delete pbr.metallicRoughnessTexture;\n          changed = true;\n        }\n        if (pbr.roughnessFactor !== MATTE_ROUGHNESS) {\n          pbr.roughnessFactor = MATTE_ROUGHNESS;\n          changed = true;\n        }\n        if (pbr.metallicFactor !== 0) {\n          pbr.metallicFactor = 0;\n          changed = true;\n        }\n      });\n      if (!changed) return blob;\n      let encoded = new TextEncoder().encode(JSON.stringify(json));\n      const pad = (4 - (encoded.length % 4)) % 4;\n      if (pad) {\n        const padded = new Uint8Array(encoded.length + pad);\n        padded.set(encoded);\n        padded.fill(0x20, encoded.length);\n        encoded = padded;\n      }\n      const rest = new Uint8Array(buffer, 20 + jsonLength);\n      const out = new Uint8Array(20 + encoded.length + rest.length);\n      out.set(new Uint8Array(buffer, 0, 12), 0);\n      const outView = new DataView(out.buffer);\n      outView.setUint32(8, out.length, true);\n      outView.setUint32(12, encoded.length, true);\n      outView.setUint32(16, GLB_JSON_CHUNK, true);\n      out.set(encoded, 20);\n      out.set(rest, 20 + encoded.length);\n      return new Blob([out], { type: 'model/gltf-binary' });\n    } catch (error) {\n      console.warn('[realizeit] model kept as delivered (matte pass skipped):', error);\n      return blob;\n    }\n  }\n\n  /* Stream the GLB to a blob URL with a real download percentage. The viewer's\n     loadAsync gives no progress, so we fetch manually (Content-Length + reader)\n     and hand the finished blob to the viewer instead. Returns the blob URL, or\n     { error } with a reason class on failure. */\n  async function downloadModelWithProgress(modelUrl, onProgress, prepare = {}) {\n    // Watchdog: no response, or no new bytes, for MODEL_DOWNLOAD_STALL_MS\n    // aborts the fetch so the caller can retry instead of waiting for ever.\n    const controller = typeof AbortController === 'function' ? new AbortController() : null;\n    let stallTimer = 0;\n    let stalled = false;\n    const watch = () => {\n      if (!controller) return;\n      clearTimeout(stallTimer);\n      stallTimer = setTimeout(() => {\n        stalled = true;\n        controller.abort();\n      }, MODEL_DOWNLOAD_STALL_MS);\n    };\n    try {\n      watch();\n      const response = await fetch(modelUrl, controller ? { signal: controller.signal } : undefined);\n      if (!response.ok) {\n        // 410 = the free preview's model was removed for good (handoff); 403 =\n        // a signed link that timed out and is refreshed by re-opening the gallery.\n        const kind = response.status === 410 ? 'gone' : response.status === 403 ? 'expired' : 'network';\n        return { error: kind, status: response.status, message: `Request failed (${response.status})` };\n      }\n      const total = Number(response.headers.get('Content-Length')) || 0;\n      if (!response.body || typeof response.body.getReader !== 'function') {\n        // No streaming support (old engines): one blob, no stall detection.\n        clearTimeout(stallTimer);\n        const blob = await response.blob();\n        onProgress?.({ percent: 100, received: blob.size, total: blob.size });\n        return { blobUrl: URL.createObjectURL(await prepareModelBlob(blob, prepare)) };\n      }\n      // Streamed whether or not the size is known: the proxy sends no\n      // Content-Length, and the bytes still have to keep coming.\n      const reader = response.body.getReader();\n      const chunks = [];\n      let received = 0;\n      for (;;) {\n        watch();\n        const { done, value } = await reader.read();\n        if (done) break;\n        chunks.push(value);\n        received += value.length;\n        onProgress?.({\n          percent: total ? Math.min(100, Math.round((received / total) * 100)) : null,\n          received,\n          total,\n        });\n      }\n      clearTimeout(stallTimer);\n      const blob = await prepareModelBlob(new Blob(chunks, { type: 'model/gltf-binary' }), prepare);\n      return { blobUrl: URL.createObjectURL(blob) };\n    } catch (error) {\n      if (stalled) return { error: 'stalled', message: `No data for ${Math.round(MODEL_DOWNLOAD_STALL_MS / 1000)} s` };\n      return { error: 'network', message: String(error?.message || error || 'Failed to fetch') };\n    } finally {\n      clearTimeout(stallTimer);\n    }\n  }\n\n  /* A dropped connection, a stalled stream or a 5xx from the proxy is fetched\n     again a couple of times before the shopper sees an error; an expired (403)\n     or removed (410) model is not. */\n  async function downloadModelWithRetries(modelUrl, onProgress, prepare) {\n    let result = null;\n    for (let attempt = 0; attempt <= MODEL_DOWNLOAD_RETRIES; attempt += 1) {\n      if (attempt) {\n        onProgress?.({ retrying: attempt });\n        await new Promise((resolve) => setTimeout(resolve, MODEL_DOWNLOAD_RETRY_DELAY_MS * attempt));\n      }\n      result = await downloadModelWithProgress(modelUrl, onProgress, prepare);\n      if (!result.error || (result.error !== 'network' && result.error !== 'stalled')) return result;\n      console.warn(`[realizeit] 3D model download failed (attempt ${attempt + 1}/${MODEL_DOWNLOAD_RETRIES + 1}): ${result.message || result.error}`);\n    }\n    return result;\n  }\n\n  /* One load per design at a time: the auto-selected first card, a view\n     switch and a card tap can all ask for the same model within a second, and\n     each used to start its own download of the same GLB. Later callers share\n     the first load's outcome. */\n  const modelLoadsInFlight = new Map(); // design key -> Promise<boolean>\n\n  /* Crash-reload detection. A phone that runs out of memory decoding a model\n     kills the tab and reloads the page with no error anywhere. A marker is set\n     while a model loads and cleared when it settles or the page is left\n     normally (pagehide); finding it at start-up means the last load never\n     settled, so the tab most likely crashed. */\n  const MODEL_LOAD_MARKER = 'mythumi:3d-loading';\n  function markModelLoad(jobId) {\n    try { sessionStorage.setItem(MODEL_LOAD_MARKER, JSON.stringify({ jobId: jobId ?? null, at: Date.now() })); } catch (_) {}\n  }\n  function clearModelLoad() {\n    try { sessionStorage.removeItem(MODEL_LOAD_MARKER); } catch (_) {}\n  }\n  window.addEventListener('pagehide', clearModelLoad);\n\n  /* After a crash the 3D waits to be asked for (2026-10-27). Loading the same\n     model again on its own would likely close the page again, round and round.\n     For the rest of this tab's session a design shows its 2D with a View 3D\n     button (showThreeDPaused); a model that then loads fine lifts the hold. */\n  const THREE_D_PAUSED_KEY = 'mythumi:3d-paused';\n  let threeDPaused = false;\n  try {\n    threeDPaused = sessionStorage.getItem(THREE_D_PAUSED_KEY) === '1';\n  } catch (_) {}\n  function setThreeDPaused(paused) {\n    threeDPaused = paused;\n    try {\n      if (paused) sessionStorage.setItem(THREE_D_PAUSED_KEY, '1');\n      else sessionStorage.removeItem(THREE_D_PAUSED_KEY);\n    } catch (_) {}\n  }\n  try {\n    const crashed = JSON.parse(sessionStorage.getItem(MODEL_LOAD_MARKER) || 'null');\n    clearModelLoad();\n    if (crashed && Date.now() - crashed.at < 30 * 60 * 1000) {\n      setThreeDPaused(true);\n      clarityEvent('mythumi_3d_crash_reload', crashed.jobId, { secondsAgo: Math.round((Date.now() - crashed.at) / 1000) });\n    }\n  } catch (_) {}\n\n  function showThreeDPaused(item) {\n    setStage3DProgress(false);\n    g.stageStatus.hidden = false;\n    setStageStatusText('Showing your 2D design so this page stays open. Tap View 3D to load the 3D preview.', {\n      retry: () => {\n        setThreeDPaused(false);\n        retryStageModel(item);\n      },\n      retryLabel: 'View 3D',\n      quiet: true,\n    });\n    clarityEvent('mythumi_3d_paused', item.id);\n  }\n\n  function loadStageModel(item) {\n    if (!item?.modelUrl) return Promise.resolve(false);\n    const key = stageModelKey(item);\n    const running = key ? modelLoadsInFlight.get(key) : null;\n    if (running) return running;\n    const task = loadStageModelOnce(item).finally(() => {\n      if (key && modelLoadsInFlight.get(key) === task) modelLoadsInFlight.delete(key);\n    });\n    if (key) modelLoadsInFlight.set(key, task);\n    return task;\n  }\n\n  /* Loads the model with the on-stage progress bar. Failure reasons are\n     distinguished: an HTTP 403 from the signed URL really means expiry,\n     anything else (parse error, network) gets its own message. */\n  async function loadStageModelOnce(item) {\n    if (!item.modelUrl) return false;\n    // Viewer already shows this exact design \u2014 nothing to do.\n    if (item.id && galleryStage.loadedModelUrl === item.id) {\n      if (stageShows(item)) setStage3DProgress(false);\n      return true;\n    }\n    // Cache hit keyed on the stable id: the signed URL rotates on every\n    // /history pull, so the same design must not re-download.\n    const cached = item.id ? modelBlobCache.get(item.id) : null;\n    const stageProgress = createModelStageProgress(item);\n    markModelLoad(item.id);\n    try {\n      // Provider generation already filled 0\u201388%; start the browser-side\n      // download/decode/render tail without replaying a fake loading phase.\n      stageProgress.start();\n      let blobUrl = cached?.blobUrl;\n      if (!blobUrl) {\n        // Map the real byte count into 88\u201398%.\n        const result = await downloadModelWithRetries(item.modelUrl, (progress) => {\n          stageProgress.downloading(progress);\n        }, { keyring: KEYRING_STYLES.has(item.style) });\n        if (result.error) throw Object.assign(new Error(result.error === 'expired' ? '403 Forbidden' : result.error === 'gone' ? '410 Gone' : (result.message || 'Failed to fetch')), { modelError: result.error });\n        blobUrl = result.blobUrl;\n        cacheModelBlob(item.id, blobUrl, item.provider);\n      } else {\n        stageProgress.downloading(100);\n      }\n      // Meshopt exposes no partial decode progress. Keep 98% until the decoded scene is mounted.\n      stageProgress.decoding();\n      // The stage viewer is shared: a design no longer on stage (a background\n      // run, or a card the shopper has since left) must not replace the scene\n      // under the design being shown. Its blob stays cached for later.\n      if (!stageShows(item)) return false;\n      galleryStage.legacyBaseModelKey = null;\n      await galleryStage.viewer.loadModel(blobUrl, { provider: item.provider });\n      if (!stageShows(item)) return false;\n      galleryStage.loadedModelUrl = stageModelKey(item);\n      // A freshly loaded scene starts without base/engraving \u2014 re-apply the\n      // shopper's current choices on top of it.\n      syncViewerOptions();\n      // loadModel resolves after parsing and scene insertion. Wait through two\n      // paints so the model and its selected base are rendered before 100%.\n      await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));\n      if (!stageShows(item)) return false;\n      stageProgress.complete();\n      // Let the 98\u2192100% transition finish before the strip disappears.\n      await new Promise((resolve) => setTimeout(resolve, MODEL_COMPLETE_HOLD_MS));\n      // AICP-TIMING: 3D is decoded and on screen \u2014 the model is now usable.\n      // TOTAL = photo upload \u2192 this moment (first model of a user-initiated run).\n      if (AICP_TIMING.armTotal && !AICP_TIMING.totalLogged && AICP_TIMING.uploadStart) {\n        AICP_TIMING.totalLogged = true;\n        const who = item.styleLabel ? ` \u2014 ${item.styleLabel}` : '';\n        aicpTimingLog(`TOTAL (upload \u2192 3D usable)${who}`, Date.now() - AICP_TIMING.uploadStart);\n      }\n      // A design made in this visit is now loaded in the 3D viewer.\n      if (item.id != null && clarityJobs.has(String(item.id))) clarityEvent('mythumi_3d_ready', item.id);\n      if (threeDPaused) setThreeDPaused(false);\n      return true;\n    } catch (error) {\n      const isCurrentItem = stageShows(item);\n      if (isCurrentItem) galleryStage.loadedModelUrl = null;\n      // A cached blob can go stale if the entry was evicted or the model failed\n      // to parse; drop it so the next attempt re-downloads a fresh copy.\n      dropCachedModel(item.id);\n      if (isCurrentItem) {\n        g.stageViewer.hidden = true;\n        g.stageStatus.hidden = false;\n      }\n      // GLTFLoader surfaces expired signed URLs as HTTP 403 fetch errors.\n      const message = String(error?.message || error || '');\n      if (!isCurrentItem) return false;\n      const retry = () => retryStageModel(item);\n      if (error?.modelError === 'gone') {\n        // Do not keep re-requesting a model that is gone; the 2D stays usable.\n        setStageStatusText('This free 3D preview has expired.');\n      } else if (error?.modelError === 'expired' || /403|Forbidden/i.test(message)) {\n        setStageStatusText('The 3D preview link expired. Reopen the gallery to refresh it.');\n      } else if (error?.modelError === 'stalled') {\n        setStageStatusText('The 3D model download stalled. Check your connection and retry.', { retry, failure: 'download_stalled' });\n      } else if (error?.modelError === 'network' || NETWORK_ERROR_RE.test(message)) {\n        setStageStatusText('The 3D model could not be downloaded. Check your connection and retry.', { retry, failure: 'download_failed' });\n      } else {\n        // Parse/decoding failure \u2014 usually an oversized or unsupported export.\n        // The raw error goes under the message so a screenshot tells support why.\n        console.error('[realizeit] 3D model failed to load:', error);\n        const name = error?.name && error.name !== 'Error' ? `${error.name}: ` : '';\n        setStageStatusText('The 3D model could not be displayed. Retry, or contact support if this keeps happening.', {\n          detail: `${name}${message}`.slice(0, 140),\n          retry,\n          failure: 'render_failed',\n        });\n      }\n      return false;\n    } finally {\n      clearModelLoad();\n      stageProgress.stop();\n      // A stale preload must not hide the progress for the item now on stage.\n      if (stageShows(item)) setStage3DProgress(false);\n    }\n  }\n\n  /* 2D stays on screen while the 3D loads in the background; the stage\n     switches to 3D by itself once the model is in. */\n  async function preloadStage3D(item) {\n    if (!item.modelUrl) return;\n    if (threeDPaused) {\n      if (stageShows(item)) showThreeDPaused(item);\n      return;\n    }\n    // The visit's first gallery shows its 2D cards before the viewer script and\n    // the model start downloading (capped; later calls pass straight through).\n    if (firstCardsPainted) await firstCardsPainted;\n    const ready = await ensureStageViewer();\n    if (!ready) {\n      if (stageShows(item)) {\n        setStage3DProgress(false);\n        g.stageStatus.hidden = false;\n        setStageStatusText('The 3D viewer could not be loaded. Please try again shortly.', { retry: () => retryStageModel(item), failure: 'viewer_load' });\n      }\n      return;\n    }\n    const ok = await loadStageModel(item);\n    if (ok && stageShows(item)) stageSetPill('3d');\n    // First visible frame of this run's model (the third client timing):\n    // the stage has switched to 3D and painted twice. A model that stays\n    // hidden (the shopper moved to another design) records nothing.\n    const renderMetrics = metricsFor(item.id);\n    if (renderMetrics && !renderMetrics.trender) {\n      if (ok) {\n        await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));\n        const galleryView = root.querySelector('[data-aicp-view=\"gallery\"]');\n        if (stageShows(item) && galleryStage.pill === '3d' && g.stageViewer && !g.stageViewer.hidden\n          && galleryView && !galleryView.hidden) {\n          renderMetrics.trender = performance.now();\n        }\n      }\n      // Sent now either way: a model that failed or stayed hidden has no render time.\n      sendClientMetrics(item.id);\n    }\n  }\n\n  g.stagePills.forEach((pill) => {\n    pill.addEventListener('click', () => stageSetPill(pill.dataset.aicpGstage));\n  });\n\n  if (g.stageRetry) {\n    g.stageRetry.addEventListener('click', () => {\n      const action = stageRetryAction;\n      hideStageStatus();\n      if (typeof action === 'function') action();\n    });\n  }\n\n  g.trash.forEach((button) => {\n    button.addEventListener('click', () => {\n      button.closest('.aicp-g-history__card')?.remove();\n      syncHistoryVisibility();\n    });\n  });\n\n  if (g.name && g.nameCount) {\n    // 2026-10-27 (Drew): spaces are allowed (\"Sir Lancelot\"). The field keeps what\n    // was typed; writing the trimmed value back on every keystroke ate a space\n    // before the next word could follow it. Only the engraving itself is trimmed.\n    const syncName = () => {\n      const typed = g.name.value.slice(0, MAX_ENGRAVING_CHARS);\n      if (g.name.value !== typed) g.name.value = typed;\n      basePreview.engraving = typed.trim();\n      g.nameCount.textContent = `${typed.length}/${MAX_ENGRAVING_CHARS}`;\n    };\n    syncName();\n    g.name.addEventListener('input', () => {\n      syncName();\n      if (basePreview.engraving) ensureBaseForEngraving();\n      // Live CanvasTexture preview; text also intentionally pauses the idle spin.\n      syncViewerOptions({ snapToFront: Boolean(activeEngraving()) });\n    });\n  }\n\n  // 2026-10-13 (Drew): the name and the memorial line are engraved on the base,\n  // so filling either in while \"No base\" is chosen picks the first base, through\n  // its own click handler (variant, line properties and 3D follow).\n  function ensureBaseForEngraving() {\n    if (selectedBaseConfig()) return;\n    const first = [...root.querySelectorAll('.aicp-g-base input')].find((radio) => (\n      radio.value !== NO_BASE_ID && !radio.disabled && !radio.closest('.aicp-g-base')?.hidden\n      && engravableBase(basePreview.catalogue.bases.find((base) => base.id === radio.value))\n    ));\n    first?.click();\n  }\n  root.querySelector('[data-ai3d-g-memorial]')?.addEventListener('change', (event) => {\n    if (event.target.checked) ensureBaseForEngraving();\n  });\n\n  // Display base is a Shopify product option. It remains selected like a\n  // normal radio group because its color/model can change price and inventory.\n  root.querySelectorAll('.aicp-g-base input').forEach((radio) => {\n    radio.addEventListener('click', async () => {\n      if (radio.disabled) return;\n      radio.checked = true;\n      basePreview.touched = true;\n      basePreview.baseType = radio.value;\n      // 2026-10-15 (Drew): the memorial line (\"Forever loved\") is engraved on a\n      // base, so choosing \"No base\" takes the memorial tick off. Ticking it again\n      // picks the first engravable base (ensureBaseForEngraving).\n      if (radio.value === NO_BASE_ID) {\n        const memorial = root.querySelector('[data-ai3d-g-memorial]');\n        if (memorial) memorial.checked = false;\n      }\n      selectedProductOptions.set('base', radio.dataset.aicpOptionValue || '');\n      syncProductVariant();\n      if (galleryStage.viewer\n        && basePreview.baseType\n        && galleryStage.baseViewerKey !== selectedBaseViewerKey()\n        && typeof galleryStage.viewer.setBaseModelUrl !== 'function') {\n        await refreshStageViewerForBase();\n      } else {\n        syncViewerOptions({ snapToFront: Boolean(activeEngraving()) });\n      }\n    });\n  });\n\n  // Detail content is composed from independent Shopify sections. Keep the\n  // gallery state in this creator root, but discover the editorial sections\n  // from the document so Theme Editor ordering does not affect the wiring.\n  const detailScope = document;\n  const details = {\n    nav: [...detailScope.querySelectorAll('[data-aicp-detail-nav]')],\n    sections: [...detailScope.querySelectorAll('[data-aicp-detail-section]')],\n    sizeButtons: [...detailScope.querySelectorAll('[data-aicp-detail-size]')],\n    sizeGuide: detailScope.querySelector('.aicp-size-guide'),\n    sizeLabel: detailScope.querySelector('[data-aicp-detail-size-label]'),\n    sizeCategory: detailScope.querySelector('[data-aicp-detail-size-category]'),\n    sizeDescription: detailScope.querySelector('[data-aicp-detail-size-description]'),\n    sizeOptions: detailScope.querySelector('[data-aicp-detail-size-options]'),\n    figureLabel: detailScope.querySelector('[data-aicp-detail-figure-label]'),\n    figureCategory: detailScope.querySelector('[data-aicp-detail-figure-category]'),\n    figureImage: detailScope.querySelector('[data-aicp-detail-figure-image]'),\n    phone: detailScope.querySelector('.aicp-size-guide__phone'),\n    back: detailScope.querySelector('[data-aicp-detail-back]'),\n    photoButtons: [...detailScope.querySelectorAll('[data-aicp-detail-photo]')],\n    dialog: root.querySelector('[data-aicp-detail-dialog]'),\n    dialogTitle: root.querySelector('[data-aicp-detail-dialog-title]'),\n    dialogImage: root.querySelector('[data-aicp-detail-dialog-image]'),\n    dialogClose: root.querySelector('[data-aicp-detail-dialog-close]'),\n  };\n\n  // Shopify product options are the source of truth for purchasable choices.\n  // Theme settings still own presentation (base images/GLBs and finish copy),\n  // but never create a sellable value or price by themselves.\n  const productSizeOption = productOptionByKey.get('size') || null;\n  const productBaseOption = productOptionByKey.get('base') || null;\n  const productFinishOption = productOptionByKey.get('finish') || null;\n  const currentSizeCategory = () => (INITIAL_CAT === 'pet' || INITIAL_CAT === 'trpg') ? INITIAL_CAT : 'people';\n  // 2026-09-28 (Drew): when the product has no Size option the picker still\n  // shows, using the section's own size list. Those labels reach the order as\n  // the Size line property only; variant matching ignores options the\n  // product does not have (see variantMatchesSelections).\n  const fallbackSizeOptions = g.sizeButtons\n    .map((button) => button.dataset.aicpGSizeLabel || button.textContent.trim())\n    .filter(Boolean);\n  const currentSizeOptions = () => productSizeOption?.values || fallbackSizeOptions;\n  const categoryCopy = {\n    pet: {\n      name: 'Pet',\n      description: 'Compare your pet figurine with an iPhone. Larger pet sizes include hand finishing.',\n      image: root.dataset.aicpPetFigureUrl || root.dataset.aicpPeopleFigureUrl,\n      alt: 'Illustrated pet figurine',\n    },\n    people: {\n      name: 'People',\n      description: 'Compare portrait height with an iPhone. Base dimensions are specified separately.',\n      image: root.dataset.aicpPeopleFigureUrl,\n      alt: 'Illustrated people figurine',\n    },\n    trpg: {\n      name: 'TRPG',\n      description: 'Compare figurine height with an iPhone. Base dimensions are specified separately.',\n      image: root.dataset.aicpPeopleFigureUrl,\n      alt: 'Illustrated figurine',\n    },\n  };\n\n  const sizeInches = (label) => {\n    const value = Number.parseFloat(String(label));\n    // Shopify option values are commonly compact (for example `6cm`). A\n    // leading word boundary does not exist between the digit and `c`, so the\n    // old /\\bcm\\b/ check treated centimetres as inches and displayed 6cm as\n    // 15 cm \u00B7 6 in. Match the unit at the end instead while keeping the raw\n    // Shopify label as the value used for exact variant selection.\n    return /cm\\b/i.test(String(label)) ? value / 2.54 : value;\n  };\n  const formatInches = (inches) => `${Number(inches.toFixed(2))} in`;\n  const formatCentimeters = (inches) => `${Math.round(inches * 2.54)} cm`;\n  const formatSizeOption = (label) => {\n    const inches = sizeInches(label);\n    return Number.isFinite(inches) ? `${formatCentimeters(inches)} \u00B7 ${formatInches(inches)}` : label;\n  };\n  const defaultGalleryPrice = g.priceNow?.textContent.trim() || '';\n  const defaultGalleryComparePrice = g.priceWas?.textContent.trim() || '';\n\n  const variantMatchesSelections = (variant, selections) => (\n    PRODUCT_CATALOGUE.options.every((option, index) => {\n      if (option.key === 'title' && option.values.length === 1 && option.values[0] === 'Default Title') return true;\n      if (!SUPPORTED_PRODUCT_OPTIONS.has(option.key)) return false;\n      const selected = selections.get(option.key);\n      return selected && normalizeShopifyOptionValue(variant.options[index]) === normalizeShopifyOptionValue(selected);\n    })\n  );\n\n  const findVariantForSelections = (selections, availableOnly = false) => (\n    PRODUCT_CATALOGUE.variants.find((variant) => (\n      (!availableOnly || variant.available) && variantMatchesSelections(variant, selections)\n    )) || null\n  );\n\n  const hasAvailableVariantForValue = (key, value) => {\n    const optionIndex = PRODUCT_CATALOGUE.options.findIndex((option) => option.key === key);\n    // An option the product does not have cannot make a value unavailable.\n    if (optionIndex < 0) return true;\n    return PRODUCT_CATALOGUE.variants.some((variant) => (\n      variant.available\n      && normalizeShopifyOptionValue(variant.options[optionIndex]) === normalizeShopifyOptionValue(value)\n    ));\n  };\n\n  const syncGalleryVariantPrice = (variant) => {\n    if (g.priceNow) {\n      g.priceNow.textContent = variant?.priceText\n        || (PRODUCT_CATALOGUE.variants.length ? 'Unavailable' : defaultGalleryPrice);\n    }\n    if (g.priceWas) {\n      g.priceWas.textContent = variant?.compareAtPriceText\n        || (!PRODUCT_CATALOGUE.variants.length ? defaultGalleryComparePrice : '');\n      g.priceWas.hidden = !g.priceWas.textContent.trim();\n    }\n  };\n\n  const syncProductOptionAvailability = () => {\n    g.sizeButtons.forEach((button) => {\n      const value = button.dataset.aicpGSizeLabel || '';\n      button.disabled = !hasAvailableVariantForValue('size', value);\n    });\n    root.querySelectorAll('.aicp-g-base input[data-aicp-option-value]').forEach((radio) => {\n      radio.disabled = !hasAvailableVariantForValue('base', radio.dataset.aicpOptionValue || '');\n      radio.closest('.aicp-g-base')?.classList.toggle('is-disabled', radio.disabled);\n    });\n    g.finish.forEach((button) => {\n      if (button.hidden) return;\n      button.disabled = !hasAvailableVariantForValue('finish', button.dataset.aicpGFinishValue || '');\n    });\n  };\n\n  function syncProductVariant() {\n    selectedProductVariant = unsupportedProductOptions.length\n      ? null\n      : findVariantForSelections(selectedProductOptions);\n    syncGalleryVariantPrice(selectedProductVariant);\n    syncProductOptionAvailability();\n    syncEngravingAvailability();\n    if (selectedProductVariant?.id) {\n      const nextUrl = new URL(window.location.href);\n      nextUrl.searchParams.set('variant', String(selectedProductVariant.id));\n      window.history.replaceState(window.history.state, '', nextUrl);\n    }\n    syncCheckoutGate();\n  }\n\n  const setSizeSelection = (index) => {\n    const selected = g.sizeButtons[index];\n    if (!selected || selected.disabled) return;\n    g.sizeButtons.forEach((button, buttonIndex) => {\n      const active = buttonIndex === index;\n      button.classList.toggle('is-active', active);\n      button.setAttribute('aria-pressed', String(active));\n    });\n    g.customSize?.classList.remove('is-active');\n    g.customSize?.setAttribute('aria-pressed', 'false');\n    const selectedLabel = selected.dataset.aicpGSizeLabel || selected.textContent.trim();\n    selectedProductOptions.set('size', selectedLabel);\n    const selectedInches = sizeInches(selectedLabel);\n    details.sizeButtons.forEach((button) => {\n      const detailInches = sizeInches(button.textContent);\n      const active = Number.isFinite(selectedInches) && Math.abs(detailInches - selectedInches) < 0.01;\n      button.classList.toggle('is-active', active);\n      button.setAttribute('aria-pressed', String(active));\n    });\n    details.sizeGuide?.setAttribute('data-aicp-detail-selected-size', String(index));\n    syncProductVariant();\n    const inches = selectedInches;\n    if (details.sizeLabel) details.sizeLabel.textContent = Number.isFinite(inches)\n      ? `${formatCentimeters(inches)} / ${formatInches(inches)}`\n      : selectedLabel;\n    if (details.figureLabel) details.figureLabel.textContent = Number.isFinite(inches)\n      ? formatInches(inches)\n      : selectedLabel;\n    const desktop = window.matchMedia?.('(min-width: 990px)').matches;\n    const basePhoneHeight = desktop ? 460 : 365;\n    const maxFigureHeight = desktop ? 460 : 365;\n    // On mobile, the three smallest catalog sizes are below 54px when shown\n    // beside the phone. Keep their real proportions instead of flattening\n    // 1.57 / 1.97 / 2.36 in into the same minimum height.\n    const minFigureHeight = desktop ? 54 : 36;\n    const scale = Number.isFinite(inches) ? inches / 5.81 : 1;\n    const isPet = currentSizeCategory() === 'pet';\n    const petStandardSizes = isPet\n      ? currentSizeOptions().map(sizeInches).filter((size) => Number.isFinite(size) && size <= 5.81)\n      : [];\n    const petReferenceInches = petStandardSizes.length ? Math.max(...petStandardSizes) : 5.81;\n    const holdPetFigure = isPet && Number.isFinite(inches) && inches > petReferenceInches;\n    const petFigureHeight = Math.round(Math.min(maxFigureHeight, basePhoneHeight * (petReferenceInches / 5.81)));\n    const minPhoneHeight = holdPetFigure ? (desktop ? 120 : 96) : (desktop ? 180 : 160);\n    const phoneHeight = holdPetFigure\n      ? Math.max(minPhoneHeight, Math.min(basePhoneHeight, petFigureHeight / Math.max(scale, 0.01)))\n      : Math.max(desktop ? 180 : 160, Math.min(basePhoneHeight, maxFigureHeight / Math.max(scale, 0.01)));\n    const figureHeight = holdPetFigure\n      ? petFigureHeight\n      : Number.isFinite(inches)\n      ? Math.max(minFigureHeight, Math.min(maxFigureHeight, Math.round(scale * phoneHeight)))\n      : 120;\n    if (details.figureImage) {\n      const naturalFigureHeight = details.figureImage.offsetHeight || basePhoneHeight;\n      const figureScale = Math.max(0.01, Math.min(1, figureHeight / naturalFigureHeight));\n      details.sizeGuide?.style.setProperty('--aicp-figure-scale', String(figureScale));\n      details.figureImage.dataset.aicpSizeReady = 'true';\n    }\n    if (details.phone) {\n      const previousHeight = details.phone.getBoundingClientRect().height;\n      const animatePhone = details.phone.dataset.aicpSizeReady === 'true'\n        && window.gsap\n        && !window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n      window.gsap?.killTweensOf(details.phone);\n      details.phone.style.height = `${Math.round(phoneHeight)}px`;\n      details.phone.style.width = `${Math.round(phoneHeight * 0.478)}px`;\n      details.phone.style.setProperty('--aicp-phone-notch-width', `${Math.max(18, Math.round(phoneHeight * 0.18))}px`);\n      details.phone.style.setProperty('--aicp-phone-notch-height', `${Math.max(4, Math.round(phoneHeight * 0.035))}px`);\n      details.phone.style.setProperty('--aicp-phone-time-size', `${Math.max(18, Math.round(phoneHeight * 0.105))}px`);\n      details.phone.style.setProperty('--aicp-phone-name-size', `${Math.max(11, Math.round(phoneHeight * 0.043))}px`);\n      if (animatePhone && previousHeight > 0 && Math.abs(previousHeight - phoneHeight) > 1) {\n        window.gsap.fromTo(details.phone, {\n          scale: previousHeight / phoneHeight,\n          transformOrigin: 'bottom center',\n        }, {\n          scale: 1,\n          duration: 0.42,\n          ease: 'power2.out',\n          overwrite: true,\n          clearProps: 'transform',\n        });\n      }\n      details.phone.dataset.aicpSizeReady = 'true';\n    }\n  };\n\n  const createSizeButton = (label, index, detail) => {\n    const button = document.createElement('button');\n    button.type = 'button';\n    button.textContent = formatSizeOption(label);\n    if (detail) {\n      button.dataset.aicpDetailSize = String(index);\n      button.setAttribute('aria-pressed', 'false');\n    } else {\n      button.className = 'aicp-g-size';\n      button.dataset.aicpGSize = '';\n      button.dataset.aicpGSizeLabel = label;\n      button.setAttribute('aria-pressed', 'false');\n    }\n    button.addEventListener('click', () => {\n      if (!detail) return setSizeSelection(index);\n      const inches = sizeInches(label);\n      const galleryIndex = g.sizeButtons.findIndex((sizeButton) => (\n        Math.abs(sizeInches(sizeButton.dataset.aicpGSizeLabel || sizeButton.textContent) - inches) < 0.01\n      ));\n      setSizeSelection(galleryIndex >= 0 ? galleryIndex : index);\n    });\n    return button;\n  };\n\n  const renderCategorySizeControls = () => {\n    const category = currentSizeCategory();\n    const galleryOptions = currentSizeOptions();\n    const copy = categoryCopy[category] || categoryCopy.people;\n    if (g.sizeControl) g.sizeControl.hidden = false;\n    if (!galleryOptions.length) {\n      g.sizeGrid?.replaceChildren();\n      details.sizeOptions?.replaceChildren();\n      g.sizeButtons = [];\n      details.sizeButtons = [];\n      selectedProductOptions.delete('size');\n      return;\n    }\n\n    if (g.sizeGrid) {\n      g.sizeGrid.replaceChildren(...galleryOptions.map((label, index) => createSizeButton(label, index, false)));\n      g.sizeButtons = [...g.sizeGrid.querySelectorAll('[data-aicp-g-size]')];\n    }\n    if (details.sizeOptions) {\n      details.sizeOptions.replaceChildren(...galleryOptions.map((label, index) => createSizeButton(label, index, true)));\n      details.sizeButtons = [...details.sizeOptions.querySelectorAll('[data-aicp-detail-size]')];\n    }\n    if (details.sizeCategory) details.sizeCategory.textContent = `${copy.name} \u00B7 Size & Scale`;\n    if (details.sizeDescription) details.sizeDescription.textContent = copy.description;\n    if (details.figureCategory) details.figureCategory.textContent = copy.name;\n    if (details.figureImage && copy.image) {\n      details.figureImage.src = copy.image;\n      details.figureImage.alt = copy.alt;\n    }\n    const selectedSize = selectedProductOptions.get('size');\n    const selectedIndex = galleryOptions.findIndex((value) => (\n      normalizeShopifyOptionValue(value) === normalizeShopifyOptionValue(selectedSize)\n    ));\n    // 2026-09-29 (Drew): the section can name a default position (1-based)\n    // for products that do not preselect a size, e.g. \"4 in \u00B7 Most loved\".\n    const configuredDefault = Number(g.sizeGrid?.dataset.aicpGSizeDefault) || 1;\n    const fallbackIndex = Math.min(galleryOptions.length - 1, Math.max(0, configuredDefault - 1));\n    const defaultIndex = selectedIndex >= 0 ? selectedIndex : fallbackIndex;\n    setSizeSelection(defaultIndex);\n  };\n\n  const syncBaseOptionControls = () => {\n    if (g.baseControl) g.baseControl.hidden = false;\n    if (!productBaseOption) {\n      // No Base option on the product: the panel stays visible and optional\n      // (the merchant's default base, if any, stays checked). The choice\n      // reaches the order as line properties only, \"No base\" included.\n      const checked = root.querySelector('.aicp-g-base input:checked');\n      if (checked?.value === NO_BASE_ID) selectedProductOptions.set('base', checked.dataset.aicpOptionValue || '');\n      return;\n    }\n    const currentValue = selectedProductOptions.get('base');\n    let selectedRadio = null;\n    root.querySelectorAll('.aicp-g-base input[data-aicp-option-value]').forEach((radio) => {\n      const configuredValue = radio.dataset.aicpOptionValue || '';\n      const actualValue = productBaseOption.values.find((value) => (\n        normalizeShopifyOptionValue(value) === normalizeShopifyOptionValue(configuredValue)\n      ));\n      const supported = Boolean(actualValue);\n      radio.closest('.aicp-g-base').hidden = !supported;\n      if (!supported) {\n        radio.checked = false;\n        return;\n      }\n      radio.dataset.aicpOptionValue = actualValue;\n      if (normalizeShopifyOptionValue(actualValue) === normalizeShopifyOptionValue(currentValue)) {\n        selectedRadio = radio;\n      }\n    });\n    selectedRadio ||= [...root.querySelectorAll('.aicp-g-base input[data-aicp-option-value]')]\n      .find((radio) => !radio.closest('.aicp-g-base')?.hidden);\n    if (!selectedRadio || selectedRadio.closest('.aicp-g-base')?.hidden) {\n      selectedProductOptions.delete('base');\n      basePreview.baseType = '';\n      return;\n    }\n    selectedRadio.checked = true;\n    selectedProductOptions.set('base', selectedRadio.dataset.aicpOptionValue || '');\n    basePreview.baseType = selectedRadio.value;\n  };\n\n  const syncFinishOptionControls = () => {\n    if (g.finishControl) g.finishControl.hidden = false;\n    if (!productFinishOption) {\n      // No Finish option on the product: keep the choice visible with the\n      // first finish preselected. It reaches the order as Finish/_finish only.\n      const current = g.finish.find((button) => button.classList.contains('is-selected')) || g.finish[0];\n      g.finish.forEach((button) => {\n        const selected = button === current;\n        button.classList.toggle('is-selected', selected);\n        button.setAttribute('aria-pressed', String(selected));\n      });\n      if (current) selectedProductOptions.set('finish', current.dataset.aicpGFinishValue || '');\n      return;\n    }\n    const currentValue = selectedProductOptions.get('finish');\n    let selectedButton = null;\n    g.finish.forEach((button) => {\n      const configuredValue = button.dataset.aicpGFinishValue || '';\n      const actualValue = productFinishOption.values.find((value) => (\n        normalizeShopifyOptionValue(value) === normalizeShopifyOptionValue(configuredValue)\n      ));\n      button.hidden = !actualValue;\n      if (!actualValue) return;\n      button.dataset.aicpGFinishValue = actualValue;\n      if (normalizeShopifyOptionValue(actualValue) === normalizeShopifyOptionValue(currentValue)) {\n        selectedButton = button;\n      }\n    });\n    selectedButton ||= g.finish.find((button) => !button.hidden);\n    g.finish.forEach((button) => {\n      const selected = button === selectedButton;\n      button.classList.toggle('is-selected', selected);\n      button.setAttribute('aria-pressed', String(selected));\n    });\n    if (selectedButton) selectedProductOptions.set('finish', selectedButton.dataset.aicpGFinishValue || '');\n    else selectedProductOptions.delete('finish');\n    // 2026-10-31 (Drew, after the tester): with one finish there is nothing to pick, so the card\n    // folds away and the finish is named under the price. The button stays selected in the page:\n    // the order's Finish / _finish properties are read from it.\n    const shown = g.finish.filter((button) => !button.hidden);\n    const single = shown.length === 1;\n    g.finishControl?.classList.toggle('is-single', single);\n    if (g.finishLine) {\n      g.finishLine.textContent = single ? shown[0].dataset.aicpGFinishLineText || '' : '';\n      g.finishLine.hidden = !g.finishLine.textContent;\n    }\n  };\n\n  syncBaseOptionControls();\n  syncFinishOptionControls();\n  renderCategorySizeControls();\n  syncProductVariant();\n  variantLogicReady = true;\n  if (galleryStage.item) syncCharactersToDesign(galleryStage.item);\n\n  g.finish.forEach((button) => {\n    button.addEventListener('click', () => {\n      if (button.disabled || button.hidden) return;\n      g.finish.forEach((other) => {\n        const selected = other === button;\n        other.classList.toggle('is-selected', selected);\n        other.setAttribute('aria-pressed', String(selected));\n      });\n      selectedProductOptions.set('finish', button.dataset.aicpGFinishValue || '');\n      syncProductVariant();\n    });\n  });\n\n  // Custom sizes have no Shopify variant and therefore cannot silently fall\n  // back to a cheaper standard SKU. Selecting it deliberately locks checkout.\n  g.customSize?.addEventListener('click', () => {\n    g.sizeButtons.forEach((button) => {\n      button.classList.remove('is-active');\n      button.setAttribute('aria-pressed', 'false');\n    });\n    g.customSize.classList.add('is-active');\n    g.customSize.setAttribute('aria-pressed', 'true');\n    selectedProductOptions.delete('size');\n    syncProductVariant();\n  });\n\n  const setDetailNavigation = (name) => {\n    details.nav.forEach((button) => {\n      const active = button.dataset.aicpDetailNav === name;\n      button.classList.toggle('is-active', active);\n      if (active) button.setAttribute('aria-current', 'true');\n      else button.removeAttribute('aria-current');\n    });\n  };\n  const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n  const getDetailScroller = () => window.matchMedia?.('(min-width: 990px)').matches\n    ? document.querySelector('.page-wrapper')\n    : null;\n  const getDetailOffset = () => {\n    const nav = detailScope.querySelector('.aicp-detail-nav');\n    return (nav?.getBoundingClientRect().height || 0) + 16;\n  };\n  const scrollToDetail = (target) => {\n    const scroller = getDetailScroller();\n    const offset = getDetailOffset();\n    const behavior = reducedMotion ? 'auto' : 'smooth';\n    if (scroller) {\n      const top = scroller.scrollTop + target.getBoundingClientRect().top - scroller.getBoundingClientRect().top - offset;\n      scroller.scrollTo({ top: Math.max(0, top), behavior });\n      return;\n    }\n    window.scrollTo({ top: Math.max(0, window.scrollY + target.getBoundingClientRect().top - offset), behavior });\n  };\n  details.nav.forEach((button) => {\n    button.addEventListener('click', () => {\n      const name = button.dataset.aicpDetailNav;\n      const target = details.sections.find((section) => section.dataset.aicpDetailSection === name);\n      if (!target) return;\n      setDetailNavigation(name);\n      scrollToDetail(target);\n    });\n  });\n  if (details.sections.length) {\n    let navigationFrame = 0;\n    const updateDetailNavigation = () => {\n      navigationFrame = 0;\n      const scroller = getDetailScroller();\n      const rootTop = scroller?.getBoundingClientRect().top || 0;\n      const marker = rootTop + getDetailOffset();\n      const scrollNode = scroller || document.scrollingElement;\n      const atEnd = scrollNode\n        && scrollNode.scrollTop + scrollNode.clientHeight >= scrollNode.scrollHeight - 2;\n      const current = atEnd ? details.sections.at(-1) : details.sections.reduce((active, section) => (\n        section.getBoundingClientRect().top <= marker + 2 ? section : active\n      ), details.sections[0]);\n      setDetailNavigation(current.dataset.aicpDetailSection);\n    };\n    const queueDetailNavigation = () => {\n      if (!navigationFrame) navigationFrame = window.requestAnimationFrame(updateDetailNavigation);\n    };\n    document.querySelector('.page-wrapper')?.addEventListener('scroll', queueDetailNavigation, { passive: true });\n    window.addEventListener('scroll', queueDetailNavigation, { passive: true });\n    window.addEventListener('resize', queueDetailNavigation, { passive: true });\n    updateDetailNavigation();\n  }\n  if (details.back) {\n    details.back.addEventListener('click', () => {\n      root.querySelector('.aicp-panel')?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n    });\n  }\n\n  let detailPhotoTrigger = null;\n  const closeDetailPhoto = () => details.dialog?.open && details.dialog.close();\n  details.photoButtons.forEach((button) => {\n    button.addEventListener('click', () => {\n      if (!details.dialog || !details.dialogTitle || !details.dialogImage) return;\n      detailPhotoTrigger = button;\n      details.dialogTitle.textContent = button.dataset.aicpDetailCaption || '';\n      details.dialogImage.src = button.dataset.aicpDetailSrc || '';\n      details.dialogImage.alt = button.dataset.aicpDetailAlt || '';\n      details.dialog.showModal();\n    });\n  });\n  details.dialogClose?.addEventListener('click', closeDetailPhoto);\n  details.dialog?.addEventListener('click', (event) => {\n    if (event.target === details.dialog) closeDetailPhoto();\n  });\n  details.dialog?.addEventListener('close', () => {\n    details.dialogImage?.removeAttribute('src');\n    detailPhotoTrigger?.focus();\n    detailPhotoTrigger = null;\n  });\n\n  // GSAP arrives after this runs (2026-10-21, loadAnimationLibs): until then, and with reduced motion,\n  // a click is left to the native <details> toggle.\n  if (!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {\n    detailScope.querySelectorAll('[data-aicp-accordion]').forEach((item) => {\n      const summary = item.querySelector('summary');\n      const panel = summary?.nextElementSibling;\n      if (!summary || !panel) return;\n      summary.addEventListener('click', (event) => {\n        if (!window.gsap) return;\n        event.preventDefault();\n        window.gsap.killTweensOf(panel);\n        if (item.open) {\n          window.gsap.set(panel, { height: panel.offsetHeight, paddingBottom: 22, overflow: 'hidden' });\n          window.gsap.to(panel, {\n            height: 0,\n            paddingBottom: 0,\n            autoAlpha: 0,\n            duration: 0.2,\n            ease: 'power1.inOut',\n            onComplete: () => {\n              item.open = false;\n              window.gsap.set(panel, { clearProps: 'height,paddingBottom,visibility,opacity,overflow' });\n              panel.style.removeProperty('padding-bottom');\n            },\n          });\n          return;\n        }\n        item.open = true;\n        window.gsap.set(panel, { height: 'auto', autoAlpha: 1, overflow: 'hidden' });\n        const expandedHeight = panel.offsetHeight;\n        window.gsap.set(panel, { height: 0, paddingBottom: 0, autoAlpha: 0 });\n        window.gsap.to(panel, {\n          height: expandedHeight,\n          paddingBottom: 22,\n          autoAlpha: 1,\n          duration: 0.24,\n          ease: 'power2.out',\n          onComplete: () => {\n            window.gsap.set(panel, { clearProps: 'height,paddingBottom,visibility,opacity,overflow' });\n            panel.style.removeProperty('padding-bottom');\n          },\n        });\n      });\n    });\n  }\n\n  if (g.qtyVal && g.qtyMinus && g.qtyPlus) {\n    const setQty = (value) => {\n      const qty = Math.max(1, Math.min(99, value));\n      g.qtyVal.textContent = String(qty);\n      g.qtyMinus.disabled = qty <= 1;\n    };\n    g.qtyMinus.addEventListener('click', () => setQty((parseInt(g.qtyVal.textContent, 10) || 1) - 1));\n    g.qtyPlus.addEventListener('click', () => setQty((parseInt(g.qtyVal.textContent, 10) || 1) + 1));\n    setQty(1);\n  }\n\n  const giftBoxPurchasable = GIFT_BOX_PRODUCT.configured\n    && GIFT_BOX_PRODUCT.variantId\n    && GIFT_BOX_PRODUCT.available;\n  if (g.giftRow && g.gift) {\n    g.gift.disabled = !giftBoxPurchasable;\n    g.gift.checked = giftBoxPurchasable && g.gift.checked;\n    g.giftRow.classList.toggle('is-unavailable', !giftBoxPurchasable);\n    g.giftRow.setAttribute('aria-disabled', String(!giftBoxPurchasable));\n    g.gift.addEventListener('change', syncCheckoutGate);\n    // Whole gift row toggles the checkbox; the switch's own <label> click is\n    // already native, so ignore clicks originating inside it (no double-toggle).\n    g.giftRow.addEventListener('click', (event) => {\n      if (g.gift.disabled) return;\n      if (event.target.closest('.aicp-g-switch')) return;\n      g.gift.checked = !g.gift.checked;\n      g.gift.dispatchEvent(new Event('change', { bubbles: true }));\n    });\n  }\n\n  /* ---------- gallery add-to-cart (reuses the theme cart) ----------\n     POST /cart/add.js, then dispatch \"shopify:cart:lines-update\" \u2014 the same\n     theme event the product form publishes \u2014 so the cart drawer auto-opens\n     and the header bubble refreshes. Buy Now goes straight to /checkout. */\n\n  function galleryCheckoutReadiness() {\n    const item = galleryStage.item;\n    // While this run is still being made the stage shows the upload seed, then\n    // the 2D: neither is cartable yet, and the reason is simply \"not finished\"\n    // (Drew, 2026-10-12: the _clean_file message below was showing up then).\n    if (state.busy && stageOwnedByGeneration && (!item || !item.id || !item.cleanFile)) {\n      return { locked: true, code: 'generating', message: 'Your design is still being made. You can add it to your cart as soon as your 2D design appears.' };\n    }\n    // Checkout no longer waits for the 3D (2026-10-27). An order paid before\n    // the 3D exists, or for a design whose 3D build fails, is held by the\n    // backend until the 3D is ready or rebuilt from this same 2D\n    // (resolvePaidSource in the backend's process-job.mjs). The shopper is told\n    // instead of being made to wait.\n    const building3d = Boolean(item?.id)\n      && (pending3dCount > 0 || (state.busy && stageOwnedByGeneration && !item.modelUrl));\n    if (!item) {\n      return { locked: true, message: 'Select a completed design before adding it to your cart.' };\n    }\n    if (!item.cleanFile) {\n      return { locked: true, message: \"This design's production file isn't available. Please generate it again.\" };\n    }\n    if (!item.id) {\n      return { locked: true, message: \"This design's preview reference isn't available. Please generate it again.\" };\n    }\n    if (item.fromHistory !== true && !item.sourceFile) {\n      return { locked: true, message: 'The original upload is missing. Please generate this preview again.' };\n    }\n    if (!item.sourceFile) {\n      return {\n        locked: false,\n        warning: true,\n        message: 'The original photo has expired; the remaining production files can still be archived.',\n      };\n    }\n    if (building3d) {\n      return {\n        locked: false,\n        code: '3d_pending',\n        message: \"Your 3D preview is still being made. You can order now \u2014 your figure is made from this design and we'll finish the 3D for you.\",\n      };\n    }\n    return { locked: false, message: '' };\n  }\n\n  function productVariantCheckoutReadiness() {\n    if (unsupportedProductOptions.length) {\n      return {\n        locked: true,\n        message: `Unsupported Shopify option: ${unsupportedProductOptions.map((option) => option.name).join(', ')}.`,\n      };\n    }\n    if (!PRODUCT_CATALOGUE.variants.length) {\n      return { locked: true, message: 'This product has no Shopify variants configured.' };\n    }\n    const missingOption = PRODUCT_CATALOGUE.options.find((option) => (\n      SUPPORTED_PRODUCT_OPTIONS.has(option.key) && !selectedProductOptions.get(option.key)\n    ));\n    if (missingOption) {\n      return { locked: true, message: `Choose a ${missingOption.name} option before checkout.` };\n    }\n    if (productCharactersOption && galleryStage.item?.style) {\n      const need = charactersForStyle(galleryStage.item.style);\n      if (!sameCharacters(selectedProductOptions.get('characters'), need)) {\n        return { locked: true, message: `This design was made as ${need}. Make a new design to order ${selectedProductOptions.get('characters') || 'another option'}.` };\n      }\n    }\n    if (!selectedProductVariant) {\n      return { locked: true, message: 'This option combination is not available.' };\n    }\n    if (!selectedProductVariant.available) {\n      return { locked: true, message: 'This option combination is sold out.' };\n    }\n    if (g.gift?.checked && !giftBoxPurchasable) {\n      return { locked: true, message: 'The selected Gift Box is unavailable.' };\n    }\n    return { locked: false, message: '' };\n  }\n\n  function galleryProperties() {\n    const item = galleryStage.item;\n    if (galleryCheckoutReadiness().locked || productVariantCheckoutReadiness().locked) return null;\n    const style = item.styleLabel || styleLabelFor(item.style) || item.style || 'Custom';\n    const props = {\n      // Production correlation fields must travel with every order. They are\n      // stable storage identifiers, never expiring preview URLs.\n      _clean_file: item.cleanFile,\n      _preview_job: item.id || '',\n      _style: item.style || '',\n      _source_file: item.sourceFile || '',\n      Style: style,\n    };\n    const selectedBase = selectedBaseConfig();\n    const base = selectedProductOptions.get('base');\n    if (base) props.Base = base;\n    if (base && selectedBase) {\n      props._base_type = selectedBase.id;\n      props._base_version = selectedBase.modelVersion || '';\n      if (activeEngraving()) {\n        props.Engraving = activeEngraving();\n        props._engraving = activeEngraving();\n      }\n    }\n    if (item.instructions) props.Instructions = item.instructions;\n    if (item.pose) {\n      props.Pose = item.pose;\n      props._pose = item.pose;\n    }\n    const size = selectedProductOptions.get('size');\n    if (size) props.Size = size;\n    const characters = productCharactersOption ? selectedProductOptions.get('characters') : '';\n    if (characters) {\n      props.Characters = characters;\n      props._characters = normalizeShopifyOptionValue(characters);\n    }\n    const selectedFinish = g.finish.find((button) => button.getAttribute('aria-pressed') === 'true');\n    const finish = selectedProductOptions.get('finish');\n    if (finish) {\n      // `Finish` is merchant-readable in Shopify order details; `_finish` is\n      // the stable machine field consumed by the paid-order webhook for OSS.\n      props.Finish = finish;\n      props._finish = selectedFinish?.dataset.aicpGFinishCode || finish;\n    }\n    // Design G v2 (2026-09-28): the dates field and the memorial option in the\n    // Base & name panel. Both are optional and price-neutral; they travel as\n    // line properties only. `Memorial` is merchant-readable, `_memorial` is\n    // the stable machine field.\n    // Both are engraved too, so a base without engraving drops them (2026-10-14).\n    const engravable = engravableBase(selectedBase);\n    const dates = engravable ? root.querySelector('[data-ai3d-g-dates]')?.value.trim().slice(0, 24) || '' : '';\n    if (dates) props.Dates = dates;\n    // The memorial needs a real base to be engraved on (2026-10-15).\n    if (engravable && selectedBase && root.querySelector('[data-ai3d-g-memorial]')?.checked) {\n      props.Memorial = 'Yes';\n      props._memorial = 'yes';\n    }\n    return props;\n  }\n\n  function publishCartLinesUpdate(cart) {\n    const detail = {\n      action: 'add',\n      context: 'gallery',\n      source: 'ai3d-creator-page',\n      promise: Promise.resolve({ detail: { items: cart.items || [], source: 'ai3d-creator-page' } }),\n    };\n    const event = new CustomEvent(CART_LINES_UPDATE, { detail, bubbles: true });\n    // The theme's event class exposes action/promise as getters on the event\n    // itself; mirror that so the cart drawer and bubble read them naturally.\n    Object.defineProperty(event, 'action', { get: () => detail.action });\n    Object.defineProperty(event, 'promise', { get: () => detail.promise });\n    document.dispatchEvent(event);\n  }\n\n  async function galleryAddToCart(mode) {\n    const button = mode === 'buy' ? g.cartButtons.buy : g.cartButtons.cart;\n    if (!button || button.disabled) return;\n    const properties = galleryProperties();\n    if (!properties) {\n      syncCheckoutGate();\n      return;\n    }\n    const variantId = Number(selectedProductVariant?.id);\n    const original = button.textContent;\n    if (!variantId || !selectedProductVariant?.available) {\n      button.textContent = 'Unavailable';\n      setTimeout(() => { button.textContent = original; }, 2000);\n      return;\n    }\n\n    button.disabled = true;\n    button.textContent = 'Adding\u2026';\n    try {\n      const quantity = parseInt(g.qtyVal?.textContent, 10) || 1;\n      const items = [{\n        id: variantId,\n        quantity,\n        properties,\n      }];\n      if (g.gift?.checked) {\n        items.push({\n          id: GIFT_BOX_PRODUCT.variantId,\n          quantity,\n          properties: {\n            _addon: 'gift-box',\n            _linked_preview_job: galleryStage.item?.id || '',\n          },\n        });\n      }\n      const response = await fetch('/cart/add.js', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n        credentials: 'same-origin',\n        body: JSON.stringify({ items }),\n      });\n      const data = await response.json();\n      if (!response.ok) {\n        throw new Error(data.description || data.message || 'Add to cart failed');\n      }\n      clarityEvent('mythumi_add_to_cart');\n      const cart = await fetch('/cart.js', { headers: { Accept: 'application/json' } })\n        .then((r) => r.json());\n      publishCartLinesUpdate(cart);\n      // Belt and braces: open the drawer even if the theme's auto-open is off.\n      document.querySelector('cart-drawer-component')?.closest('theme-drawer')?.open?.();\n\n      if (mode === 'buy') {\n        window.location.href = '/checkout';\n        return;\n      }\n      button.textContent = 'Added \u2713';\n      setTimeout(() => { button.textContent = original; }, 2000);\n    } catch (error) {\n      button.textContent = error.message.length > 22 ? 'Failed \u2014 try again' : error.message;\n      setTimeout(() => { button.textContent = original; }, 2500);\n    } finally {\n      syncCheckoutGate();\n    }\n  }\n\n  g.cartButtons.cart?.addEventListener('click', () => galleryAddToCart('cart'));\n  g.cartButtons.buy?.addEventListener('click', () => galleryAddToCart('buy'));\n\n  /* A disabled button gets no click, so a tap on a locked Add to Cart / Buy is\n     caught by position instead: how often shoppers try to buy while the 3D is\n     still coming is the question behind the checkout lock. */\n  let lastBlockedTap = 0;\n  const noteBlockedTap = (event) => {\n    if (Date.now() - lastBlockedTap < 800) return;\n    const point = event.touches?.[0] || event;\n    ['cart', 'buy'].forEach((mode) => {\n      const button = g.cartButtons[mode];\n      if (!button?.disabled || !button.offsetParent) return;\n      const box = button.getBoundingClientRect();\n      if (point.clientX < box.left || point.clientX > box.right || point.clientY < box.top || point.clientY > box.bottom) return;\n      lastBlockedTap = Date.now();\n      const variant = productVariantCheckoutReadiness();\n      const readiness = variant.locked ? { ...variant, code: 'variant' } : galleryCheckoutReadiness();\n      clarityEvent('mythumi_cart_blocked', null, { button: mode, lock: readiness.code || 'other', jobId: galleryStage.item?.id || '' });\n    });\n  };\n  document.addEventListener('pointerdown', noteBlockedTap, { capture: true, passive: true });\n  document.addEventListener('touchstart', noteBlockedTap, { capture: true, passive: true });\n\n  /* Result cards (one \"MAKE IT REAL\" form per style) were removed 2026-09-07:\n     the top stage + history selection is the single preview/entry, and the\n     fixed bottom Add To Cart / Buy Sculpture bar handles checkout. */\n\n  /* ---------- 3D viewer (same bundle + options as the extension) ---------- */\n\n  function loadViewerBundle() {\n    if (window.RealizeItViewer?.createViewer) return Promise.resolve(true);\n    if (viewerLoad) return viewerLoad;\n    if (!VIEWER_URL) return Promise.resolve(false);\n\n    const attempt = new Promise((resolve) => {\n      const timer = setTimeout(() => resolve(false), VIEWER_TIMEOUT_MS);\n      const settle = (ok) => {\n        clearTimeout(timer);\n        resolve(ok);\n      };\n      const script = document.createElement('script');\n      script.src = VIEWER_URL;\n      script.onerror = () => settle(false);\n      script.onload = () => settle(Boolean(window.RealizeItViewer?.createViewer));\n      document.head.appendChild(script);\n    });\n    // A failure is not cached: one network blip during load should not brick the 3D block.\n    viewerLoad = attempt.then((ok) => {\n      if (ok) return ok;\n      viewerLoad = null;\n      return window.RealizeItViewer?.createViewer ? true : false;\n    });\n    return viewerLoad;\n  }\n\n  /* Add To Cart / Buy Sculpture must not fire while a 3D model is still being\n     prepared \u2014 the shopper would be ordering a preview they cannot see yet. */\n  function syncCheckoutGate() {\n    // cleanFile identifies a production item and previewJobId is the archive\n    // correlation key. A fresh preview must also retain its source upload;\n    // historical previews are allowed to proceed without it because expiry of\n    // that optional archive artifact does not make the clean image unbuildable.\n    const variantReadiness = productVariantCheckoutReadiness();\n    const galleryReadiness = galleryCheckoutReadiness();\n    const readiness = variantReadiness.locked ? variantReadiness : galleryReadiness;\n    ['cart', 'buy'].forEach((mode) => {\n      const button = g.cartButtons[mode];\n      if (!button) return;\n      button.disabled = readiness.locked;\n      button.setAttribute('aria-disabled', String(readiness.locked));\n      if (readiness.locked && readiness.message) button.title = readiness.message;\n      else button.removeAttribute('title');\n    });\n    if (g.checkoutStatus) {\n      g.checkoutStatus.textContent = readiness.message || '';\n      g.checkoutStatus.hidden = !readiness.message;\n    }\n  }\n  syncCheckoutGate();\n\n  /* Kick off a 3D preload and keep `pending3dCount` in sync so the stage progress\n     and the checkout buttons can gate on it. Returns the promise (generate()\n     awaits it so the CTA is not re-enabled before the model is usable). */\n  function begin3dPreload(item) {\n    pending3dCount += 1;\n    syncCheckoutGate();\n    return preloadStage3D(item).finally(() => {\n      pending3dCount = Math.max(0, pending3dCount - 1);\n      syncCheckoutGate();\n    });\n  }\n\n  /* ---------- polling: GET /jobs/:id every 2.5 s, 40 min cap (per run) ---------- */\n\n  function generationRunOwnsStage(run) {\n    if (!stageOwnedByGeneration) return false;\n    const visibleJobId = galleryStage.item?.id;\n    return !visibleJobId || visibleJobId === run.jobId;\n  }\n\n  // fresh: a run this page just created (a 404 then is not a stale memory).\n  async function poll(run, { fresh = false, first = null } = {}) {\n    const deadline = Date.now() + POLL_DEADLINE_MS;\n    let showedClean = false;\n    // A single failed poll is retried quietly with a growing delay; only a\n    // lasting outage tells the shopper, once (handoff: no popup every 2\u20133 s).\n    let pollFailures = 0;\n    // `first`: a /jobs answer the caller already has (resume of a finished run),\n    // used at once instead of a wait and a second identical fetch.\n    let prefetched = first;\n    clarityJobs.add(String(run.jobId));\n\n    while (Date.now() < deadline) {\n      if (pollAbort) {\n        stopStageGenerationProgress();\n        setStageMask(false);\n        return 'aborted';\n      }\n      const wait = pollFailures ? Math.min(10000, POLL_INTERVAL_MS * (1 + pollFailures)) : POLL_INTERVAL_MS;\n      if (!prefetched) await new Promise((resolve) => setTimeout(resolve, wait));\n      if (pollAbort) {\n        stopStageGenerationProgress();\n        setStageMask(false);\n        return 'aborted';\n      }\n\n      let job = prefetched;\n      prefetched = null;\n      if (!job) try {\n        // Timeout a single /jobs fetch so a hung upstream (502/blank) can't\n        // stall this job's whole poll round; non-404 errors retry next tick.\n        const controller = new AbortController();\n        const timer = setTimeout(() => controller.abort(), POLL_REQUEST_TIMEOUT_MS);\n        try {\n          job = await call(`/jobs/${encodeURIComponent(run.jobId)}`, { signal: controller.signal });\n        } finally {\n          clearTimeout(timer);\n        }\n      } catch (error) {\n        // A 404 means the job does not exist (stale/expired run) \u2014 stop early\n        // instead of polling a dead id for 40 minutes.\n        if (error.status === 404) {\n          run.failed = true;\n          forgetJob();\n          if (generationRunOwnsStage(run)) {\n            stopStageGenerationProgress();\n            setStageMask(false);\n          }\n          if (fresh) {\n            clarityTag('mythumi_fail_stage', 'job_missing');\n            clarityEvent('mythumi_generate_failed', run.jobId, { failStage: 'job_missing' });\n            logApiError('poll', error);\n            notify({\n              message: \"We couldn't check on your design. Please check your gallery before trying again.\",\n              actions: [{ label: 'Open gallery', primary: true, run: () => switchView('gallery') }],\n              key: `poll-404-${run.jobId}`,\n            });\n          }\n          return 'failed';\n        }\n        // One failed poll is not a failed job; the next usually lands.\n        pollFailures += 1;\n        if (pollFailures === 6) {\n          logApiError('poll', error);\n          notify({\n            message: \"We're having trouble checking on your design. It keeps going in the background \u2014 please check your gallery in a few minutes.\",\n            key: `poll-trouble-${run.jobId}`,\n          });\n        }\n        continue;\n      }\n      pollFailures = 0;\n\n      run.pct = job.progress || 0;\n      run.phase = job.phase || job.status;\n      // The 2D service has no incremental percentage, so its bar uses the\n      // visual estimate started at CREATE. Once cleanUrl exists, switch to the\n      // real provider-backed 3D progress and keep that bar visible while the\n      // 2D result remains on stage.\n      if (!showedClean) {\n        const phaseDesc = {\n          // No \"queue\" in the copy: the word reads as slow (2026-10-29).\n          queued: 'Starting your design\u2026',\n          designing: 'Generating your 2D design from your photo\u2026',\n          submitting: 'Your 2D design is ready. Preparing the 3D build\u2026',\n          running: 'Building your 3D model \u2014 sculpting and texturing\u2026',\n          downloading: 'Putting the finishing touches on your 3D model\u2026',\n        }[run.phase] || 'Generating your 2D design from your photo\u2026';\n        // A run that was finished before this page opened has nothing to build.\n        if (generationRunOwnsStage(run) && !run.finishedBefore) {\n          updateStageGenerationProgress('2d', run.pct, phaseDesc);\n        }\n      } else if (generationRunOwnsStage(run)) {\n        const phaseDesc = {\n          queued: 'Starting your 3D build\u2026',\n          submitting: 'Submitting your design for 3D generation\u2026',\n          pending: 'Preparing your 3D model\u2026',\n          running: 'Building your 3D model \u2014 sculpting and texturing\u2026',\n          downloading: 'Finalizing your generated 3D model\u2026',\n        }[run.phase] || 'Building your 3D model \u2014 sculpting and texturing\u2026';\n        updateStageGenerationProgress('3d', run.pct, phaseDesc);\n      }\n\n      if (job.cleanUrl && !showedClean) {\n        showedClean = true;\n        run.cleanReady = true;\n        clarityEvent('mythumi_2d_ready', run.jobId);\n        offerSaveDesign(run.jobId, job.cardUrl || job.cleanUrl);\n        // The viewer script downloads while the 3D is built (minutes), not after it\n        // (2026-10-27). Only the script: the WebGL viewer itself still starts with the model.\n        void loadViewerBundle();\n        const cleanMetrics = metricsFor(run.jobId);\n        if (cleanMetrics && !cleanMetrics.t2d) cleanMetrics.t2d = performance.now();\n        // AICP-TIMING: 2D image is ready \u2014 log time since this job's create.\n        const t2d = aicpTimingFor(run.jobId);\n        if (!t2d.cleanAt) {\n          t2d.cleanAt = Date.now();\n          const from2d = t2d.createAt || AICP_TIMING.createStart;\n          if (from2d) aicpTimingLog(`2D generated (style: ${run.styleLabel || run.style})`, t2d.cleanAt - from2d);\n        }\n        // The 2D link is back. Show it on the stage only if the generation\n        // still owns it \u2014 a user-clicked history card keeps the stage. Once\n        // the generated image is decoded, remove the 2D mask and start\n        // the compact provider-backed 3D progress without obscuring the image.\n        run.displayItem = {\n          // One job = one history entry, so the jobId IS the stable history id.\n          // Carrying it lets the 3D blob cache work for fresh generations too.\n          id: run.jobId,\n          thumbnailUrl: job.cleanUrl,\n          style: run.style,\n          styleLabel: run.styleLabel,\n          pose: run.pose || null,\n          instructions: run.instructions || '',\n          cleanFile: job.cleanFile || null,\n          sourceFile: run.sourceFile || null,\n          fromHistory: false,\n          madeEarlier: run.finishedBefore === true,\n        };\n        if (stageOwnedByGeneration) {\n          showOnStage(run.displayItem, false);\n          const stageImage = g.stageFrame?.querySelector('.aicp-g-stage__img');\n          if (stageImage?.decode) await stageImage.decode().catch(() => {});\n          if (generationRunOwnsStage(run) && galleryStage.item === run.displayItem) {\n            galleryStage.pill = '2d';\n            stageSetPill('2d');\n            if (run.finishedBefore) {\n              setStageMask(false);\n              setStage3DProgress(true, 0, 'Loading your model\u2026', { spinner: true });\n            } else {\n              startStageGenerationProgress(\n                '3d',\n                job.progress,\n                'Preparing your design for 3D generation\u2026',\n              );\n            }\n          }\n        }\n        // The 2D now exists server-side \u2014 refresh the history rail so the new\n        // design appears immediately (it lands under its category).\n        loadGalleryHistory();\n      }\n      if (job.status === 'succeeded') {\n        // Model arrived: hand it to the stage's 3D preloader (the stage is the\n        // only preview area \u2014 it flips to 3D automatically once loaded).\n        // The run is \"done\" server-side, but the compact bar remains until the\n        // GLB download, decode and first render have all settled.\n        run.done = true;\n        const doneMetrics = metricsFor(run.jobId);\n        if (doneMetrics) {\n          if (job.modelUrl && !doneMetrics.t3d) doneMetrics.t3d = performance.now();\n          doneMetrics.terminal = true;\n          // With a model, wait for its first frame (preloadStage3D sends);\n          // the fallback covers a preload that never starts or never ends.\n          if (job.modelUrl) doneMetrics.fallbackTimer = setTimeout(() => sendClientMetrics(run.jobId), METRICS_FALLBACK_MS);\n          else sendClientMetrics(run.jobId);\n        }\n        if (job.modelUrl) {\n          // AICP-TIMING: 3D model URL is ready \u2014 log time since the 2D landed\n          // (falls back to CREATE if the 2D never surfaced a cleanUrl).\n          const t3d = aicpTimingFor(run.jobId);\n          if (!t3d.modelAt) {\n            t3d.modelAt = Date.now();\n            const from = t3d.cleanAt || t3d.createAt || AICP_TIMING.createStart;\n            if (from) aicpTimingLog(`3D generated (style: ${run.styleLabel || run.style})`, t3d.modelAt - from);\n          }\n          run.modelUrl = job.modelUrl;\n          run.provider = job.provider;\n          if (run.displayItem) {\n            run.displayItem.modelUrl = job.modelUrl;\n            run.displayItem.provider = job.provider;\n            // The production file can arrive with the finished job when the\n            // 2D poll did not carry it yet; without it the design is not cartable.\n            if (job.cleanFile && !run.displayItem.cleanFile) run.displayItem.cleanFile = job.cleanFile;\n            if (job.sourceFile && !run.displayItem.sourceFile) run.displayItem.sourceFile = job.sourceFile;\n            if (generationRunOwnsStage(run) && galleryStage.item === run.displayItem) {\n              stopStageGenerationProgress();\n              if (run.finishedBefore) setStage3DProgress(true, 0, 'Loading your model\u2026', { spinner: true });\n              else setStage3DProgress(true, MODEL_READY_PROGRESS, 'Downloading your 3D model\u2026');\n            }\n            run.preload3d = begin3dPreload(run.displayItem);\n          } else if (generationRunOwnsStage(run)) {\n            stopStageGenerationProgress();\n            setStageMask(false);\n          }\n          loadGalleryHistory();\n        } else if (generationRunOwnsStage(run)) {\n          stopStageGenerationProgress();\n          setStageMask(false);\n        }\n        return 'succeeded';\n      }\n\n      if (job.status === 'failed') {\n        run.failed = true;\n        clarityTag('mythumi_fail_stage', 'job');\n        clarityEvent('mythumi_generate_failed', run.jobId, { failStage: 'job', reason: String(job?.reason || '') });\n        // A failed build may still have surfaced a 2D \u2014 clean the mask so the\n        // surviving 2D is visible. Failed is a 200 terminal state: stop polling\n        // and show the job's own (plain-text) reason.\n        if (generationRunOwnsStage(run)) {\n          stopStageGenerationProgress();\n          setStageMask(false);\n        }\n        const failMetrics = metricsFor(run.jobId);\n        if (failMetrics) {\n          failMetrics.terminal = true;\n          sendClientMetrics(run.jobId);\n        }\n        showJobFailed({ ...job, id: job.id || run.jobId });\n        return 'failed';\n      }\n    }\n\n    run.failed = true;\n    clarityTag('mythumi_fail_stage', 'timeout');\n    clarityEvent('mythumi_generate_failed', run.jobId, { failStage: 'timeout' });\n    notify({\n      message: 'This is taking longer than expected. Your design keeps going \u2014 please reload the page in a few minutes.',\n      key: `poll-timeout-${run.jobId}`,\n    });\n    if (generationRunOwnsStage(run)) {\n      stopStageGenerationProgress();\n      setStageMask(false);\n    }\n    return 'timeout';\n  }\n\n  /* ---------- create event: one POST /create for the chosen option ---------- */\n\n  // A directive card leaves the shopper's note the rest of the prompt.\n  function noteLimit() {\n    const directive = state.styles[0]?.directive || '';\n    return directive ? MAX_PROMPT_CHARS - directive.length - 1 : MAX_PROMPT_CHARS;\n  }\n\n  async function generate() {\n    const poseMode = styleAreaIsPoseMode();\n    if (state.busy || state.uploading) return;\n    // The button stays live before the photo and the choice (refreshCta): lead to what is missing.\n    if (!state.uploaded) {\n      el.file?.click();\n      return;\n    }\n    if (!hasRequiredCreatorSelection()) {\n      if (hasCardChoice()) nudgeSelection();\n      return;\n    }\n    if (unknownRun && !sameAsUnknownRun(unknownRun, poseMode)) unknownRun = null;\n    if (unknownRun && Date.now() - unknownRun.seenAt < 180000) {\n      const pending = unknownRun;\n      state.busy = true;\n      refreshCta();\n      let items;\n      try {\n        items = await readHistoryItems();\n      } catch (error) {\n        // Unchecked is not \"absent\": sending /create now could start the same\n        // design twice. Keep unknownRun so the next click checks again.\n        logApiError('history', error);\n        state.busy = false;\n        refreshCta();\n        notify({ message: \"We couldn't check your gallery right now. Please look again in a minute before trying again.\", key: 'unknown-run-unchecked' });\n        return;\n      }\n      state.busy = false;\n      const match = unknownRunJob(pending, items);\n      if (match) {\n        followRecoveredJob(match, pending);\n        return;\n      }\n      const completedJob = unknownRunJob(pending, items, { succeeded: true });\n      if (completedJob) {\n        publishPreviewStart(completedJob.id, pending.previewAnalytics);\n        unknownRun = null;\n        notify({ message: 'Your earlier design did start and is in your gallery.', actions: [{ label: 'Open gallery', primary: true, run: () => switchView('gallery') }] });\n        refreshCta();\n        return;\n      }\n      const failedJob = unknownRunJob(pending, items, { failed: true });\n      if (failedJob) publishPreviewStart(failedJob.id, pending.previewAnalytics);\n      unknownRun = null;\n    }\n    // Designs this page already knows: an unknown outcome is never one of them.\n    const knownIds = [\n      ...historyItems.map((item) => String(item.id)),\n      ...currentRuns.map((run) => String(run.jobId)),\n      galleryStage.item?.id != null ? String(galleryStage.item.id) : '',\n    ].filter(Boolean);\n    state.busy = true;\n    syncHistoryVisibility();\n    AICP_TIMING.createStart = Date.now(); // AICP-TIMING: 2D/3D clocks start here\n    AICP_TIMING.armTotal = true;          // AICP-TIMING: next usable 3D ends TOTAL\n    refreshCta();\n    // The stage belongs to this new run until the shopper clicks a history card.\n    stageOwnedByGeneration = true;\n    // Land the shopper in the gallery right away. The uploaded photo goes on\n    // the stage immediately as the \"before\" 2D so the stage is never blank\n    // while the generated 2D is pending; the caption shows the first style.\n    switchView('gallery');\n    const firstStyle = (poseMode && activeCategoryRecord())\n      ? (activeCategoryRecord().styleLabel || activeCategoryRecord().style)\n      : (state.styles[0] && (state.styles[0].label || state.styles[0].name));\n    const stageSeed = {\n      thumbnailUrl: state.uploadedPreview || '',\n      styleLabel: firstStyle || '',\n    };\n    // Solo / Duo: price and variant follow the design being made, not the previously staged one.\n    syncCharactersToDesign({ style: state.styles[0]?.name });\n    if (stageSeed.thumbnailUrl) {\n      showOnStage(stageSeed, false);\n      galleryStage.pill = '2d';\n      stageSetPill('2d');\n    }\n    // The 2D endpoint is synchronous and exposes no byte/job percentage. The\n    // on-stage estimate gives continuous feedback until cleanUrl arrives.\n    startStageGenerationProgress(\n      '2d',\n      0,\n      'Generating your 2D design from your photo. This usually takes about a minute.',\n    );\n    currentRuns = [];\n    // Set once /create returns a job: a later failure is then after_create.\n    let createdJobId = null;\n\n    try {\n      const runs = [];\n      const createFor = async (styleName, styleLabel, poseName, directive = '') => {\n        const note = el.notes ? el.notes.value.trim().slice(0, noteLimit()).trim() : '';\n        const body = {\n          file: state.uploaded,\n          style: styleName,\n          prompt: directive ? `${directive} ${note}`.trim() : note,\n        };\n        if (state.screeningId) body.screeningId = state.screeningId;\n        if (visitorToken()) body.visitorToken = visitorToken();\n        if (poseName) body.pose = poseName;\n        // Snapshot before the request: gallery selections may change while it runs.\n        const previewAnalytics = { variant_id: selectedProductVariant?.id || null, creator_category: INITIAL_CAT };\n        lastCreateAttempt = { style: styleName, styleLabel: styleLabel || styleName, at: Date.now(), file: state.uploaded, note, pose: poseName || null, directive, knownIds, previewAnalytics };\n        // t0 for the client timings: just before /create is sent.\n        const t0 = performance.now();\n        // The content check runs inside /create; give it time, but never hang.\n        // A timeout is an \"unknown\" outcome: the popup sends the shopper to the\n        // gallery instead of re-sending /create (no duplicate jobs).\n        const controller = new AbortController();\n        const timer = setTimeout(() => controller.abort(), 90000);\n        let started;\n        try {\n          started = await call('/create', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(body),\n            signal: controller.signal,\n          });\n        } finally {\n          clearTimeout(timer);\n        }\n        if (!started.jobId) {\n          throw new Error(started.error || 'The design service did not start a job. Please try again.');\n        }\n        createdJobId = started.jobId;\n        publishPreviewStart(started.jobId, previewAnalytics);\n        clarityTag('creator_category', INITIAL_CAT);\n        clarityTag('creator_style', styleLabel || styleName);\n        clarityEvent('mythumi_generate_start', started.jobId);\n        CLIENT_METRICS.set(started.jobId, { t0 });\n        showQuota(started.quota);\n        runs.push({\n          jobId: started.jobId,\n          style: styleName,\n          styleLabel: styleLabel || styleName,\n          pose: poseName || null,\n          instructions: note,\n          sourceFile: state.uploaded,\n          pct: 0,\n        });\n        // AICP-TIMING: anchor this job's 2D clock at its own /create return.\n        aicpTimingFor(started.jobId).createAt = Date.now();\n      };\n\n      if (poseMode) {\n        // PET uses the category record's server style plus one selected pose.\n        await categoriesReady;\n        const record = activeCategoryRecord();\n        if (!record?.style) throw new Error('The pet style is not available yet. Please try again in a moment.');\n        const pose = state.poses.find((entry) => entry.category === record.category);\n        await createFor(record.style, record.styleLabel || record.style, pose?.name);\n      } else {\n        const style = state.styles[0];\n        await createFor(style.name, style.label || style.name, undefined, style.directive);\n      }\n      currentRuns = runs;\n      // Resume memory holds a single job; only remember solo runs.\n      if (runs.length === 1) {\n        rememberJob(runs[0].jobId, runs[0].style, runs[0].styleLabel, state.uploaded, { instructions: runs[0].instructions, pose: runs[0].pose });\n      }\n\n      const outcomes = await Promise.all(runs.map((run) => poll(run, { fresh: true })));\n      // Server side is done, but the 3D preloads may still be downloading and\n      // parsing. Wait for them so the bar does not claim \"Done\" early and the\n      // CTA is not re-enabled before the model is actually usable.\n      await Promise.all(runs.map((run) => run.preload3d).filter(Boolean));\n      state.busy = false;\n      syncHistoryVisibility();\n      if (outcomes.some((outcome) => outcome !== 'succeeded')) {\n        say('Some styles did not finish \u2014 the finished designs are ready below.', 'error');\n        if (runs.length === 1 && outcomes[0] !== 'succeeded') {\n          forgetJob();\n          if (!runs[0].displayItem && !galleryStage.item?.id) {\n            clearStage();\n            switchView('gallery');\n          }\n        }\n      } else {\n        say('');\n      }\n    } catch (error) {\n      // create_unknown: /create timed out or the network dropped, the job may exist.\n      const failStage = createdJobId != null ? 'after_create' : isUnknownCreateOutcome(error) ? 'create_unknown' : 'create';\n      clarityTag('mythumi_fail_stage', failStage);\n      clarityEvent('mythumi_generate_failed', createdJobId, { failStage, status: Number(error?.status) || 0 });\n      if (error.quota) showQuota(error.quota);\n      state.busy = false;\n      syncHistoryVisibility();\n      stopStageGenerationProgress();\n      setStageMask(false);\n      if (currentRuns.length === 1) forgetJob();\n      // The stage was switched to the gallery for this run: take the photo seed\n      // off it (it is not a design) and go back to the create step, where the\n      // kept photo, style and note are in view again.\n      if (!currentRuns.length && !galleryStage.item?.id) {\n        clearStage();\n        switchView('create');\n        syncHistoryVisibility();\n      }\n      // JSON 404/409: the uploaded file no longer matches \u2014 the photo leaves the\n      // form and the shopper uploads again (style and note stay as they are).\n      if (!error.nonJson && (error.status === 404 || error.status === 409)) clearPhoto();\n      // Unknown outcome: \"Open gallery\" looks for this job instead of a resend.\n      if (!currentRuns.length && isUnknownCreateOutcome(error) && lastCreateAttempt) unknownRun = { ...lastCreateAttempt, seenAt: Date.now() };\n      if (currentRuns.length) {\n        // /create already worked; something after it broke \u2014 never resend.\n        logApiError('generate', error);\n        notify({ message: 'Something went wrong building your figurine. Please check your gallery before trying again.', actions: [{ label: 'Open gallery', primary: true, run: () => switchView('gallery') }] });\n      } else {\n        showApiError('create', error, { retry: () => { if (!state.busy) generate(); } });\n      }\n    } finally {\n      refreshCta();\n    }\n  }\n\n  el.go?.addEventListener('click', generate);\n\n  async function resume(saved) {\n    state.busy = true;\n    // Like generate(): the resumed run owns the stage, so its 2D/3D (with the\n    // upload, note and pose) go there and its mask ends with it \u2014 unless the\n    // shopper is already looking at another design.\n    stageOwnedByGeneration = !galleryStage.item?.id || String(galleryStage.item.id) === String(saved.jobId);\n    syncHistoryVisibility();\n    refreshCta();\n    // The run is remembered for 45 minutes, also after it finished. One look first: a finished\n    // design is a design made earlier, shown with the grey spinner instead of \"Picking up\u2026\" and a\n    // download percentage (Drew, 2026-10-29). Unknown (no answer in time) keeps the old behaviour.\n    let finishedBefore = false;\n    let firstAnswer = null;\n    // Neutral while asking (also clears the markup's default 2D mask): the answer decides.\n    if (stageOwnedByGeneration) {\n      stopStageGenerationProgress();\n      setStage3DProgress(true, 0, 'Loading your model\u2026', { spinner: true });\n    }\n    try {\n      const controller = new AbortController();\n      const timer = setTimeout(() => controller.abort(), 4000);\n      try {\n        firstAnswer = await call(`/jobs/${encodeURIComponent(saved.jobId)}`, { signal: controller.signal });\n        finishedBefore = firstAnswer?.status === 'succeeded' && Boolean(firstAnswer.modelUrl);\n      } finally {\n        clearTimeout(timer);\n      }\n    } catch {}\n    // Still building: the usual 2D mask (startStageGenerationProgress clears the spinner).\n    if (stageOwnedByGeneration && !finishedBefore) startStageGenerationProgress('2d', 0, 'Picking up your design where it left off\u2026');\n    // The upload, note and pose it was made from: without the upload the\n    // finished design could not be added to the cart.\n    currentRuns = [{\n      jobId: saved.jobId,\n      style: saved.style,\n      styleLabel: saved.styleLabel || saved.style,\n      sourceFile: saved.uploaded || null,\n      instructions: saved.instructions || '',\n      pose: saved.pose || null,\n      pct: 0,\n      finishedBefore,\n    }];\n    try {\n      const outcome = await poll(currentRuns[0], { first: finishedBefore ? firstAnswer : null });\n      // Picked up once: a failed or timed-out run is not shown again on every\n      // later visit for the rest of the memory window.\n      if (outcome !== 'succeeded') forgetJob();\n    } catch (error) {\n      logApiError('resume', error);\n    } finally {\n      state.busy = false;\n      syncHistoryVisibility();\n      refreshCta();\n    }\n  }\n\n  /* ---------- soft keyboard handling ----------\n     When a text field is focused on mobile the on-screen keyboard shrinks (or\n     covers) the viewport and the page squashes/scrolls unpredictably. Two\n     measures, both scoped to this section:\n     1. Keep the page from reflowing: lock body height to the LAYOUT viewport\n        while a field is focused (undoing Safari's automatic visual-viewport\n        shrink), and let inner scrolling happen naturally instead.\n     2. Follow the keyboard: on every visual-viewport change scroll the focused\n        field back into view so it is never hidden behind the keyboard. */\n  const keyboard = { viewportHandler: null, focusedField: null };\n\n  function scrollFocusedFieldIntoView() {\n    const field = keyboard.focusedField || document.activeElement;\n    if (!field || !field.matches?.('input[type=\"text\"], textarea')) return;\n    // Small delay: iOS reports the new viewport size a beat after the event.\n    setTimeout(() => {\n      field.scrollIntoView({ block: 'center', behavior: 'smooth' });\n    }, 120);\n  }\n\n  /* ---------- save your design (2026-10-27) ----------\n     Facebook and Instagram open the store in their own browser. A shopper who\n     moves to Safari or Chrome leaves this browser's visitor token behind, and\n     with it every design. Once a run's 2D is ready, a shopper who is not\n     signed in (the section renders no card for a customer) is offered a link\n     that brings the designs along: {proxy}/restore/<code>, from the backend's\n     POST /restore-codes.\n\n     Emailed: the native customer form subscribes the address with the tags\n     creator-lead and restore-<code>, and a Shopify Flow on that tag sends the\n     Messaging email with the link. That is a marketing subscription, so Send\n     stays disabled until the unticked consent box is ticked (CASL). The form\n     is sent with fetch (postSaveForm) so this page and its run stay and\n     Shopify's answer can be read: this page (?customer_posted=true), this\n     page with the form's errors, or its hCaptcha /challenge, which the shopper\n     then completes as a page of its own. (A hidden frame could do neither: the\n     storefront refuses to be framed, X-Frame-Options DENY and frame-ancestors\n     'none'.) Copied: the same link. Once sent, the card is not offered again\n     for 30 days.\n\n     The card starts as one row (2026-10-28): the run's 2D, Email me a link and\n     Copy link. Email me a link opens the form in place (.is-open); a send folds\n     the card to one confirmation row (.is-sent). */\n  const save = {\n    card: root.querySelector('[data-aicp-save]'),\n    form: root.querySelector('[data-aicp-save-form]'),\n    panel: root.querySelector('[data-aicp-save-panel]'),\n    openButton: root.querySelector('[data-aicp-save-open]'),\n    closeButton: root.querySelector('[data-aicp-save-close]'),\n    title: root.querySelector('[data-aicp-save-title]'),\n    thumb: root.querySelector('[data-aicp-save-thumb]'),\n    thumbImg: root.querySelector('[data-aicp-save-thumb-img]'),\n    field: root.querySelector('[data-aicp-save-field]'),\n    email: root.querySelector('[data-aicp-save-email]'),\n    consent: root.querySelector('[data-aicp-save-consent]'),\n    consentRow: root.querySelector('[data-aicp-save-consent-row]'),\n    send: root.querySelector('[data-aicp-save-send]'),\n    sendLabel: root.querySelector('[data-aicp-save-send]')?.textContent.trim() || 'Send my design',\n    tags: root.querySelector('[data-aicp-save-tags]'),\n    status: root.querySelector('[data-aicp-save-status]'),\n    fieldStatus: root.querySelector('[data-aicp-save-field-status]'),\n    copies: [...root.querySelectorAll('[data-aicp-save-copy]')],\n    copiedTimer: null,\n    link: root.querySelector('[data-aicp-save-link]'),\n    jobId: null,\n    code: null,\n    codeValue: '',\n    sending: false,\n    awaiting: false,\n    timer: null,\n    attempt: 0, // bumped when an attempt ends, so a late answer to it is ignored\n    controller: null, // the post in flight\n  };\n\n  function saveWasSent() {\n    try {\n      const at = Number(localStorage.getItem(SAVE_SENT_KEY));\n      return Boolean(at) && Date.now() - at < SAVE_SENT_MS;\n    } catch {\n      return false;\n    }\n  }\n\n  function restoreLink(code) {\n    return `${location.origin}${proxy}/restore/${code}`;\n  }\n\n  // The page to come back to: this path, and the category tab when it is in the URL.\n  function restorePage() {\n    const cat = new URLSearchParams(location.search).get('cat');\n    return `${location.pathname}${cat && /^[a-z]+$/.test(cat) ? `?cat=${cat}` : ''}`;\n  }\n\n  function restoreCodeFor(jobId) {\n    if (save.jobId !== jobId || !save.code) {\n      save.jobId = jobId;\n      save.codeValue = '';\n      const body = { jobId, page: restorePage() };\n      if (visitorToken()) body.visitorToken = visitorToken();\n      const pending = call('/restore-codes', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(body),\n      }).then((reply) => {\n        if (!reply?.code) throw new Error('No restore code.');\n        if (save.code === pending) save.codeValue = reply.code;\n        return reply.code;\n      });\n      // A failed request is asked again on the next tap.\n      pending.catch(() => {\n        if (save.code === pending) save.code = null;\n      });\n      save.code = pending;\n    }\n    return save.code;\n  }\n\n  // One message at a time. field: about the address, under the email field (sending,\n  // errors); otherwise the card's line under its top row (copy, sent). note: a success\n  // note; quiet: announced only (the Send button already says \"Sending\u2026\").\n  function setSaveStatus(message, { error = false, field = false, note = false, quiet = false } = {}) {\n    for (const [node, mine] of [[save.status, !field], [save.fieldStatus, field]]) {\n      if (!node) continue;\n      const text = mine ? message || '' : '';\n      node.textContent = text;\n      node.hidden = !text;\n      node.classList.toggle('is-error', Boolean(text) && error);\n      node.classList.toggle('is-note', Boolean(text) && note);\n      node.classList.toggle('is-quiet', Boolean(text) && quiet);\n    }\n    save.field?.classList.toggle('is-error', Boolean(message) && field && error);\n    save.email?.setAttribute('aria-invalid', String(Boolean(message) && field && error));\n  }\n\n  function syncSaveSend() {\n    if (!save.send) return;\n    const email = save.email?.value.trim() || '';\n    const valid = Boolean(email) && Boolean(save.email?.validity.valid);\n    save.send.disabled = save.sending || !save.consent?.checked || !valid;\n    save.send.classList.toggle('is-busy', save.sending);\n    save.send.setAttribute('aria-busy', String(save.sending));\n    save.send.textContent = save.sending ? 'Sending\u2026' : save.sendLabel;\n    // Locked while a send is out, so what was sent is what the shopper sees.\n    if (save.email) save.email.readOnly = save.sending;\n    if (save.consent) save.consent.disabled = save.sending;\n    save.consentRow?.classList.toggle('is-checked', Boolean(save.consent?.checked));\n    save.consentRow?.classList.toggle('is-locked', save.sending);\n    save.field?.classList.toggle('is-valid', valid);\n  }\n\n  // The run's 2D in the card. A picture that fails (or none) leaves the text-only row.\n  function showSaveThumb(url) {\n    if (!save.thumb || !save.thumbImg) return;\n    const hide = () => {\n      save.thumb.hidden = true;\n      save.card.classList.remove('has-thumb');\n    };\n    if (!url) {\n      hide();\n      return;\n    }\n    if (save.thumbImg.getAttribute('src') === url) return;\n    save.thumbImg.onerror = hide;\n    save.thumbImg.src = url;\n    save.thumb.hidden = false;\n    save.card.classList.add('has-thumb');\n  }\n\n  function offerSaveDesign(jobId, pictureUrl) {\n    if (!save.card || !save.form || !jobId || saveWasSent()) return;\n    // Asked now, so Copy link has the link inside the tap (iOS only copies there).\n    restoreCodeFor(jobId).catch(() => {});\n    showSaveThumb(pictureUrl);\n    if (!save.card.hidden) return;\n    save.card.hidden = false;\n    clarityEvent('mythumi_save_shown', jobId);\n  }\n\n  function openSaveForm() {\n    if (!save.card || !save.panel || save.card.classList.contains('is-open')) return;\n    save.card.classList.add('is-open');\n    save.panel.hidden = false;\n    save.openButton?.setAttribute('aria-expanded', 'true');\n    clarityEvent('mythumi_save_open', save.jobId);\n    save.email?.focus({ preventScroll: true });\n    save.card.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n  }\n\n  // Back to one row; what was typed stays. Not while a send is out.\n  function closeSaveForm() {\n    if (!save.card?.classList.contains('is-open') || save.sending) return;\n    save.card.classList.remove('is-open');\n    save.panel.hidden = true;\n    save.openButton?.setAttribute('aria-expanded', 'false');\n    setSaveStatus('');\n    save.openButton?.focus({ preventScroll: true });\n  }\n\n  function maskedEmail(value) {\n    const [name, domain] = String(value).split('@');\n    return domain ? `${name.slice(0, 1)}***@${domain}` : 'your inbox';\n  }\n\n  // A send that may leave this page (Shopify's hCaptcha page, see postSaveForm): kept for this tab\n  // so the page Shopify brings the shopper back to can say it was sent.\n  function setSavePending(on) {\n    try {\n      if (on) sessionStorage.setItem(SAVE_PENDING_KEY, JSON.stringify({ at: Date.now(), email: save.email.value.trim(), job: save.jobId || '' }));\n      else sessionStorage.removeItem(SAVE_PENDING_KEY);\n    } catch {}\n  }\n\n  function endSaveAttempt() {\n    clearTimeout(save.timer);\n    save.timer = null;\n    save.attempt += 1;\n    save.controller?.abort();\n    save.controller = null;\n    save.awaiting = false;\n    save.sending = false;\n    setSavePending(false);\n    syncSaveSend();\n  }\n\n  function saveFailed(reason, message) {\n    endSaveAttempt();\n    setSaveStatus(message, { error: true, field: true });\n    clarityEvent('mythumi_save_failed', null, { reason, job: save.jobId || '' });\n  }\n\n  function saveSent() {\n    const address = save.email.value.trim();\n    endSaveAttempt();\n    try {\n      localStorage.setItem(SAVE_SENT_KEY, String(Date.now()));\n    } catch {}\n    // The card folds to one row: the design, \"Check your inbox\" and where it went.\n    save.card.classList.remove('is-open');\n    save.card.classList.add('is-sent');\n    save.panel.hidden = true;\n    save.link.hidden = true;\n    if (save.title) save.title.textContent = 'Check your inbox';\n    setSaveStatus(`Sent to ${maskedEmail(address)}. The link in that email opens your design in any browser.`);\n    // The focused field or button just left the page with the form: land focus on what\n    // happened, which also reads it out (a live region shown in the same tick may not be).\n    if (save.status) {\n      save.status.tabIndex = -1;\n      save.status.focus({ preventScroll: true });\n    }\n    clarityEvent('mythumi_save_sent', save.jobId);\n  }\n\n  async function sendSaveDesign() {\n    if (save.sending || !save.jobId) return;\n    save.sending = true;\n    syncSaveSend();\n    setSaveStatus('Sending\u2026', { field: true, quiet: true });\n    clarityEvent('mythumi_save_submit', save.jobId);\n    let code;\n    try {\n      code = await restoreCodeFor(save.jobId);\n    } catch {\n      saveFailed('code', \"We couldn't save your design just now. Please try again.\");\n      return;\n    }\n    save.tags.value = `creator-lead,restore-${code}`;\n    save.awaiting = true;\n    clearTimeout(save.timer);\n    save.timer = setTimeout(() => saveFailed('timeout', \"We couldn't send it just now. Please try again.\"), SAVE_CHECK_MS);\n    setSavePending(true);\n    // Shopify's supported way to attach hCaptcha to a form submitted from script. Its helper puts\n    // the token in the form, then calls the form's own submit(): postSaveForm (set below).\n    if (typeof window.Shopify?.captcha?.protect === 'function') window.Shopify.captcha.protect(save.form, () => save.form.submit());\n    else postSaveForm();\n  }\n\n  // The form's submit(), replaced below before Shopify's hCaptcha helper binds the form (on its\n  // first focus, or through protect above): the helper keeps the submit() it finds and calls it\n  // once its token is in the form. Same fields as a native post, so Shopify answers the same way.\n  async function postSaveForm() {\n    // One post per attempt: a second token for it (a late hCaptcha answer) is not posted again.\n    if (!save.awaiting || save.controller) return;\n    const attempt = save.attempt;\n    const controller = new AbortController();\n    save.controller = controller;\n    clearTimeout(save.timer);\n    save.timer = setTimeout(() => saveFailed('timeout', \"We couldn't reach our email service. Please try again.\"), SAVE_RESPONSE_MS);\n    let response;\n    let html = '';\n    try {\n      response = await fetch(save.form.action, { method: 'POST', body: new FormData(save.form), credentials: 'same-origin', signal: controller.signal });\n      html = await response.text();\n    } catch {\n      if (attempt === save.attempt) saveFailed('network', \"We couldn't reach our email service. Please try again.\");\n      return;\n    }\n    if (attempt !== save.attempt) return; // this attempt already ended (timed out)\n    save.controller = null;\n    const landed = new URL(response.url || location.href, location.href);\n    if (landed.searchParams.get('customer_posted') === 'true') {\n      saveSent();\n    } else if (/\\/challenge$/.test(landed.pathname)) {\n      // hCaptcha wants a visible check. Its page cannot be shown inside this one, so the shopper goes\n      // there: solving it sends the form (the helper kept it under form_key) and Shopify brings them\n      // back to this page, where the pending send is picked up (below).\n      clearTimeout(save.timer);\n      save.timer = null;\n      clarityEvent('mythumi_save_challenge', null, { job: save.jobId || '' });\n      setSaveStatus('One quick check, then we email your link\u2026', { field: true });\n      location.assign(landed.href);\n    } else {\n      const errors = new DOMParser().parseFromString(html, 'text/html')\n        .querySelector('#aicp-save-form [data-aicp-save-errors]')?.textContent.trim() || '';\n      saveFailed(errors ? 'form_error' : 'unknown', errors || \"We couldn't send to that address. Please check it and try again.\");\n    }\n  }\n\n  if (save.card && save.form) {\n    save.openButton?.addEventListener('click', openSaveForm);\n    save.closeButton?.addEventListener('click', closeSaveForm);\n    save.card.addEventListener('keydown', (event) => {\n      if (event.key !== 'Escape' || !save.card.classList.contains('is-open')) return;\n      event.stopPropagation();\n      closeSaveForm();\n    });\n    save.email?.addEventListener('input', () => {\n      // A new address clears the last send's complaint about the old one.\n      if (save.field?.classList.contains('is-error')) setSaveStatus('');\n      syncSaveSend();\n    });\n    save.consent?.addEventListener('change', syncSaveSend);\n    save.email?.addEventListener('keydown', (event) => {\n      if (event.key !== 'Enter') return;\n      event.preventDefault();\n      if (!save.send.disabled) sendSaveDesign();\n    });\n    save.send?.addEventListener('click', () => {\n      if (!save.send.disabled) sendSaveDesign();\n    });\n    // Nothing submits this form natively: only sendSaveDesign, once the tags are set.\n    save.form.addEventListener('submit', (event) => event.preventDefault());\n    save.form.submit = postSaveForm;\n    // Back from Shopify's hCaptcha page without solving it: the browser may restore this page as\n    // it was (back/forward cache), mid-attempt with Send disabled. End that attempt.\n    window.addEventListener('pageshow', (event) => {\n      if (!event.persisted || !save.awaiting) return;\n      endSaveAttempt();\n      setSaveStatus('');\n    });\n\n    // Back from Shopify's hCaptcha page: Shopify sent the form from there and returned the\n    // shopper to this page with ?customer_posted=true.\n    let pending = null;\n    try {\n      pending = JSON.parse(sessionStorage.getItem(SAVE_PENDING_KEY) || 'null');\n      sessionStorage.removeItem(SAVE_PENDING_KEY);\n    } catch {}\n    if (pending && Date.now() - pending.at < SAVE_PENDING_MS && urlParams.get('customer_posted') === 'true') {\n      try {\n        localStorage.setItem(SAVE_SENT_KEY, String(Date.now()));\n      } catch {}\n      notify({ message: `Sent to ${maskedEmail(pending.email)}. The link in that email opens your design in any browser.`, key: 'save-sent' });\n      clarityEvent('mythumi_save_sent', pending.job || null);\n    }\n\n    // Both Copy link buttons (the row's and the open form's) say Copied for a moment.\n    const markCopied = (on) => {\n      save.copies.forEach((button) => {\n        button.classList.toggle('is-copied', on);\n        const label = button.querySelector('span');\n        if (label) label.textContent = on ? 'Copied' : 'Copy link';\n      });\n    };\n\n    const copySaveLink = async () => {\n      const jobId = save.jobId;\n      let code = save.codeValue;\n      if (!code) {\n        try {\n          code = await restoreCodeFor(jobId);\n        } catch {\n          setSaveStatus(\"We couldn't make a link just now. Please try again.\", { error: true });\n          return;\n        }\n      }\n      const link = restoreLink(code);\n      let copied = false;\n      try {\n        await navigator.clipboard.writeText(link);\n        copied = true;\n      } catch {}\n      if (save.link) {\n        save.link.value = link;\n        save.link.hidden = copied;\n        if (!copied) {\n          save.link.focus();\n          save.link.select();\n        }\n      }\n      clearTimeout(save.copiedTimer);\n      markCopied(copied);\n      if (copied) save.copiedTimer = setTimeout(() => markCopied(false), 2500);\n      setSaveStatus(copied\n        ? 'Link copied. Paste it into Safari or Chrome to pick up where you left off.'\n        : 'Copy this link and open it in Safari or Chrome to pick up where you left off.', { note: copied });\n      clarityEvent('mythumi_save_link', null, { copied, job: jobId || '' });\n    };\n    save.copies.forEach((button) => button.addEventListener('click', copySaveLink));\n  }\n\n  function initKeyboardHandling() {\n    const fields = root.querySelectorAll('input[type=\"text\"], textarea');\n    if (!fields.length) return;\n\n    const vv = window.visualViewport;\n    fields.forEach((field) => {\n      field.addEventListener('focus', () => {\n        keyboard.focusedField = field;\n        document.body.classList.add('aicp-keyboard-open');\n        if (vv) {\n          keyboard.viewportHandler = scrollFocusedFieldIntoView;\n          vv.addEventListener('resize', keyboard.viewportHandler);\n          vv.addEventListener('scroll', keyboard.viewportHandler);\n        } else {\n          // Fallback (older browsers): focus fires after the keyboard starts\n          // appearing; nudge the field into view once it settles.\n          setTimeout(scrollFocusedFieldIntoView, 300);\n        }\n      });\n      field.addEventListener('blur', () => {\n        keyboard.focusedField = null;\n        document.body.classList.remove('aicp-keyboard-open');\n        if (vv && keyboard.viewportHandler) {\n          vv.removeEventListener('resize', keyboard.viewportHandler);\n          vv.removeEventListener('scroll', keyboard.viewportHandler);\n          keyboard.viewportHandler = null;\n        }\n      });\n    });\n  }\n\n  function initRadioGroups() {\n    root.addEventListener('keydown', (event) => {\n      if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) return;\n      const radio = event.target.closest?.('[role=\"radio\"]');\n      const group = radio?.closest?.('[role=\"radiogroup\"]');\n      if (!radio || !group) return;\n      // cards of the other Solo/Duo mode sit in hidden units (only on a product with a Characters option)\n      const radios = [...group.querySelectorAll('[role=\"radio\"]:not([disabled])')].filter((r) => !r.closest('.aicp-style-unit[hidden]'));\n      const index = radios.indexOf(radio);\n      if (index < 0) return;\n      event.preventDefault();\n      const next = event.key === 'Home' ? radios[0]\n        : event.key === 'End' ? radios[radios.length - 1]\n          : radios[(index + (event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1) + radios.length) % radios.length];\n      next?.focus();\n      next?.click();\n    });\n  }\n\n  /* ---------- poster swiper (PC only, JuJuBit-style) ----------\n     Vertical thumbnail rail + swipeable stage. No library: drag with pointer\n     events, wheel, thumb click and dots all drive one translateX offset.\n     CSS keeps it display:none below 990px \u2014 wiring is harmless there. */\n\n  function initPosterSwiper() {\n    const swiper = root.querySelector('[data-aicp-poster-swiper]');\n    if (!swiper || swiper.dataset.bound) return;\n    swiper.dataset.bound = '1';\n    swiper.addEventListener('dragstart', (event) => event.preventDefault());\n\n    const track = swiper.querySelector('[data-aicp-poster-track]');\n    const stage = swiper.querySelector('[data-aicp-poster-stage]');\n    const thumbsHost = swiper.querySelector('[data-aicp-poster-thumbs]');\n    // `let`: a chosen style can swap in its own slides (posterSwiper.show, syncStyleShowcase)\n    let slides = track ? [...track.children] : [];\n    let thumbs = [...swiper.querySelectorAll('[data-aicp-poster-thumb]')];\n    if (!track || !slides.length) return;\n\n    // Dots are generated so slide count stays single-source (the markup).\n    const dotsHost = document.createElement('div');\n    dotsHost.className = 'aicp-poster-swiper__dots';\n    let dots = [];\n    function buildDots() {\n      dotsHost.replaceChildren(...slides.map((_, i) => {\n        const dot = document.createElement('button');\n        dot.type = 'button';\n        dot.className = 'aicp-poster-swiper__dot' + (i === 0 ? ' is-active' : '');\n        dot.setAttribute('aria-label', `Show slide ${i + 1}`);\n        dot.addEventListener('click', () => goTo(i));\n        return dot;\n      }));\n      dots = [...dotsHost.children];\n    }\n    buildDots();\n    if (stage) stage.append(dotsHost);\n\n    let index = 0;\n\n    function goTo(next) {\n      index = Math.max(0, Math.min(slides.length - 1, next));\n      track.style.transform = `translateX(-${index * 100}%)`;\n      thumbs.forEach((t) => t.classList.toggle('is-active', Number(t.dataset.index) === index));\n      dots.forEach((d, i) => d.classList.toggle('is-active', i === index));\n    }\n\n    // one listener on the rail, so swapped-in thumbnails work too\n    swiper.addEventListener('click', (event) => {\n      const thumb = event.target.closest?.('[data-aicp-poster-thumb]');\n      if (thumb && swiper.contains(thumb)) goTo(Number(thumb.dataset.index));\n    });\n\n    // Pointer drag: capture, follow, and settle to the nearest slide.\n    let dragging = false;\n    let startX = 0;\n    let startOffset = 0;\n    let width = 1;\n\n    stage?.addEventListener('pointerdown', (event) => {\n      dragging = true;\n      startX = event.clientX;\n      width = stage.clientWidth || 1;\n      startOffset = -index * width;\n      track.style.transition = 'none';\n      stage.setPointerCapture(event.pointerId);\n    });\n    stage?.addEventListener('pointermove', (event) => {\n      if (!dragging) return;\n      const delta = event.clientX - startX;\n      track.style.transform = `translateX(${startOffset + delta}px)`;\n    });\n    const endDrag = (event) => {\n      if (!dragging) return;\n      dragging = false;\n      track.style.transition = '';\n      const delta = event.clientX - startX;\n      if (Math.abs(delta) > width * 0.15) goTo(index + (delta < 0 ? 1 : -1));\n      else goTo(index);\n    };\n    stage?.addEventListener('pointerup', endDrag);\n    stage?.addEventListener('pointercancel', endDrag);\n    // Horizontal wheel flips one slide per swipe; shift+wheel scrolls the page normally.\n    // A trackpad swipe is a burst of wheel events (finger, then inertia), so flipping\n    // on every event raced to the last or first slide. The deltas add up to a flip,\n    // then the rest of the burst is ignored until the events pause or a new swipe\n    // starts (inertia only decays, so a clearly bigger delta is a fresh swipe).\n    const WHEEL_FLIP_PX = 30;\n    const WHEEL_IDLE_MS = 200;\n    let wheelSum = 0;\n    let wheelLocked = false;\n    let wheelLast = 0;\n    let wheelFlipAt = 0;\n    let wheelIdle = 0;\n    stage?.addEventListener(\n      'wheel',\n      (event) => {\n        if (Math.abs(event.deltaX) <= Math.abs(event.deltaY)) return;\n        event.preventDefault();\n        const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? stage.clientWidth : 1;\n        const dx = event.deltaX * unit;\n        const now = performance.now();\n        clearTimeout(wheelIdle);\n        wheelIdle = setTimeout(() => { wheelLocked = false; wheelSum = 0; }, WHEEL_IDLE_MS);\n        if (wheelLocked && Math.abs(dx) > wheelLast * 1.5 + 4 && now - wheelFlipAt > 250) {\n          wheelLocked = false;\n          wheelSum = 0;\n        }\n        wheelLast = Math.abs(dx);\n        if (wheelLocked) return;\n        wheelSum += dx;\n        if (Math.abs(wheelSum) < WHEEL_FLIP_PX) return;\n        goTo(index + (wheelSum > 0 ? 1 : -1));\n        wheelLocked = true;\n        wheelFlipAt = now;\n        wheelSum = 0;\n      },\n      { passive: false }\n    );\n\n    goTo(0);\n\n    posterSwiper = {\n      // the slides and thumbnails shown now (kept to put back later)\n      current: () => ({ slides: [...track.children], thumbs: thumbsHost ? [...thumbsHost.children] : [] }),\n      show(set) {\n        track.style.transition = 'none';\n        track.replaceChildren(...set.slides);\n        thumbsHost?.replaceChildren(...set.thumbs);\n        slides = [...track.children];\n        thumbs = [...swiper.querySelectorAll('[data-aicp-poster-thumb]')];\n        buildDots();\n        goTo(0);\n        void track.offsetWidth; // land on the first slide before the slide transition comes back\n        track.style.transition = '';\n        thumbsHost?.scrollTo?.({ top: 0, left: 0 });\n      },\n    };\n    syncStyleShowcase();\n  }\n\n  /* Style showcase (Drew, 2026-10-29; the Create page, section setting g_style_showcase): the poster gallery\n     shows the chosen style's own pictures, one <template data-aicp-showcase> per style from its\n     creator_style Showcase images. A style without them shows the page's own pictures again. Runs from\n     refreshCta, which follows every change of the choice. */\n  function syncStyleShowcase() {\n    if (!posterSwiper || !showcaseTemplates.size) return;\n    const name = state.styles[0]?.name || '';\n    const want = showcaseTemplates.has(name) ? name : '';\n    if (want === shownShowcase) return;\n    if (!showcaseSets.has('')) showcaseSets.set('', posterSwiper.current());\n    if (!showcaseSets.has(want)) {\n      const content = showcaseTemplates.get(want).content;\n      const nodes = (selector) => [...(content.querySelector(selector)?.children || [])].map((node) => document.importNode(node, true));\n      showcaseSets.set(want, { slides: nodes('[data-aicp-showcase-slides]'), thumbs: nodes('[data-aicp-showcase-thumbs]') });\n    }\n    posterSwiper.show(showcaseSets.get(want));\n    shownShowcase = want;\n  }\n\n  /* Notes hint (2026-11-03, the Business page's scene styles): a list style with a \"Notes hint\"\n     (creator_style note_hint) shows it as the notes placeholder while it is chosen, e.g. what to say\n     about the truck; any other style puts the page's own placeholder back. Also runs from refreshCta;\n     its state (STYLE_HINTS \u2026) is declared next to the showcase state, before anything can call it. */\n  function syncStyleNoteHint() {\n    if (!STYLE_HINTS.size || !el.notes) return;\n    const hint = STYLE_HINTS.get(state.styles[0]?.name || '') || '';\n    if (hint === shownNoteHint) return;\n    if (notesPlaceholderBase === null) notesPlaceholderBase = el.notes.getAttribute('placeholder') || '';\n    el.notes.setAttribute('placeholder', hint || notesPlaceholderBase);\n    shownNoteHint = hint;\n  }\n\n  /* ---------- rail scrollbars (PC) ----------\n     The style / pose / history rails keep a 10px scrollbar slot reserved, but\n     its track+thumb paint only while `.is-hover` is set. This must be driven\n     from JS: Chrome matches `:hover` on a custom ::-webkit-scrollbar but does\n     not repaint it, so a pure-CSS hover rule leaves the bar invisible.\n     pointermove (not pointerenter) is used so the flag survives the rail's\n     children being re-rendered under a stationary cursor. */\n  function initRailScrollbars() {\n    const rails = [\n      root.querySelector('.aicp-styles'),\n      root.querySelector('[data-aicp-poses-row]'),\n      root.querySelector('[data-aicp-g-history-rail]'),\n    ].filter(Boolean);\n\n    rails.forEach((rail) => {\n      const show = () => rail.classList.add('is-hover');\n      const hide = () => rail.classList.remove('is-hover');\n      rail.addEventListener('pointermove', show);\n      rail.addEventListener('pointerenter', show);\n      rail.addEventListener('pointerleave', hide);\n      // Keyboard users get the same affordance when the rail holds focus.\n      rail.addEventListener('focusin', show);\n      rail.addEventListener('focusout', hide);\n    });\n\n    // Safety net: if the pointer leaves the section entirely (or the rail is\n    // re-rendered), drop any stale flag so a bar never stays stuck on.\n    root.addEventListener('pointerleave', () => {\n      rails.forEach((rail) => rail.classList.remove('is-hover'));\n    });\n  }\n\n  /* ---------- boot ---------- */\n\n  // The storefront header's Create action resets only this product page. It is\n  // deliberately a reload rather than a route change so the active theme\n  // preview, product handle and Shopify context all remain intact.\n  root.querySelector('[data-aicp-header-create]')?.addEventListener('click', () => {\n    window.location.reload();\n  });\n\n  root.querySelector('[data-aicp-header-menu]')?.addEventListener('click', (event) => {\n    const button = event.currentTarget;\n    const expanded = button.getAttribute('aria-expanded') === 'true';\n    button.setAttribute('aria-expanded', String(!expanded));\n    root.querySelector('.aicp-site-header')?.classList.toggle('is-menu-open', !expanded);\n  });\n\n  // URL decides the active category + landing view. Sync the tab highlight to\n  // the URL category so the active tab reflects where the shopper landed.\n  el.catTabs.forEach((tab) => {\n    const active = tab.dataset.aicpCat === INITIAL_CAT;\n    tab.classList.toggle('is-active', active);\n    if (active) tab.setAttribute('aria-current', 'true');\n    else tab.removeAttribute('aria-current');\n  });\n  // PET has no Solo concept (single pet figurine) \u2014 hide the badge there.\n  root.classList.toggle('aicp--pet', INITIAL_CAT === 'pet');\n\n  // The pickers are server-rendered from active_tab, but the live category comes\n  // from ?cat= \u2014 reselect the matching option and refresh the closed label.\n  el.catSelects.forEach((picker) => {\n    const list = picker.querySelector('[data-aicp-cat-list]');\n    const valueEl = picker.querySelector('[data-aicp-cat-value]');\n    if (!list || !valueEl) return;\n    const match = list.querySelector(`.aicp-cat-select__option[data-value=\"${INITIAL_CAT}\"]`);\n    if (!match) return;\n    list.querySelectorAll('.aicp-cat-select__option').forEach((option) => {\n      option.setAttribute('aria-selected', String(option === match));\n      option.classList.toggle('is-selected', option === match);\n    });\n    valueEl.textContent = match.dataset.label || match.textContent.trim();\n  });\n\n  // Fetch option data while Step 1 is visible. Step 2 remains hidden until a\n  // successful upload, so late network responses cannot interrupt page entry.\n  const categoriesReady = ensureStyleCategories();\n\n  // /styles labels for Shopify-managed styles come from the metaobject `name`,\n  // which ops keep in Chinese; shoppers (and the cart's Style line) get an\n  // English name built from the handle instead, e.g. people-pirate -> Pirate.\n  function shopperStyleLabel(label, name) {\n    const text = String(label || '').trim();\n    if (text && !/[\\u3000-\\u9fff\\uff00-\\uffef]/.test(text)) return text;\n    const words = String(name || '').replace(/^(people|pet|trpg|keychain)-/, '').split('-').filter(Boolean);\n    return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') || String(name || '');\n  }\n\n  // Fixed style: confirm the backend knows the name (dynamic Shopify styles are\n  // listed by /styles; the pet page's built-in pet-special comes from\n  // /categories), take its display label, and hide the style step. A missing\n  // name blocks generation \u2014 never fall back to another style.\n  async function initFixedStyle() {\n    fixedStyle.ready = false;\n    fixedStyle.error = null;\n    // Pet pages may still need their pose row; everything else hides the\n    // picker from the start instead of flashing the loading cards.\n    if (INITIAL_CAT !== 'pet') root.classList.add('aicp--fixed-style');\n    refreshCta();\n    let styles = [];\n    try {\n      const body = await call('/styles', { prefetch: true });\n      styles = Array.isArray(body.styles) ? body.styles : [];\n    } catch (error) {\n      fixedStyle.error = 'options';\n      showApiError('options', error, { retry: () => { void initFixedStyle().then(() => syncStyleArea()); } });\n      return;\n    }\n    let match = styles.find((style) => style.name === FIXED_STYLE) || null;\n    let usesPoses = false;\n    if (INITIAL_CAT === 'pet') {\n      await categoriesReady;\n      const record = activeCategoryRecord();\n      if (record?.style === FIXED_STYLE) {\n        usesPoses = record.poses === true;\n        match = match || { name: FIXED_STYLE, label: record.styleLabel || '' };\n      }\n    }\n    if (!match) {\n      // Without /categories a pet page cannot tell yet whether its style is the\n      // built-in pet one: that is a load failure, not a bad style.\n      if (INITIAL_CAT === 'pet' && !styleCategoriesLoaded) {\n        fixedStyle.error = 'options';\n        showApiError('options', null, { retry: () => { void ensureStyleCategories().then(() => initFixedStyle()).then(() => syncStyleArea()); } });\n        refreshCta();\n        return;\n      }\n      fixedStyle.error = 'unknown';\n      console.error(`[realizeit] fixed style \"${FIXED_STYLE}\" is not offered by /styles`);\n      // Usually a style added in Shopify minutes ago (/styles is cached for about\n      // five minutes) or a typo in the product field: block it, offer a retry.\n      notify({\n        message: \"This design isn't available right now. Please try again in a few minutes.\",\n        actions: [{ label: 'Try again', primary: true, run: () => { void initFixedStyle().then(() => syncStyleArea()); } }],\n      });\n      refreshCta();\n      return;\n    }\n    // The product's own English name (custom.creator_style_label) wins; the\n    // name built from the handle is only the fallback when it is empty.\n    fixedStyle.label = (root.dataset.aicpFixedStyleLabel || '').trim() || shopperStyleLabel(match.label, FIXED_STYLE);\n    fixedStyle.usesPoses = usesPoses;\n    fixedStyle.ready = true;\n    state.styles = [{ id: 'fixed', name: FIXED_STYLE, label: fixedStyle.label, directive: '' }];\n    root.classList.toggle('aicp--fixed-style', !usesPoses);\n    refreshCta();\n  }\n\n  // The product's own style list (custom.creator_styles) is drawn now, before /styles answers:\n  // the page already shows these cards and this takes them over (2026-10-21). /styles then only\n  // redraws when the backend does not offer one of them, which drops that card.\n  if (HAS_STYLE_LIST) renderStyles([], { optimistic: true });\n  const stylesReady = FIXED_STYLE ? initFixedStyle() : (INITIAL_CAT === 'people' || HAS_STYLE_LIST) ? call('/styles', { prefetch: true })\n    .then(async (body) => {\n      const offered = body.styles || [];\n      // A category's own style (pet-special) comes from /categories, not /styles; a list may carry it too\n      // (the Create page's Pet card, 2026-10-29).\n      if (HAS_STYLE_LIST) {\n        await categoriesReady;\n        styleCategories.forEach((record) => {\n          if (record.style && !offered.some((style) => style.name === record.style)) offered.push({ name: record.style });\n        });\n      }\n      if (!HAS_STYLE_LIST || STYLE_LIST.some((entry) => !offered.some((style) => style.name === entry.name))) renderStyles(offered);\n      if (!HAS_STYLE_LIST) applyCategoryFilter();\n    })\n    .catch((error) => {\n      el.styles.textContent = '';\n      const message = document.createElement('p');\n      message.className = 'aicp-muted';\n      message.textContent = \"Design options couldn't load. Please refresh and try again.\";\n      el.styles.append(message);\n      showApiError('options', error);\n    }) : Promise.resolve().then(() => {\n      if (INITIAL_CAT === 'trpg') renderStyles([]);\n    });\n\n  selectionReady = stylesReady\n    .then(() => syncStyleArea())\n    .then(() => {\n      syncSelectionLabel();\n      // Landing view is URL-driven: ?aicp_view=gallery \u2192 gallery, else create.\n      switchView(INITIAL_VIEW);\n      if (!FIXED_STYLE && INITIAL_CAT !== 'pet') loadGalleryHistory();\n      const saved = rememberedJob();\n      if (!saved) return;\n      // The resume memory is store-wide: only pick up a run made with one of\n      // this page's styles (another product's run stays for that product).\n      const ownStyles = pageStyleNames.size ? pageStyleNames\n        : new Set([activeCategoryRecord()?.style].filter(Boolean));\n      if (ownStyles.size && !ownStyles.has(saved.style)) return;\n      const escapeSelector = window.CSS?.escape ? window.CSS.escape : (value) => value.replace(/\"/g, '\\\\\"');\n      // Solo / Duo: open the mode the remembered run was made in, so its card is visible.\n      if (productCharactersOption && saved.style) setCreateCharacters(charactersForStyle(saved.style), { syncVariant: false });\n      const savedStyleId = saved.style === 'trpg' && !HAS_STYLE_LIST ? 'trpg-default' : saved.style;\n      const card = savedStyleId && el.styles.querySelector(`[data-style=\"${escapeSelector(savedStyleId)}\"]`);\n      if (card && !card.classList.contains('is-selected')) card.click();\n      resume(saved);\n    });\n\n  initKeyboardHandling();\n  initRadioGroups();\n  initPosterSwiper();\n  focusMobileCreateOnEntry();\n  setStage3DProgress(false);\n\n  if (el.backTop) {\n    const scroller = document.querySelector('.page-wrapper');\n    const readTop = () => scroller?.scrollTop || window.scrollY;\n    const updateBackTop = () => {\n      const mobile = window.matchMedia?.('(max-width: 749px)').matches;\n      el.backTop.hidden = !mobile || readTop() < window.innerHeight * 0.65;\n    };\n    // Some theme layouts keep `.page-wrapper` in the DOM without making it\n    // the active scroller, so always watch the document as well.\n    window.addEventListener('scroll', updateBackTop, { passive: true });\n    if (scroller) scroller.addEventListener('scroll', updateBackTop, { passive: true });\n    window.addEventListener('resize', updateBackTop, { passive: true });\n    updateBackTop();\n    el.backTop.addEventListener('click', () => {\n      const start = readTop();\n      if (start <= 0) return;\n      const duration = Math.min(900, Math.max(360, start * 0.32));\n      const began = performance.now();\n      const tick = (now) => {\n        const progress = Math.min(1, (now - began) / duration);\n        const top = start * (1 - progress);\n        if (scroller && scroller.scrollHeight > scroller.clientHeight) scroller.scrollTo({ top, behavior: 'auto' });\n        else window.scrollTo({ top, behavior: 'auto' });\n        if (progress < 1) window.requestAnimationFrame(tick);\n      };\n      window.requestAnimationFrame(tick);\n    });\n  }\n\n  initRailScrollbars();\n\n  // The upload tile is a native <label> around the file input, so on a slow phone a shopper can pick\n  // a photo before this module has started (2026-10-21): take that photo now.\n  if (el.file?.files?.length) el.file.dispatchEvent(new Event('change'));\n}\n\n/* ---------- start (2026-10-21) ----------\n   The section loads this module with `async`: it starts as soon as the page has been parsed\n   (readyState 'interactive', which also covers the detail sections below the creator), instead of\n   waiting in line behind GSAP and the theme's ~50 deferred scripts. On a phone on slow 4G that\n   line ended at ~8-9 s; the upload tile, the zoom buttons and the cards did nothing until then.\n   Nothing here needs those scripts: the cart drawer is only looked up on a click.\n   (Tried and dropped the same day: waiting for the first poster slide to be painted first. On a\n   phone on slow 4G it made that LCP picture ~1 s later on the TTRPG page, not sooner - Shopify's\n   own scripts then fetch their files while it is still coming in - and the creator ~1 s later.) */\nfunction bootCreatorPages() {\n  document.querySelectorAll('[data-aicp]').forEach(initCreatorPage);\n  loadAnimationLibs();\n}\n\nif (document.readyState === 'loading') {\n  document.addEventListener('readystatechange', bootCreatorPages, { once: true });\n} else {\n  bootCreatorPages();\n}\n\n/* GSAP only adds motion (scroll reveals, the accordion, the phone in the size guide), so its two\n   files (~45 KB) are fetched once the creator is up rather than ahead of it. Reduced motion skips\n   every animation, so they are not fetched at all then. */\nfunction loadAnimationLibs() {\n  if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return;\n  const holder = document.querySelector('[data-aicp-gsap]');\n  if (!holder) return;\n  const start = () => {\n    if (window.gsap && window.ScrollTrigger) {\n      initAicpAnimations();\n      return;\n    }\n    const urls = [holder.dataset.aicpGsap, holder.dataset.aicpScrollTrigger].filter(Boolean);\n    let pending = urls.length;\n    urls.forEach((src) => {\n      const script = document.createElement('script');\n      script.src = src;\n      // fetched together, run in this order (ScrollTrigger after GSAP)\n      script.async = false;\n      script.onload = () => {\n        pending -= 1;\n        if (!pending) initAicpAnimations();\n      };\n      document.head.append(script);\n    });\n  };\n  if ('requestIdleCallback' in window) window.requestIdleCallback(start, { timeout: 1500 });\n  else window.setTimeout(start, 200);\n}\n\n/* ---------- entrance & scroll animations (GSAP, progressive enhancement) ----------\n   Nothing is hidden in HTML or CSS: the from-states below are painted by GSAP\n   only AFTER the library has loaded, so crawlers and no-JS visitors always get\n   the complete static page (text, images, prices \u2014 SEO untouched). Only\n   transform/opacity animate, and everything is skipped when the shopper\n   prefers reduced motion. ScrollTrigger watches .page-wrapper because the\n   theme scrolls inside that element, not the window. */\nfunction initAicpAnimations() {\n  if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return;\n  if (!window.gsap || !window.ScrollTrigger) return;\n  const gsap = window.gsap;\n  const ScrollTrigger = window.ScrollTrigger;\n  gsap.registerPlugin(ScrollTrigger);\n  const scroller = document.querySelector('.page-wrapper');\n  if (scroller) ScrollTrigger.defaults({ scroller });\n\n  // GSAP arrives after the page is up: a block the shopper can already see (or has scrolled past)\n  // is left as it is rather than hidden and faded in again.\n  const alreadyShown = (node) => node.getBoundingClientRect().top < window.innerHeight * 0.85;\n\n  document.querySelectorAll('[data-aicp]').forEach((root) => {\n    const reveal = (selector, vars = {}) => {\n      const target = root.querySelector(selector);\n      if (!target || alreadyShown(target)) return;\n      gsap.from(target, {\n        duration: 0.72,\n        ease: 'power3.out',\n        autoAlpha: 0,\n        y: 24,\n        scrollTrigger: { trigger: target, start: 'top 85%', once: true },\n        ...vars,\n      });\n    };\n    const revealGroup = (selector, vars = {}) => {\n      const targets = root.querySelectorAll(selector);\n      if (!targets.length || alreadyShown(targets[0].parentElement)) return;\n      gsap.from(targets, {\n        duration: 0.7,\n        ease: 'power3.out',\n        autoAlpha: 0,\n        y: 22,\n        stagger: 0.08,\n        scrollTrigger: { trigger: targets[0].parentElement, start: 'top 85%', once: true },\n        ...vars,\n      });\n    };\n\n    // 2026-10-21: the pose / style cards come with the page, so they are no longer faded in. And\n    // when this runs well after the first paint (a phone on 4G: ~4 s later) the shopper has been\n    // looking at the title and the upload box already: hiding them to fade them in would blink.\n    const firstPaint = performance.getEntriesByName?.('first-contentful-paint')?.[0]?.startTime;\n    if (firstPaint == null || performance.now() - firstPaint < 1200) {\n      gsap.from(root.querySelectorAll('.aicp-title, .aicp-create-form .aicp-upload'), {\n        duration: 0.68,\n        ease: 'power3.out',\n        autoAlpha: 0,\n        y: 18,\n        stagger: 0.1,\n      });\n    }\n    reveal('.aicp-steps');\n    reveal('.aicp-marketing > .aicp-marketing__title');\n    reveal('.aicp-marketing > .aicp-marketing__sub', { delay: 0.08 });\n    reveal('.aicp-detail-nav');\n    revealGroup('.aicp-real-card');\n    reveal('.aicp-size-guide__panel');\n    reveal('.aicp-material-guide');\n    revealGroup('.aicp-finish-guide__comparison article');\n    reveal('.aicp-finish-guide__editorial');\n    reveal('.aicp-making');\n  });\n}\n"],
  "mappings": "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",
  "names": ["entry", "state", "resume", "blob"]
}
