{
  "version": 3,
  "sources": ["/cdn/shop/t/11/assets/printnz-uploader.js?nc=22655"],
  "sourcesContent": ["/**\n * PrintNZ uploader \u2014 the printnz.photos order app as a Shopify theme section.\n * Ported from printnz-cloud/public/app.js; markup is sections/printnz-uploader.liquid.\n *\n * Flow:\n *   1. User adds photos \u2192 prepare-upload on Build4 allocates an order + presigned URLs\n *   2. Each photo PUTs directly to R2\n *   3. Place order \u2192 /print-orders/complete\n *   4. Build4 downloads from R2, generates contact sheet, prints\n *\n * Fulfilment: \"instore\" (pay at the counter), or pay online via Shopify and\n * \"collect\" / \"ship\". For online orders /complete returns a checkout_url (a\n * Shopify draft-order invoice) and we redirect there; Build4 holds the photos\n * until Shopify reports the order paid.\n *\n * Pricing tables must stay in sync with Build4 modules/print-pricing.ts \u2014 the\n * server charges its own figure for online orders.\n */\n(function () {\n  'use strict';\n\n  // \u2500\u2500\u2500 Fixed-size pricing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // Quantity breaks are the till's (BPRO STOCK.BREAK_Qn/BREAK_Dn on T46, T57,\n  // T68, T810, T812 \u2014 best reached tier wins), so online and the counter charge\n  // the same. They're also listed on the printing page and product descriptions.\n  const pricingTiers = {\n    '6x4': [\n      { min: 1, max: 19, price: 1.50 },\n      { min: 20, max: 49, price: 0.80 },\n      { min: 50, max: 99, price: 0.60 },\n      { min: 100, max: Infinity, price: 0.40 }\n    ],\n    '5x7': [\n      { min: 1, max: 29, price: 2.50 },\n      { min: 30, max: Infinity, price: 1.00 }\n    ],\n    '6x8': [\n      { min: 1, max: 4, price: 7.00 },\n      { min: 5, max: 9, price: 4.90 },\n      { min: 10, max: Infinity, price: 4.20 }\n    ],\n    '8x10': [\n      { min: 1, max: 4, price: 12.00 },\n      { min: 5, max: 9, price: 8.40 },\n      { min: 10, max: Infinity, price: 6.00 }\n    ],\n    '8x12': [\n      { min: 1, max: 4, price: 15.00 },\n      { min: 5, max: 9, price: 10.50 },\n      { min: 10, max: Infinity, price: 7.50 }\n    ]\n  };\n\n  const pricing = {\n    'passport': 15.00,\n    '6x4': 1.50,\n    '5x7': 2.50,\n    '6x8': 7.00,\n    '8x10': 12.00,\n    '8x12': 15.00,\n    'A4': 15.00,\n    'A3': 37.50,\n    'A2': 55.00,\n    'A1': 75.00\n  };\n\n  // Sizes where each line picks its own paper instead of the global gloss/matt\n  // finish. The paper key is sent as the line's `finish`, which also tells the\n  // shop which printer it runs on: glossy/matte \u2192 Fujifilm, Ilford \u2192 Epson.\n  // A3\u2013A1 match the store's product variants. A4 can go either way: Fuji\n  // gloss/matt, or Epson on Ilford (rung up in the till as 8x12/T812 at the\n  // fine art price). Must match PRICES in Build4 print-pricing.ts.\n  const PAPER_LABELS = {\n    glossy:          'Fujifilm Gloss',\n    matte:           'Fujifilm Matt',\n    luster:          'Ilford Luster',\n    cotton_smooth:   'Ilford Smooth Cotton Rag',\n    cotton_textured: 'Ilford Textured Cotton Rag',\n  };\n  const PAPER_PRICES = {\n    'A4': { glossy: 15.00, matte: 15.00, luster: 30.00, cotton_smooth: 35.00, cotton_textured: 35.00 },\n    'A3': { luster: 37.50, cotton_smooth: 42.50, cotton_textured: 42.50 },\n    'A2': { luster: 55.00, cotton_smooth: 65.00, cotton_textured: 65.00 },\n    'A1': { luster: 75.00, cotton_smooth: 85.00, cotton_textured: 85.00 },\n  };\n  // Too big for the print envelope \u2014 posted in a tube.\n  const TUBE_SIZES = new Set(['A3', 'A2', 'A1']);\n  function hasPaperChoice(size) { return Object.prototype.hasOwnProperty.call(PAPER_PRICES, size); }\n  function papersFor(size) { return Object.keys(PAPER_PRICES[size] || {}); }\n  // Keep line.paper valid for its size (first listed paper otherwise).\n  function normalizePaper(line) {\n    const papers = papersFor(line.size);\n    if (papers.length && !papers.includes(line.paper)) line.paper = papers[0];\n  }\n\n  const sizeOrder = ['6x4', '5x7', '6x8', '8x10', '8x12', 'A4', 'A3', 'A2', 'A1', 'custom', 'passport'];\n  const sizeLabels = { passport: 'Passport Photo', custom: 'Custom Poster' };\n  function sizeLabel(s) { return sizeLabels[s] || s; }\n\n  // \u2500\u2500\u2500 Custom Poster pricing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // In-house Poster Price Calculator (xlsx), in inches:\n  //   RRP = ((coef \u00D7 H) + (W / 24) \u00D7 0.11 \u00D7 H) \u00D7 7 + flat\n  // Customers enter cm; we convert before pricing.\n  const POSTER_PAPERS = {\n    luster:          { label: 'Ilford Luster',   coef: 0.26, flat: 10 },\n    cotton_smooth:   { label: 'Cotton Smooth',   coef: 0.41, flat: 15 },\n    cotton_textured: { label: 'Cotton Textured', coef: 0.56, flat: 15 },\n  };\n  // Smaller (Fuji minilab) sizes use standard photo paper, no Ilford/Cotton.\n  const FUJI_FINISHES = {\n    glossy: { label: 'Glossy' },\n    lustre: { label: 'Lustre' },\n  };\n  const ROLL_WIDTH_IN = 24;\n  const CM_PER_IN = 2.54;\n  const POSTER_W_MIN_CM = 1;\n  const POSTER_W_MAX_CM = 60;\n  const POSTER_H_MIN_CM = 1;\n  const POSTER_H_MAX_CM = 243;\n  // Fuji minilab tiers: short-side cutoff (inches) \u2192 flat price.\n  const FUJI_TIERS = [\n    { maxShortIn: 6,    price: 10 },\n    { maxShortIn: 8.27, price: 15 },\n  ];\n  const FUJI_MAX_SHORT_IN = FUJI_TIERS[FUJI_TIERS.length - 1].maxShortIn;\n\n  function cmToIn(cm) { return cm / CM_PER_IN; }\n  function getPaperDef(paper) { return POSTER_PAPERS[paper] || FUJI_FINISHES[paper] || null; }\n  function defaultPaperFor(fuji) { return fuji ? 'glossy' : 'luster'; }\n\n  function isFujiSpec(p) {\n    const h = cmToIn(Number(p.height_cm));\n    const w = cmToIn(Number(p.width_cm));\n    if (!isFinite(h) || !isFinite(w)) return false;\n    return Math.min(h, w) < FUJI_MAX_SHORT_IN;\n  }\n\n  function isPosterSpecValid(p) {\n    if (!p) return false;\n    const h = Number(p.height_cm), w = Number(p.width_cm);\n    if (!isFinite(h) || !isFinite(w)) return false;\n    if (h < POSTER_H_MIN_CM || h > POSTER_H_MAX_CM) return false;\n    if (w < POSTER_W_MIN_CM || w > POSTER_W_MAX_CM) return false;\n    if (!getPaperDef(p.paper)) return false;\n    // Paper must match the printer this size routes to.\n    const fuji = isFujiSpec(p);\n    if (fuji && !FUJI_FINISHES[p.paper]) return false;\n    if (!fuji && !POSTER_PAPERS[p.paper]) return false;\n    return true;\n  }\n\n  function calculatePosterUnitPrice(p) {\n    if (!isPosterSpecValid(p)) return 0;\n    const h = cmToIn(Number(p.height_cm));\n    const w = cmToIn(Number(p.width_cm));\n    const shortIn = Math.min(h, w);\n    if (shortIn < FUJI_MAX_SHORT_IN) {\n      const tier = FUJI_TIERS.find(t => shortIn <= t.maxShortIn);\n      if (tier) return tier.price;\n    }\n    const paper = POSTER_PAPERS[p.paper];\n    const cost = (paper.coef * h) + ((w / ROLL_WIDTH_IN) * 0.11) * h;\n    return Math.round((cost * 7 + paper.flat) * 100) / 100;\n  }\n\n  // The till has no A4 code: Fuji A4 is rung up as 8x12 (T812), so it shares\n  // 8x12's breaks and its count. Ilford A4 is fine art \u2014 own price, no breaks.\n  // Must match tierKey in Build4 print-pricing.ts.\n  function tierKey(size, paper) {\n    return size === 'A4' && (paper === 'glossy' || paper === 'matte') ? '8x12' : size;\n  }\n\n  // `quantity` is the total for the line's tierKey across the whole order.\n  function calculateTieredPrice(size, quantity, paper) {\n    if (size === 'custom') return 0; // posters priced per-item, not via tiers\n    const base = hasPaperChoice(size)\n      ? (PAPER_PRICES[size][paper] ?? PAPER_PRICES[size][papersFor(size)[0]])\n      : (pricing[size] || 0);\n    const tiers = pricingTiers[tierKey(size, paper)];\n    const tier = tiers && tiers.find(t => quantity >= t.min && quantity <= t.max);\n    return tier ? tier.price : base;\n  }\n\n  // Must match SHIPPING_* in Build4 modules/printnz-checkout.ts.\n  const SHIPPING_PRINTS = 6.50;\n  const SHIPPING_POSTER_TUBE = 12.50;\n\n  // Quantity is bounded 1\u2013100. parseInt alone lets negatives through, which\n  // would produce a negative line total and silently discount the order.\n  function clampQty(v) {\n    const n = parseInt(v, 10);\n    if (!isFinite(n)) return 1;\n    return Math.min(100, Math.max(1, n));\n  }\n\n  // \u2500\u2500\u2500 Allowed file types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const ALLOWED_MIME_TYPES = new Set([\n    'image/jpeg', 'image/pjpeg',\n    'image/png',\n    'image/bmp', 'image/x-bmp', 'image/x-ms-bmp',\n    'image/tiff', 'image/tiff-fx',\n    'image/heic', 'image/heif',\n  ]);\n  const ALLOWED_EXTS = new Set(['jpg', 'jpeg', 'png', 'bmp', 'tif', 'tiff', 'heic', 'heif']);\n  const MAX_CLIENT_BYTES = 100 * 1024 * 1024;\n\n  function extOf(name) {\n    const m = String(name || '').toLowerCase().match(/\\.([a-z0-9]+)$/);\n    return m ? m[1] : '';\n  }\n  // MIME match OR extension match \u2014 mobile pickers (Android Google Photos\n  // especially) often hand over file.type === \"\" for perfectly good JPEGs.\n  function isAcceptedFile(file) {\n    if (file.type && ALLOWED_MIME_TYPES.has(file.type.toLowerCase())) return true;\n    return ALLOWED_EXTS.has(extOf(file.name));\n  }\n\n  // Browsers can only thumbnail some of these; TIFF/HEIC (outside Safari) get a placeholder.\n  const PREVIEWABLE = new Set(['jpg', 'jpeg', 'png', 'bmp', 'gif', 'webp']);\n\n  function escapeHTML(s) {\n    return String(s).replace(/[&<>\"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]));\n  }\n  function money(n) { return '$' + n.toFixed(2); }\n\n  // A failure is worth retrying when it's network-level (no status \u2014 includes\n  // Cloudflare error pages, which lack CORS headers) or a 5xx. 4xx won't improve.\n  function isTransientError(err) {\n    if (err && typeof err.status === 'number') return err.status >= 500;\n    return true;\n  }\n\n  // Retry schedule for /complete. A backend restart takes ~75s and we lost a\n  // real order to one; these delays ride out ~95s. Photos are already in R2,\n  // so retrying with the identical payload is cheap.\n  const COMPLETE_RETRY_DELAYS_MS = [5000, 15000, 30000, 45000];\n\n  function defaultPrintLine(size, paper) {\n    const line = {\n      size: size || '6x4',\n      quantity: 1,\n      paper: paper || null, // only used by sizes with a paper choice\n      poster: { paper: 'luster', height_cm: 36, width_cm: 51 },\n    };\n    normalizePaper(line);\n    return line;\n  }\n\n  function init(root) {\n    // Theme editor re-renders and duplicate script tags must not double-bind.\n    if (root.dataset.pnzReady) return;\n    root.dataset.pnzReady = '1';\n    const API_BASE = root.dataset.apiBase;\n    const API_KEY = root.dataset.apiKey;\n    // Size of the product page this was opened from (e.g. 5x7); 6x4 otherwise.\n    const sizeFromPage = sizeOrder.find(s => s.toLowerCase() === String(root.dataset.defaultSize || '').toLowerCase());\n    const DEFAULT_SIZE = sizeFromPage && sizeFromPage !== 'custom' ? sizeFromPage : '6x4';\n    // Paper of the variant the page was opened on (A3\u2013A1 cotton rag links).\n    const DEFAULT_PAPER = root.dataset.defaultPaper;\n    const $ = (name) => root.querySelector(`[data-pnz=\"${name}\"]`);\n\n    const el = {\n      form: $('form'), drop: $('drop'), fileInput: $('file-input'),\n      finishWrap: $('finish-wrap'), finish: $('finish'),\n      progress: $('progress'), progressBar: $('progress-bar'), progressText: $('progress-text'),\n      previews: $('previews'), more: $('more'), moreInput: $('more-input'),\n      count: $('count'), subtotal: $('subtotal'), shippingRow: $('shipping-row'),\n      shipping: $('shipping'), total: $('total'), shipPrice: $('ship-price'),\n      name: $('name'), email: $('email'), notes: $('notes'),\n      error: $('error'), submit: $('submit'), secure: $('secure'),\n      done: $('done'), orderId: $('order-id'), newOrder: $('new-order'),\n      dialog: $('dialog'), dialogBody: $('dialog-body'),\n      dialogCancel: $('dialog-cancel'), dialogConfirm: $('dialog-confirm'),\n      overlay: $('overlay'), overlayText: $('overlay-text'),\n    };\n    const fulfilmentInputs = root.querySelectorAll('.pnz-fulfil input[type=\"radio\"]');\n\n    if (root.dataset.customerName) el.name.value = root.dataset.customerName;\n    if (root.dataset.customerEmail) el.email.value = root.dataset.customerEmail;\n\n    // \u2500\u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let uploadedFiles = [];\n    let backgroundUploads = {};\n    let totalBytesToUpload = 0;\n    let totalBytesUploaded = 0;\n    let currentOrderId = null;\n    let currentR2Prefix = null;\n    let fileIndexCounter = 0;\n    let prepareChain = Promise.resolve(); // serializes /prepare-upload calls (see handleFiles)\n\n    function resetState() {\n      uploadedFiles = [];\n      backgroundUploads = {};\n      currentOrderId = null;\n      currentR2Prefix = null;\n      fileIndexCounter = 0;\n      totalBytesToUpload = 0;\n      totalBytesUploaded = 0;\n    }\n\n    function getFulfilment() {\n      const checked = root.querySelector('.pnz-fulfil input:checked');\n      return checked ? checked.value : 'instore';\n    }\n    function isOnline(f) { return f === 'ship' || f === 'collect'; }\n\n    // A3\u2013A1 and large-format posters ship in a tube; Fuji sizes fit the print envelope.\n    // Must match needsTube in Build4 modules/printnz-checkout.ts.\n    function shippingCost() {\n      const needsTube = uploadedFiles.some(f => f.options.prints.some(line => TUBE_SIZES.has(line.size) ||\n        (line.size === 'custom' && isPosterSpecValid(line.poster) && !isFujiSpec(line.poster))));\n      return needsTube ? SHIPPING_POSTER_TUBE : SHIPPING_PRINTS;\n    }\n    function shippingFor(fulfilment) { return fulfilment === 'ship' ? shippingCost() : 0; }\n\n    function showError(msg) {\n      el.error.textContent = msg;\n      el.error.hidden = !msg;\n      if (msg) el.error.scrollIntoView({ behavior: 'smooth', block: 'center' });\n    }\n\n    // \u2500\u2500\u2500 API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    function apiCall(path, body) {\n      return fetch(`${API_BASE}${path}`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY },\n        body: JSON.stringify(body),\n      }).then(res => {\n        if (!res.ok) {\n          // Error bodies aren't always JSON (Cloudflare serves HTML when the\n          // origin is down), so read text and try to parse.\n          return res.text().then(t => {\n            let msg = '';\n            try { msg = JSON.parse(t).error; } catch (e) { /* not JSON */ }\n            const err = new Error(msg || `HTTP ${res.status}`);\n            err.status = res.status;\n            throw err;\n          });\n        }\n        return res.json();\n      });\n    }\n\n    // Prepare-upload only needs bytes/type for presigned URLs \u2014 print options\n    // are decided in /complete. Dummy fields keep the server's PrepareFile happy.\n    function buildFileDesc(f) {\n      return { name: f.name, size: f.size, type: f.type || 'image/jpeg', print_size: '6x4', finish: 'glossy', qty: 1 };\n    }\n\n    // \u2500\u2500\u2500 Progress \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    function ongoingCount() {\n      return Object.values(backgroundUploads).filter(u => u.status === 'uploading').length;\n    }\n\n    function updateOverallProgress() {\n      el.progress.hidden = uploadedFiles.length === 0;\n      if (totalBytesToUpload <= 0) {\n        el.progressBar.style.width = '0%';\n        el.progressText.textContent = '';\n        updateSubmitLabel();\n        return;\n      }\n      const percent = Math.max(0, Math.min(Math.round((totalBytesUploaded / totalBytesToUpload) * 100), 100));\n      el.progressBar.style.width = percent + '%';\n      const ongoing = ongoingCount();\n      const completed = uploadedFiles.filter(f => f.uploadStatus === 'completed').length;\n      el.progress.classList.toggle('is-done', percent === 100 && ongoing === 0);\n      if (percent === 100 && ongoing === 0) {\n        el.progressText.textContent = 'All photos uploaded';\n      } else if (ongoing > 0) {\n        el.progressText.textContent = `Uploading ${ongoing} of ${uploadedFiles.length} photos (${percent}%)`;\n      } else {\n        el.progressText.textContent = `Uploaded ${completed} of ${uploadedFiles.length} photos`;\n      }\n      updateSubmitLabel();\n    }\n\n    function updateSubmitLabel() {\n      const ongoing = ongoingCount();\n      if (ongoing > 0) {\n        el.submit.textContent = `Uploading\u2026 (${ongoing} to go)`;\n      } else {\n        el.submit.textContent = isOnline(getFulfilment()) ? 'Continue to payment' : 'Place order';\n      }\n    }\n\n    // \u2500\u2500\u2500 File handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    el.drop.addEventListener('dragover', (e) => { e.preventDefault(); el.drop.classList.add('is-over'); });\n    el.drop.addEventListener('dragleave', () => el.drop.classList.remove('is-over'));\n    el.drop.addEventListener('drop', (e) => {\n      e.preventDefault();\n      el.drop.classList.remove('is-over');\n      handleFiles(e.dataTransfer.files);\n    });\n    [el.fileInput, el.moreInput].forEach(input => input.addEventListener('change', function () {\n      if (this.files && this.files.length > 0) { handleFiles(this.files); this.value = ''; }\n    }));\n\n    function handleFiles(files) {\n      if (!files || files.length === 0) return;\n      showError('');\n\n      const fileArray = Array.from(files);\n      const rejected = fileArray.filter(f => !isAcceptedFile(f));\n      const tooBig = fileArray.filter(f => isAcceptedFile(f) && f.size > MAX_CLIENT_BYTES);\n      const imageFiles = fileArray.filter(f => isAcceptedFile(f) && f.size <= MAX_CLIENT_BYTES);\n\n      const problems = [];\n      if (rejected.length) {\n        const names = rejected.map(f => f.name).slice(0, 5).join(', ');\n        const more = rejected.length > 5 ? ` and ${rejected.length - 5} more` : '';\n        problems.push(`${rejected.length} file(s) aren't a supported format (${names}${more}). We accept JPG, PNG, BMP, TIFF and iPhone HEIC photos.`);\n      }\n      if (tooBig.length) problems.push(`${tooBig.length} file(s) are over 100MB: ${tooBig.map(f => f.name).slice(0, 5).join(', ')}.`);\n      if (problems.length) showError(problems.join(' '));\n      if (imageFiles.length === 0) return;\n\n      const newFiles = [];\n      imageFiles.forEach(file => {\n        if (uploadedFiles.some(f => f.name === file.name && f.size === file.size)) return;\n        totalBytesToUpload += file.size;\n        file.id = Date.now() + Math.random().toString(36).slice(2, 11);\n        file.options = { prints: [defaultPrintLine(DEFAULT_SIZE, DEFAULT_PAPER)] };\n        file.uploadStatus = 'pending';\n        file.fileIndex = fileIndexCounter++;\n        uploadedFiles.push(file);\n        newFiles.push(file);\n        createPreviewElement(file);\n      });\n      if (newFiles.length === 0) return;\n\n      const prepareFiles = newFiles.map(buildFileDesc);\n      // Capture this batch's start index now \u2014 fileIndexCounter keeps moving.\n      const batchStartIndex = newFiles[0].fileIndex;\n      const firstBatch = uploadedFiles.length === newFiles.length;\n\n      // Serialize prepare-upload calls. Two batches racing would both see\n      // currentOrderId === null and get two separate orders, orphaning the\n      // first batch's files under an order /complete never references.\n      prepareChain = prepareChain.then(() => {\n        const prepareBody = { files: prepareFiles };\n        if (currentOrderId) {\n          prepareBody.order_id = currentOrderId;\n          prepareBody.file_index_start = batchStartIndex;\n        }\n        return apiCall('/print-orders/prepare-upload', prepareBody).then(data => {\n          if (!currentOrderId) {\n            currentOrderId = data.order_id;\n            currentR2Prefix = data.r2_prefix;\n          }\n          data.upload_urls.forEach((urlInfo, i) => {\n            const file = newFiles[i];\n            if (!file || !uploadedFiles.includes(file)) return; // removed while preparing\n            file.r2Key = urlInfo.r2_key;\n            file.uploadStatus = 'uploading';\n            uploadFileToR2(file, urlInfo.put_url);\n          });\n        });\n      }).catch(err => {\n        console.error('Failed to prepare upload:', err);\n        newFiles.forEach(f => markFileError(f.id, 'Could not start upload: ' + err.message));\n      });\n\n      updateOrderSummary();\n      el.more.hidden = false;\n      updateOverallProgress();\n\n      if (firstBatch) {\n        setTimeout(() => el.finishWrap.scrollIntoView({ behavior: 'smooth', block: 'start' }), 400);\n      }\n    }\n\n    // \u2500\u2500\u2500 Upload to R2 via presigned PUT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    function cardFor(fileId) { return el.previews.querySelector(`[data-file-id=\"${fileId}\"]`); }\n\n    function setCardStatus(fileId, percent, text, state) {\n      const card = cardFor(fileId);\n      if (!card) return;\n      const wrap = card.querySelector('.pnz-card__status');\n      wrap.hidden = false;\n      wrap.className = 'pnz-card__status' + (state ? ' is-' + state : '');\n      wrap.querySelector('span').style.width = percent + '%';\n      wrap.querySelector('p').textContent = text;\n      if (state === 'done') setTimeout(() => { wrap.hidden = true; }, 2000);\n    }\n\n    function uploadFileToR2(file, putUrl) {\n      const xhr = new XMLHttpRequest();\n      backgroundUploads[file.id] = { xhr, status: 'uploading', bytesTotal: file.size, bytesUploaded: 0 };\n      setCardStatus(file.id, 0, 'Uploading 0%');\n\n      xhr.open('PUT', putUrl, true);\n      xhr.setRequestHeader('Content-Type', file.type || 'image/jpeg');\n      // Flaky mobile networks can stop firing progress without ever erroring.\n      xhr.timeout = 10 * 60 * 1000;\n\n      xhr.upload.onprogress = function (e) {\n        if (!e.lengthComputable) return;\n        const bg = backgroundUploads[file.id];\n        if (!bg) return;\n        totalBytesUploaded += e.loaded - bg.bytesUploaded;\n        bg.bytesUploaded = e.loaded;\n        const pct = Math.round((e.loaded / e.total) * 100);\n        setCardStatus(file.id, pct, `Uploading ${pct}%`);\n        updateOverallProgress();\n      };\n\n      xhr.onload = function () {\n        const bg = backgroundUploads[file.id];\n        if (!bg) return;\n        if (xhr.status >= 200 && xhr.status < 300) {\n          file.uploadStatus = 'completed';\n          bg.status = 'completed';\n          totalBytesUploaded += bg.bytesTotal - bg.bytesUploaded;\n          bg.bytesUploaded = bg.bytesTotal;\n          setCardStatus(file.id, 100, 'Uploaded', 'done');\n          updateOverallProgress();\n        } else {\n          markFileError(file.id, `Upload failed (${xhr.status})`);\n        }\n      };\n      xhr.onerror = function () { markFileError(file.id, 'Network error \u2014 remove and re-add this photo'); };\n      xhr.ontimeout = function () { markFileError(file.id, 'Upload timed out \u2014 check your connection'); };\n      xhr.send(file);\n    }\n\n    // Sets BOTH the background status and file.uploadStatus so submit-time\n    // checks see R2 and prepare failures alike.\n    function markFileError(fileId, message) {\n      if (backgroundUploads[fileId]) backgroundUploads[fileId].status = 'error';\n      const file = uploadedFiles.find(f => f.id === fileId);\n      if (file) file.uploadStatus = 'error';\n      setCardStatus(fileId, 0, message, 'error');\n      updateOverallProgress();\n    }\n\n    // \u2500\u2500\u2500 Photo cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    function createPreviewElement(file) {\n      const card = document.createElement('div');\n      card.className = 'pnz-card';\n      card.dataset.fileId = file.id;\n      card.innerHTML = `\n        <button type=\"button\" class=\"pnz-card__remove\" aria-label=\"Remove ${escapeHTML(file.name)}\">&times;</button>\n        <div class=\"pnz-card__thumb\"><span>${escapeHTML(extOf(file.name).toUpperCase() || 'PHOTO')}</span></div>\n        <p class=\"pnz-card__name\">${escapeHTML(file.name)}</p>\n        <div class=\"pnz-card__status\" hidden><div class=\"pnz-card__bar\"><span></span></div><p></p></div>\n        <div class=\"pnz-lines\"></div>\n        <button type=\"button\" class=\"pnz-card__add\">+ Add another size</button>\n      `;\n      el.previews.appendChild(card);\n\n      // Object URLs are instant and don't hold a base64 copy of a 30MB photo in memory.\n      if (PREVIEWABLE.has(extOf(file.name)) || (file.type && /^image\\/(jpeg|png|gif|webp|bmp)$/.test(file.type))) {\n        const img = new Image();\n        img.alt = '';\n        img.decoding = 'async';\n        img.onload = () => { const t = card.querySelector('.pnz-card__thumb'); t.innerHTML = ''; t.appendChild(img); };\n        img.src = file.thumbUrl = URL.createObjectURL(file);\n      }\n\n      card.querySelector('.pnz-card__remove').addEventListener('click', () => removeFile(file.id));\n      // Delegated \u2014 print lines are re-rendered on add/remove.\n      card.addEventListener('change', handleLineChange);\n      card.addEventListener('input', handleLineInput);\n      card.addEventListener('click', handleLineClick);\n      renderPrintLines(file.id);\n    }\n\n    function paperOptionsHTMLFor(line) {\n      const fuji = isFujiSpec(line.poster);\n      const set = fuji ? FUJI_FINISHES : POSTER_PAPERS;\n      if (!set[line.poster.paper]) line.poster.paper = defaultPaperFor(fuji);\n      return Object.keys(set).map(k =>\n        `<option value=\"${k}\"${k === line.poster.paper ? ' selected' : ''}>${set[k].label}</option>`).join('');\n    }\n\n    function paperOptionsHTML(file, idx, line) {\n      if (!hasPaperChoice(line.size)) return '';\n      return `<legend>Paper</legend>` + papersFor(line.size).map(k => `\n            <label class=\"pnz-paper__opt\">\n              <input type=\"radio\" name=\"pnz-paper-${file.id}-${idx}\" value=\"${k}\"${k === line.paper ? ' checked' : ''}>\n              <span class=\"pnz-paper__name\">${PAPER_LABELS[k]}</span>\n              <span class=\"pnz-paper__price\" data-paper=\"${k}\">${money(PAPER_PRICES[line.size][k])}</span>\n            </label>`).join('');\n    }\n\n    function buildPrintLineEl(file, idx) {\n      const line = file.options.prints[idx];\n      const sizeOptions = sizeOrder.map(s =>\n        `<option value=\"${s}\"${s === line.size ? ' selected' : ''}>${sizeLabel(s)}</option>`).join('');\n      const wrapper = document.createElement('div');\n      wrapper.className = 'pnz-line';\n      wrapper.dataset.fileId = file.id;\n      wrapper.dataset.lineIdx = String(idx);\n      wrapper.innerHTML = `\n        <div class=\"pnz-line__row\">\n          <select class=\"pnz-size\" aria-label=\"Print size\">${sizeOptions}</select>\n          <input type=\"number\" class=\"pnz-qty\" aria-label=\"Quantity\" value=\"${line.quantity}\" min=\"1\" max=\"100\" inputmode=\"numeric\">\n          <button type=\"button\" class=\"pnz-line__remove\" aria-label=\"Remove this size\"${file.options.prints.length === 1 ? ' hidden' : ''}>&times;</button>\n        </div>\n        <fieldset class=\"pnz-paper\"${hasPaperChoice(line.size) ? '' : ' hidden'}>${paperOptionsHTML(file, idx, line)}</fieldset>\n        <div class=\"pnz-poster\"${line.size === 'custom' ? '' : ' hidden'}>\n          <label>Height (cm)<input type=\"number\" class=\"pnz-poster-h\" min=\"${POSTER_H_MIN_CM}\" max=\"${POSTER_H_MAX_CM}\" step=\"1\" value=\"${line.poster.height_cm}\"></label>\n          <label>Width (cm)<input type=\"number\" class=\"pnz-poster-w\" min=\"${POSTER_W_MIN_CM}\" max=\"${POSTER_W_MAX_CM}\" step=\"1\" value=\"${line.poster.width_cm}\"></label>\n          <label>Finish<select class=\"pnz-poster-paper\">${paperOptionsHTMLFor(line)}</select></label>\n          <div class=\"pnz-poster__price\"><span>Each</span><b class=\"pnz-poster-unit\">\u2014</b></div>\n          <p class=\"pnz-poster__hint\">Width ${POSTER_W_MIN_CM}\u2013${POSTER_W_MAX_CM}cm, height ${POSTER_H_MIN_CM}\u2013${POSTER_H_MAX_CM}cm. Smaller prints run on our photo printer: short side up to ${(FUJI_TIERS[0].maxShortIn * CM_PER_IN).toFixed(0)}cm $${FUJI_TIERS[0].price}, up to ${(FUJI_MAX_SHORT_IN * CM_PER_IN).toFixed(0)}cm $${FUJI_TIERS[1].price}.</p>\n        </div>\n      `;\n      return wrapper;\n    }\n\n    function renderPrintLines(fileId) {\n      const file = uploadedFiles.find(f => f.id === fileId);\n      const card = cardFor(fileId);\n      if (!file || !card) return;\n      const container = card.querySelector('.pnz-lines');\n      container.innerHTML = '';\n      file.options.prints.forEach((_, idx) => container.appendChild(buildPrintLineEl(file, idx)));\n      updatePosterUnitPrices(file);\n    }\n\n    function updatePosterUnitPrices(file) {\n      const card = cardFor(file.id);\n      if (!card) return;\n      card.querySelectorAll('.pnz-line').forEach(lineEl => {\n        const line = file.options.prints[parseInt(lineEl.dataset.lineIdx, 10)];\n        const out = lineEl.querySelector('.pnz-poster-unit');\n        if (!line || !out) return;\n        if (hasPaperChoice(line.size)) {\n          lineEl.querySelectorAll('.pnz-paper__price').forEach(p => {\n            p.textContent = money(PAPER_PRICES[line.size][p.dataset.paper]);\n          });\n        }\n        const price = line.size === 'custom' ? calculatePosterUnitPrice(line.poster) : 0;\n        const bad = line.size === 'custom' && price <= 0;\n        out.textContent = line.size !== 'custom' ? '\u2014' : bad ? 'Size out of range' : money(price);\n        out.classList.toggle('is-bad', bad);\n      });\n    }\n\n    function lineCtx(target) {\n      const lineEl = target.closest('.pnz-line');\n      if (!lineEl) return null;\n      const file = uploadedFiles.find(f => f.id === lineEl.dataset.fileId);\n      if (!file) return null;\n      const idx = parseInt(lineEl.dataset.lineIdx, 10);\n      const line = file.options.prints[idx];\n      return line ? { file, line, lineEl, idx } : null;\n    }\n\n    function handleLineChange(e) {\n      const ctx = lineCtx(e.target);\n      if (!ctx) return;\n      if (e.target.matches('.pnz-size')) {\n        ctx.line.size = e.target.value;\n        ctx.lineEl.querySelector('.pnz-poster').hidden = ctx.line.size !== 'custom';\n        // Each size has its own paper list (A4 adds the Fuji papers), so redraw it.\n        normalizePaper(ctx.line);\n        const picker = ctx.lineEl.querySelector('.pnz-paper');\n        picker.hidden = !hasPaperChoice(ctx.line.size);\n        picker.innerHTML = paperOptionsHTML(ctx.file, ctx.idx, ctx.line);\n      } else if (e.target.matches('.pnz-paper input')) {\n        ctx.line.paper = e.target.value;\n      } else if (e.target.matches('.pnz-qty')) {\n        ctx.line.quantity = clampQty(e.target.value);\n        e.target.value = ctx.line.quantity;\n      } else if (e.target.matches('.pnz-poster-paper')) {\n        ctx.line.poster.paper = e.target.value;\n      } else {\n        return;\n      }\n      updatePosterUnitPrices(ctx.file);\n      updateOrderSummary();\n    }\n\n    function handleLineInput(e) {\n      const ctx = lineCtx(e.target);\n      if (!ctx) return;\n      if (e.target.matches('.pnz-poster-h') || e.target.matches('.pnz-poster-w')) {\n        const key = e.target.matches('.pnz-poster-h') ? 'height_cm' : 'width_cm';\n        ctx.line.poster[key] = parseFloat(e.target.value) || 0;\n        ctx.lineEl.querySelector('.pnz-poster-paper').innerHTML = paperOptionsHTMLFor(ctx.line);\n        updatePosterUnitPrices(ctx.file);\n      } else if (e.target.matches('.pnz-qty')) {\n        // Don't rewrite the field mid-keystroke; just keep the value sane.\n        ctx.line.quantity = clampQty(e.target.value);\n      } else {\n        return;\n      }\n      updateOrderSummary();\n    }\n\n    function handleLineClick(e) {\n      const removeBtn = e.target.closest('.pnz-line__remove');\n      if (removeBtn) {\n        const ctx = lineCtx(removeBtn);\n        if (!ctx || ctx.file.options.prints.length <= 1) return;\n        ctx.file.options.prints.splice(ctx.idx, 1);\n        renderPrintLines(ctx.file.id);\n        updateOrderSummary();\n        return;\n      }\n      const addBtn = e.target.closest('.pnz-card__add');\n      if (addBtn) {\n        const file = uploadedFiles.find(f => f.id === addBtn.closest('.pnz-card').dataset.fileId);\n        if (!file) return;\n        file.options.prints.push(defaultPrintLine(DEFAULT_SIZE, DEFAULT_PAPER));\n        renderPrintLines(file.id);\n        updateOrderSummary();\n      }\n    }\n\n    function removeFile(fileId) {\n      const file = uploadedFiles.find(f => f.id === fileId);\n      if (!file) return;\n      // Remove the file's full contribution from BOTH totals so the overall\n      // percentage stays consistent.\n      const bg = backgroundUploads[fileId];\n      if (bg) {\n        if (bg.status === 'uploading' && bg.xhr) bg.xhr.abort();\n        totalBytesUploaded -= bg.bytesUploaded || 0;\n        delete backgroundUploads[fileId];\n      }\n      totalBytesToUpload -= file.size;\n      if (file.thumbUrl) URL.revokeObjectURL(file.thumbUrl);\n\n      uploadedFiles = uploadedFiles.filter(f => f.id !== fileId);\n      const card = cardFor(fileId);\n      if (card) card.remove();\n\n      if (uploadedFiles.length === 0) {\n        currentOrderId = null;\n        currentR2Prefix = null;\n        fileIndexCounter = 0;\n        totalBytesToUpload = 0;\n        totalBytesUploaded = 0;\n        el.more.hidden = true;\n      }\n      updateOrderSummary();\n      updateOverallProgress();\n    }\n\n    // \u2500\u2500\u2500 Order summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    function groupKey(line) { return hasPaperChoice(line.size) ? `${line.size}|${line.paper}` : line.size; }\n\n    // Fixed-size lines are grouped across every photo so tiers apply to total\n    // quantity, not per image. Posters are priced individually.\n    function summarizeOrder() {\n      const fixedGroups = {};\n      const customLines = [];\n      uploadedFiles.forEach(file => {\n        file.options.prints.forEach(line => {\n          const qty = line.quantity || 1;\n          if (line.size === 'custom') {\n            const unit = calculatePosterUnitPrice(line.poster);\n            customLines.push({ file, line, unit, qty, lineTotal: unit * qty });\n          } else {\n            // Sizes with a paper choice group per paper (each has its own price).\n            const key = groupKey(line);\n            if (!fixedGroups[key]) fixedGroups[key] = { size: line.size, paper: hasPaperChoice(line.size) ? line.paper : null, qty: 0 };\n            fixedGroups[key].qty += qty;\n          }\n        });\n      });\n      // Breaks are chosen from the total per tierKey (Fuji A4 counts with 8x12).\n      const tierQty = {};\n      Object.values(fixedGroups).forEach(grp => {\n        const k = tierKey(grp.size, grp.paper);\n        tierQty[k] = (tierQty[k] || 0) + grp.qty;\n      });\n      let subtotal = 0;\n      Object.keys(fixedGroups).forEach(key => {\n        const grp = fixedGroups[key];\n        grp.unit = calculateTieredPrice(grp.size, tierQty[tierKey(grp.size, grp.paper)], grp.paper);\n        grp.lineTotal = grp.unit * grp.qty;\n        subtotal += grp.lineTotal;\n      });\n      customLines.forEach(c => { subtotal += c.lineTotal; });\n      return { fixedGroups, customLines, subtotal };\n    }\n\n    function updateOrderSummary() {\n      const { subtotal } = summarizeOrder();\n      const fulfilment = getFulfilment();\n      const shipping = shippingFor(fulfilment);\n      el.count.textContent = uploadedFiles.length;\n      el.subtotal.textContent = money(subtotal);\n      el.shippingRow.hidden = fulfilment !== 'ship';\n      el.shipping.textContent = money(shipping);\n      el.shipPrice.textContent = '+' + money(shippingCost());\n      el.total.textContent = money(subtotal + shipping);\n      el.secure.hidden = !isOnline(fulfilment);\n      el.dialogConfirm.textContent = isOnline(fulfilment) ? 'Pay now' : 'Place order';\n      // Gloss/matt only applies to the photo-printer sizes, not posters or A3\u2013A1.\n      el.finishWrap.hidden = !uploadedFiles.some(f => f.options.prints.some(p => p.size !== 'custom' && !hasPaperChoice(p.size)));\n      updateSubmitLabel();\n    }\n\n    fulfilmentInputs.forEach(input => input.addEventListener('change', updateOrderSummary));\n    el.finish.addEventListener('change', updateOrderSummary);\n\n    // \u2500\u2500\u2500 Confirm dialog \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    function renderConfirm() {\n      const { fixedGroups, customLines, subtotal } = summarizeOrder();\n      const fulfilment = getFulfilment();\n      const shipping = shippingFor(fulfilment);\n      const finishLabel = el.finish.value === 'matte' ? 'Matt' : 'Glossy';\n      const rows = [];\n\n      const paperOrder = Object.keys(PAPER_LABELS);\n      Object.values(fixedGroups)\n        .sort((a, b) => (sizeOrder.indexOf(a.size) - sizeOrder.indexOf(b.size)) || (paperOrder.indexOf(a.paper) - paperOrder.indexOf(b.paper)))\n        .forEach(grp => {\n          const paper = grp.paper ? PAPER_LABELS[grp.paper] : finishLabel;\n          rows.push(`<tr><td><b>${escapeHTML(sizeLabel(grp.size))}</b> \u00B7 ${escapeHTML(paper)}</td><td class=\"num\">${grp.qty}</td><td class=\"num\">${money(grp.unit)}</td><td class=\"num\">${money(grp.lineTotal)}</td></tr>`);\n        });\n      customLines.forEach(c => {\n        const paperLabel = (getPaperDef(c.line.poster.paper) || {}).label || c.line.poster.paper;\n        rows.push(`<tr><td><b>Custom poster</b> \u00B7 ${c.line.poster.height_cm}\u00D7${c.line.poster.width_cm}cm \u00B7 ${escapeHTML(paperLabel)}</td><td class=\"num\">${c.qty}</td><td class=\"num\">${money(c.unit)}</td><td class=\"num\">${money(c.lineTotal)}</td></tr>`);\n      });\n      if (fulfilment === 'ship') {\n        rows.push(`<tr><td colspan=\"3\">Shipping (NZ)</td><td class=\"num\">${money(shipping)}</td></tr>`);\n      }\n\n      const how = {\n        instore: '<b>Pay in store</b> when you collect from Camera House, 77 Queens Drive, Lower Hutt.',\n        collect: '<b>Pay online now</b> \u2014 you\\'ll go to our secure checkout, then collect from Camera House, 77 Queens Drive, Lower Hutt.',\n        ship: '<b>Pay online now</b> \u2014 you\\'ll enter your delivery address at checkout. We print once payment goes through.',\n      }[fulfilment];\n\n      el.dialogBody.innerHTML = `\n        <div class=\"pnz-table-scroll\"><table class=\"pnz-table\">\n          <thead><tr><th>Item</th><th class=\"num\">Qty</th><th class=\"num\">Each</th><th class=\"num\">Line</th></tr></thead>\n          <tbody>${rows.join('')}</tbody>\n          <tfoot><tr><td colspan=\"3\">Total</td><td class=\"num\">${money(subtotal + shipping)}</td></tr></tfoot>\n        </table></div>\n        <p class=\"pnz-dialog__how\">${how}</p>\n      `;\n    }\n\n    // \u2500\u2500\u2500 Submit \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    // Each print line becomes its own payload entry sharing the photo's r2_key.\n    // Prices use the SAME aggregated tier pricing the summary showed.\n    function buildCompletePayloadFiles() {\n      const globalFinish = el.finish.value;\n      const { fixedGroups } = summarizeOrder();\n      const out = [];\n      uploadedFiles.forEach(file => {\n        file.options.prints.forEach(line => {\n          const isCustom = line.size === 'custom';\n          const entry = {\n            name: file.name,\n            r2_key: file.r2Key || '',\n            print_size: line.size,\n            finish: isCustom ? line.poster.paper : hasPaperChoice(line.size) ? line.paper : globalFinish,\n            qty: line.quantity,\n          };\n          if (isCustom) {\n            entry.poster = {\n              paper: line.poster.paper,\n              height_cm: Number(line.poster.height_cm),\n              width_cm: Number(line.poster.width_cm),\n            };\n            entry.price = calculatePosterUnitPrice(line.poster) * line.quantity;\n          } else {\n            const grp = fixedGroups[groupKey(line)];\n            entry.price = (grp ? grp.unit : calculateTieredPrice(line.size, line.quantity, line.paper)) * line.quantity;\n          }\n          out.push(entry);\n        });\n      });\n      return out;\n    }\n\n    el.submit.addEventListener('click', function () {\n      showError('');\n      if (!API_KEY) { showError('Online ordering isn\\'t switched on yet \u2014 please bring your photos in store.'); return; }\n      if (uploadedFiles.length === 0) { showError('Add at least one photo first.'); return; }\n      if (!el.name.value.trim()) { showError('Please enter your name.'); el.name.focus(); return; }\n      if (!el.email.value.trim() || !el.email.checkValidity()) { showError('Please enter a valid email address.'); el.email.focus(); return; }\n\n      const invalidPosters = uploadedFiles.reduce((n, f) =>\n        n + f.options.prints.filter(l => l.size === 'custom' && !isPosterSpecValid(l.poster)).length, 0);\n      if (invalidPosters > 0) {\n        showError(`${invalidPosters} custom poster size(s) are out of range \u2014 width ${POSTER_W_MIN_CM}\u2013${POSTER_W_MAX_CM}cm, height ${POSTER_H_MIN_CM}\u2013${POSTER_H_MAX_CM}cm.`);\n        return;\n      }\n      const ongoing = ongoingCount();\n      if (ongoing > 0) {\n        showError(`${ongoing} photo(s) are still uploading \u2014 your order can go through as soon as they finish.`);\n        return;\n      }\n      const failed = uploadedFiles.filter(f => f.uploadStatus === 'error').length;\n      if (failed > 0) { showError(`${failed} photo(s) didn't upload. Remove them (\u00D7) and add them again.`); return; }\n      if (!currentOrderId) { showError('Upload preparation failed. Please remove your photos and try again.'); return; }\n\n      renderConfirm();\n      if (typeof el.dialog.showModal === 'function') el.dialog.showModal();\n      else placeOrderNow(); // very old browser \u2014 skip the confirmation step\n    });\n\n    el.dialogCancel.addEventListener('click', () => el.dialog.close());\n    el.dialogConfirm.addEventListener('click', () => { el.dialog.close(); placeOrderNow(); });\n\n    function setOverlay(on, text) {\n      el.overlay.hidden = !on;\n      el.overlayText.textContent = text || '';\n    }\n\n    function placeOrderNow() {\n      setOverlay(true, 'Sending your order\u2026');\n      const payload = {\n        order_id: currentOrderId,\n        r2_prefix: currentR2Prefix,\n        customer: { name: el.name.value.trim(), email: el.email.value.trim() },\n        notes: el.notes.value.trim(),\n        files: buildCompletePayloadFiles(),\n        fulfilment: getFulfilment(),\n      };\n\n      function attemptComplete(attempt) {\n        return apiCall('/print-orders/complete', payload).catch(err => {\n          if (!isTransientError(err) || attempt >= COMPLETE_RETRY_DELAYS_MS.length) throw err;\n          setOverlay(true, `Connection problem \u2014 retrying automatically (attempt ${attempt + 2} of ${COMPLETE_RETRY_DELAYS_MS.length + 1})\u2026`);\n          return new Promise(r => setTimeout(r, COMPLETE_RETRY_DELAYS_MS[attempt])).then(() => attemptComplete(attempt + 1));\n        });\n      }\n\n      attemptComplete(0).then(data => {\n        if (data && data.checkout_url) {\n          // Clear the list first so the beforeunload guard doesn't block the redirect.\n          setOverlay(true, 'Taking you to secure checkout\u2026');\n          uploadedFiles = [];\n          window.location.href = data.checkout_url;\n          return;\n        }\n        setOverlay(false);\n        el.orderId.textContent = data.order_id || currentOrderId;\n        uploadedFiles.forEach(f => f.thumbUrl && URL.revokeObjectURL(f.thumbUrl));\n        resetState();\n        el.form.hidden = true;\n        el.done.hidden = false;\n        el.done.scrollIntoView({ behavior: 'smooth', block: 'center' });\n      }).catch(error => {\n        console.error('Order failed:', error);\n        setOverlay(false);\n        if (isTransientError(error)) {\n          // Photos are safe in R2 and the form is kept \u2014 a later retry completes the same order.\n          showError('We couldn\\'t reach our server to finish your order. Your photos ARE uploaded \u2014 leave this page open and press the button again in a minute or two.');\n        } else {\n          showError('There was a problem placing your order: ' + error.message);\n        }\n      });\n    }\n\n    window.addEventListener('beforeunload', function (e) {\n      if (uploadedFiles.length > 0) { e.preventDefault(); e.returnValue = ''; }\n    });\n\n    el.newOrder.addEventListener('click', function () {\n      el.previews.innerHTML = '';\n      el.notes.value = '';\n      resetState();\n      el.finish.value = 'glossy';\n      const instore = root.querySelector('.pnz-fulfil input[value=\"instore\"]');\n      if (instore) instore.checked = true;\n      el.more.hidden = true;\n      updateOrderSummary();\n      updateOverallProgress();\n      el.done.hidden = true;\n      el.form.hidden = false;\n    });\n\n    updateOrderSummary();\n  }\n\n  function boot() { document.querySelectorAll('[data-pnz-app]').forEach(init); }\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);\n  else boot();\n})();\n"],
  "mappings": "CAkBC,UAAY,CACX,aAMA,MAAM,aAAe,CACnB,MAAO,CACL,CAAE,IAAK,EAAG,IAAK,GAAI,MAAO,GAAK,EAC/B,CAAE,IAAK,GAAI,IAAK,GAAI,MAAO,EAAK,EAChC,CAAE,IAAK,GAAI,IAAK,GAAI,MAAO,EAAK,EAChC,CAAE,IAAK,IAAK,IAAK,IAAU,MAAO,EAAK,CACzC,EACA,MAAO,CACL,CAAE,IAAK,EAAG,IAAK,GAAI,MAAO,GAAK,EAC/B,CAAE,IAAK,GAAI,IAAK,IAAU,MAAO,CAAK,CACxC,EACA,MAAO,CACL,CAAE,IAAK,EAAG,IAAK,EAAG,MAAO,CAAK,EAC9B,CAAE,IAAK,EAAG,IAAK,EAAG,MAAO,GAAK,EAC9B,CAAE,IAAK,GAAI,IAAK,IAAU,MAAO,GAAK,CACxC,EACA,OAAQ,CACN,CAAE,IAAK,EAAG,IAAK,EAAG,MAAO,EAAM,EAC/B,CAAE,IAAK,EAAG,IAAK,EAAG,MAAO,GAAK,EAC9B,CAAE,IAAK,GAAI,IAAK,IAAU,MAAO,CAAK,CACxC,EACA,OAAQ,CACN,CAAE,IAAK,EAAG,IAAK,EAAG,MAAO,EAAM,EAC/B,CAAE,IAAK,EAAG,IAAK,EAAG,MAAO,IAAM,EAC/B,CAAE,IAAK,GAAI,IAAK,IAAU,MAAO,GAAK,CACxC,CACF,EAEM,QAAU,CACd,SAAY,GACZ,MAAO,IACP,MAAO,IACP,MAAO,EACP,OAAQ,GACR,OAAQ,GACR,GAAM,GACN,GAAM,KACN,GAAM,GACN,GAAM,EACR,EAQM,aAAe,CACnB,OAAiB,iBACjB,MAAiB,gBACjB,OAAiB,gBACjB,cAAiB,2BACjB,gBAAiB,4BACnB,EACM,aAAe,CACnB,GAAM,CAAE,OAAQ,GAAO,MAAO,GAAO,OAAQ,GAAO,cAAe,GAAO,gBAAiB,EAAM,EACjG,GAAM,CAAE,OAAQ,KAAO,cAAe,KAAO,gBAAiB,IAAM,EACpE,GAAM,CAAE,OAAQ,GAAO,cAAe,GAAO,gBAAiB,EAAM,EACpE,GAAM,CAAE,OAAQ,GAAO,cAAe,GAAO,gBAAiB,EAAM,CACtE,EAEM,WAAa,IAAI,IAAI,CAAC,KAAM,KAAM,IAAI,CAAC,EAC7C,SAAS,eAAe,KAAM,CAAE,OAAO,OAAO,UAAU,eAAe,KAAK,aAAc,IAAI,CAAG,CACjG,SAAS,UAAU,KAAM,CAAE,OAAO,OAAO,KAAK,aAAa,IAAI,GAAK,CAAC,CAAC,CAAG,CAEzE,SAAS,eAAe,KAAM,CAC5B,MAAM,OAAS,UAAU,KAAK,IAAI,EAC9B,OAAO,QAAU,CAAC,OAAO,SAAS,KAAK,KAAK,IAAG,KAAK,MAAQ,OAAO,CAAC,EAC1E,CAEA,MAAM,UAAY,CAAC,MAAO,MAAO,MAAO,OAAQ,OAAQ,KAAM,KAAM,KAAM,KAAM,SAAU,UAAU,EAC9F,WAAa,CAAE,SAAU,iBAAkB,OAAQ,eAAgB,EACzE,SAAS,UAAU,EAAG,CAAE,OAAO,WAAW,CAAC,GAAK,CAAG,CAMnD,MAAM,cAAgB,CACpB,OAAiB,CAAE,MAAO,gBAAmB,KAAM,IAAM,KAAM,EAAG,EAClE,cAAiB,CAAE,MAAO,gBAAmB,KAAM,IAAM,KAAM,EAAG,EAClE,gBAAiB,CAAE,MAAO,kBAAmB,KAAM,IAAM,KAAM,EAAG,CACpE,EAEM,cAAgB,CACpB,OAAQ,CAAE,MAAO,QAAS,EAC1B,OAAQ,CAAE,MAAO,QAAS,CAC5B,EACM,cAAgB,GAChB,UAAY,KACZ,gBAAkB,EAClB,gBAAkB,GAClB,gBAAkB,EAClB,gBAAkB,IAElB,WAAa,CACjB,CAAE,WAAY,EAAM,MAAO,EAAG,EAC9B,CAAE,WAAY,KAAM,MAAO,EAAG,CAChC,EACM,kBAAoB,WAAW,WAAW,OAAS,CAAC,EAAE,WAE5D,SAAS,OAAO,GAAI,CAAE,OAAO,GAAK,SAAW,CAC7C,SAAS,YAAY,MAAO,CAAE,OAAO,cAAc,KAAK,GAAK,cAAc,KAAK,GAAK,IAAM,CAC3F,SAAS,gBAAgB,KAAM,CAAE,OAAO,KAAO,SAAW,QAAU,CAEpE,SAAS,WAAW,EAAG,CACrB,MAAM,EAAI,OAAO,OAAO,EAAE,SAAS,CAAC,EAC9B,EAAI,OAAO,OAAO,EAAE,QAAQ,CAAC,EACnC,MAAI,CAAC,SAAS,CAAC,GAAK,CAAC,SAAS,CAAC,EAAU,GAClC,KAAK,IAAI,EAAG,CAAC,EAAI,iBAC1B,CAEA,SAAS,kBAAkB,EAAG,CAC5B,GAAI,CAAC,EAAG,MAAO,GACf,MAAM,EAAI,OAAO,EAAE,SAAS,EAAG,EAAI,OAAO,EAAE,QAAQ,EAIpD,GAHI,CAAC,SAAS,CAAC,GAAK,CAAC,SAAS,CAAC,GAC3B,EAAI,iBAAmB,EAAI,iBAC3B,EAAI,iBAAmB,EAAI,iBAC3B,CAAC,YAAY,EAAE,KAAK,EAAG,MAAO,GAElC,MAAM,KAAO,WAAW,CAAC,EAEzB,MADI,QAAQ,CAAC,cAAc,EAAE,KAAK,GAC9B,CAAC,MAAQ,CAAC,cAAc,EAAE,KAAK,EAErC,CAEA,SAAS,yBAAyB,EAAG,CACnC,GAAI,CAAC,kBAAkB,CAAC,EAAG,MAAO,GAClC,MAAM,EAAI,OAAO,OAAO,EAAE,SAAS,CAAC,EAC9B,EAAI,OAAO,OAAO,EAAE,QAAQ,CAAC,EAC7B,QAAU,KAAK,IAAI,EAAG,CAAC,EAC7B,GAAI,QAAU,kBAAmB,CAC/B,MAAM,KAAO,WAAW,KAAK,GAAK,SAAW,EAAE,UAAU,EACzD,GAAI,KAAM,OAAO,KAAK,KACxB,CACA,MAAM,MAAQ,cAAc,EAAE,KAAK,EAC7B,KAAQ,MAAM,KAAO,EAAO,EAAI,cAAiB,IAAQ,EAC/D,OAAO,KAAK,OAAO,KAAO,EAAI,MAAM,MAAQ,GAAG,EAAI,GACrD,CAKA,SAAS,QAAQ,KAAM,MAAO,CAC5B,OAAO,OAAS,OAAS,QAAU,UAAY,QAAU,SAAW,OAAS,IAC/E,CAGA,SAAS,qBAAqB,KAAM,SAAU,MAAO,CACnD,GAAI,OAAS,SAAU,MAAO,GAC9B,MAAM,KAAO,eAAe,IAAI,EAC3B,aAAa,IAAI,EAAE,KAAK,GAAK,aAAa,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,EAClE,QAAQ,IAAI,GAAK,EAChB,MAAQ,aAAa,QAAQ,KAAM,KAAK,CAAC,EACzC,KAAO,OAAS,MAAM,KAAK,GAAK,UAAY,EAAE,KAAO,UAAY,EAAE,GAAG,EAC5E,OAAO,KAAO,KAAK,MAAQ,IAC7B,CAGA,MAAM,gBAAkB,IAClB,qBAAuB,KAI7B,SAAS,SAAS,EAAG,CACnB,MAAM,EAAI,SAAS,EAAG,EAAE,EACxB,OAAK,SAAS,CAAC,EACR,KAAK,IAAI,IAAK,KAAK,IAAI,EAAG,CAAC,CAAC,EADV,CAE3B,CAGA,MAAM,mBAAqB,IAAI,IAAI,CACjC,aAAc,cACd,YACA,YAAa,cAAe,iBAC5B,aAAc,gBACd,aAAc,YAChB,CAAC,EACK,aAAe,IAAI,IAAI,CAAC,MAAO,OAAQ,MAAO,MAAO,MAAO,OAAQ,OAAQ,MAAM,CAAC,EACnF,iBAAmB,IAAM,KAAO,KAEtC,SAAS,MAAM,KAAM,CACnB,MAAM,EAAI,OAAO,MAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,EACjE,OAAO,EAAI,EAAE,CAAC,EAAI,EACpB,CAGA,SAAS,eAAe,KAAM,CAC5B,OAAI,KAAK,MAAQ,mBAAmB,IAAI,KAAK,KAAK,YAAY,CAAC,EAAU,GAClE,aAAa,IAAI,MAAM,KAAK,IAAI,CAAC,CAC1C,CAGA,MAAM,YAAc,IAAI,IAAI,CAAC,MAAO,OAAQ,MAAO,MAAO,MAAO,MAAM,CAAC,EAExE,SAAS,WAAW,EAAG,CACrB,OAAO,OAAO,CAAC,EAAE,QAAQ,WAAY,IAAM,CAAE,IAAK,QAAS,IAAK,OAAQ,IAAK,OAAQ,IAAK,SAAU,IAAK,OAAQ,GAAE,CAAC,CAAE,CACxH,CACA,SAAS,MAAM,EAAG,CAAE,MAAO,IAAM,EAAE,QAAQ,CAAC,CAAG,CAI/C,SAAS,iBAAiB,IAAK,CAC7B,OAAI,KAAO,OAAO,IAAI,QAAW,SAAiB,IAAI,QAAU,IACzD,EACT,CAKA,MAAM,yBAA2B,CAAC,IAAM,KAAO,IAAO,IAAK,EAE3D,SAAS,iBAAiB,KAAM,MAAO,CACrC,MAAM,KAAO,CACX,KAAM,MAAQ,MACd,SAAU,EACV,MAAO,OAAS,KAChB,OAAQ,CAAE,MAAO,SAAU,UAAW,GAAI,SAAU,EAAG,CACzD,EACA,sBAAe,IAAI,EACZ,IACT,CAEA,SAAS,KAAK,KAAM,CAElB,GAAI,KAAK,QAAQ,SAAU,OAC3B,KAAK,QAAQ,SAAW,IACxB,MAAM,SAAW,KAAK,QAAQ,QACxB,QAAU,KAAK,QAAQ,OAEvB,aAAe,UAAU,KAAK,GAAK,EAAE,YAAY,IAAM,OAAO,KAAK,QAAQ,aAAe,EAAE,EAAE,YAAY,CAAC,EAC3G,aAAe,cAAgB,eAAiB,SAAW,aAAe,MAE1E,cAAgB,KAAK,QAAQ,aAC7B,EAAK,MAAS,KAAK,cAAc,cAAc,IAAI,IAAI,EAEvD,GAAK,CACT,KAAM,EAAE,MAAM,EAAG,KAAM,EAAE,MAAM,EAAG,UAAW,EAAE,YAAY,EAC3D,WAAY,EAAE,aAAa,EAAG,OAAQ,EAAE,QAAQ,EAChD,SAAU,EAAE,UAAU,EAAG,YAAa,EAAE,cAAc,EAAG,aAAc,EAAE,eAAe,EACxF,SAAU,EAAE,UAAU,EAAG,KAAM,EAAE,MAAM,EAAG,UAAW,EAAE,YAAY,EACnE,MAAO,EAAE,OAAO,EAAG,SAAU,EAAE,UAAU,EAAG,YAAa,EAAE,cAAc,EACzE,SAAU,EAAE,UAAU,EAAG,MAAO,EAAE,OAAO,EAAG,UAAW,EAAE,YAAY,EACrE,KAAM,EAAE,MAAM,EAAG,MAAO,EAAE,OAAO,EAAG,MAAO,EAAE,OAAO,EACpD,MAAO,EAAE,OAAO,EAAG,OAAQ,EAAE,QAAQ,EAAG,OAAQ,EAAE,QAAQ,EAC1D,KAAM,EAAE,MAAM,EAAG,QAAS,EAAE,UAAU,EAAG,SAAU,EAAE,WAAW,EAChE,OAAQ,EAAE,QAAQ,EAAG,WAAY,EAAE,aAAa,EAChD,aAAc,EAAE,eAAe,EAAG,cAAe,EAAE,gBAAgB,EACnE,QAAS,EAAE,SAAS,EAAG,YAAa,EAAE,cAAc,CACtD,EACM,iBAAmB,KAAK,iBAAiB,iCAAiC,EAE5E,KAAK,QAAQ,eAAc,GAAG,KAAK,MAAQ,KAAK,QAAQ,cACxD,KAAK,QAAQ,gBAAe,GAAG,MAAM,MAAQ,KAAK,QAAQ,eAG9D,IAAI,cAAgB,CAAC,EACjB,kBAAoB,CAAC,EACrB,mBAAqB,EACrB,mBAAqB,EACrB,eAAiB,KACjB,gBAAkB,KAClB,iBAAmB,EACnB,aAAe,QAAQ,QAAQ,EAEnC,SAAS,YAAa,CACpB,cAAgB,CAAC,EACjB,kBAAoB,CAAC,EACrB,eAAiB,KACjB,gBAAkB,KAClB,iBAAmB,EACnB,mBAAqB,EACrB,mBAAqB,CACvB,CAEA,SAAS,eAAgB,CACvB,MAAM,QAAU,KAAK,cAAc,2BAA2B,EAC9D,OAAO,QAAU,QAAQ,MAAQ,SACnC,CACA,SAAS,SAAS,EAAG,CAAE,OAAO,IAAM,QAAU,IAAM,SAAW,CAI/D,SAAS,cAAe,CAGtB,OAFkB,cAAc,KAAK,GAAK,EAAE,QAAQ,OAAO,KAAK,MAAQ,WAAW,IAAI,KAAK,IAAI,GAC7F,KAAK,OAAS,UAAY,kBAAkB,KAAK,MAAM,GAAK,CAAC,WAAW,KAAK,MAAM,CAAE,CAAC,EACtE,qBAAuB,eAC5C,CACA,SAAS,YAAY,WAAY,CAAE,OAAO,aAAe,OAAS,aAAa,EAAI,CAAG,CAEtF,SAAS,UAAU,IAAK,CACtB,GAAG,MAAM,YAAc,IACvB,GAAG,MAAM,OAAS,CAAC,IACf,KAAK,GAAG,MAAM,eAAe,CAAE,SAAU,SAAU,MAAO,QAAS,CAAC,CAC1E,CAGA,SAAS,QAAQ,KAAM,KAAM,CAC3B,OAAO,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAI,CACjC,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,YAAa,OAAQ,EACpE,KAAM,KAAK,UAAU,IAAI,CAC3B,CAAC,EAAE,KAAK,KACD,IAAI,GAWF,IAAI,KAAK,EARP,IAAI,KAAK,EAAE,KAAK,GAAK,CAC1B,IAAI,IAAM,GACV,GAAI,CAAE,IAAM,KAAK,MAAM,CAAC,EAAE,KAAO,MAAY,CAAiB,CAC9D,MAAM,IAAM,IAAI,MAAM,KAAO,QAAQ,IAAI,MAAM,EAAE,EACjD,UAAI,OAAS,IAAI,OACX,GACR,CAAC,CAGJ,CACH,CAIA,SAAS,cAAc,EAAG,CACxB,MAAO,CAAE,KAAM,EAAE,KAAM,KAAM,EAAE,KAAM,KAAM,EAAE,MAAQ,aAAc,WAAY,MAAO,OAAQ,SAAU,IAAK,CAAE,CACjH,CAGA,SAAS,cAAe,CACtB,OAAO,OAAO,OAAO,iBAAiB,EAAE,OAAO,GAAK,EAAE,SAAW,WAAW,EAAE,MAChF,CAEA,SAAS,uBAAwB,CAE/B,GADA,GAAG,SAAS,OAAS,cAAc,SAAW,EAC1C,oBAAsB,EAAG,CAC3B,GAAG,YAAY,MAAM,MAAQ,KAC7B,GAAG,aAAa,YAAc,GAC9B,kBAAkB,EAClB,MACF,CACA,MAAM,QAAU,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAO,mBAAqB,mBAAsB,GAAG,EAAG,GAAG,CAAC,EACtG,GAAG,YAAY,MAAM,MAAQ,QAAU,IACvC,MAAM,QAAU,aAAa,EACvB,UAAY,cAAc,OAAO,GAAK,EAAE,eAAiB,WAAW,EAAE,OAC5E,GAAG,SAAS,UAAU,OAAO,UAAW,UAAY,KAAO,UAAY,CAAC,EACpE,UAAY,KAAO,UAAY,EACjC,GAAG,aAAa,YAAc,sBACrB,QAAU,EACnB,GAAG,aAAa,YAAc,aAAa,OAAO,OAAO,cAAc,MAAM,YAAY,OAAO,KAEhG,GAAG,aAAa,YAAc,YAAY,SAAS,OAAO,cAAc,MAAM,UAEhF,kBAAkB,CACpB,CAEA,SAAS,mBAAoB,CAC3B,MAAM,QAAU,aAAa,EACzB,QAAU,EACZ,GAAG,OAAO,YAAc,oBAAe,OAAO,UAE9C,GAAG,OAAO,YAAc,SAAS,cAAc,CAAC,EAAI,sBAAwB,aAEhF,CAGA,GAAG,KAAK,iBAAiB,WAAa,GAAM,CAAE,EAAE,eAAe,EAAG,GAAG,KAAK,UAAU,IAAI,SAAS,CAAG,CAAC,EACrG,GAAG,KAAK,iBAAiB,YAAa,IAAM,GAAG,KAAK,UAAU,OAAO,SAAS,CAAC,EAC/E,GAAG,KAAK,iBAAiB,OAAS,GAAM,CACtC,EAAE,eAAe,EACjB,GAAG,KAAK,UAAU,OAAO,SAAS,EAClC,YAAY,EAAE,aAAa,KAAK,CAClC,CAAC,EACD,CAAC,GAAG,UAAW,GAAG,SAAS,EAAE,QAAQ,OAAS,MAAM,iBAAiB,SAAU,UAAY,CACrF,KAAK,OAAS,KAAK,MAAM,OAAS,IAAK,YAAY,KAAK,KAAK,EAAG,KAAK,MAAQ,GACnF,CAAC,CAAC,EAEF,SAAS,YAAY,MAAO,CAC1B,GAAI,CAAC,OAAS,MAAM,SAAW,EAAG,OAClC,UAAU,EAAE,EAEZ,MAAM,UAAY,MAAM,KAAK,KAAK,EAC5B,SAAW,UAAU,OAAO,GAAK,CAAC,eAAe,CAAC,CAAC,EACnD,OAAS,UAAU,OAAO,GAAK,eAAe,CAAC,GAAK,EAAE,KAAO,gBAAgB,EAC7E,WAAa,UAAU,OAAO,GAAK,eAAe,CAAC,GAAK,EAAE,MAAQ,gBAAgB,EAElF,SAAW,CAAC,EAClB,GAAI,SAAS,OAAQ,CACnB,MAAM,MAAQ,SAAS,IAAI,GAAK,EAAE,IAAI,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,IAAI,EACvD,KAAO,SAAS,OAAS,EAAI,QAAQ,SAAS,OAAS,CAAC,QAAU,GACxE,SAAS,KAAK,GAAG,SAAS,MAAM,uCAAuC,KAAK,GAAG,IAAI,0DAA0D,CAC/I,CAGA,GAFI,OAAO,QAAQ,SAAS,KAAK,GAAG,OAAO,MAAM,4BAA4B,OAAO,IAAI,GAAK,EAAE,IAAI,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,EAC1H,SAAS,QAAQ,UAAU,SAAS,KAAK,GAAG,CAAC,EAC7C,WAAW,SAAW,EAAG,OAE7B,MAAM,SAAW,CAAC,EAYlB,GAXA,WAAW,QAAQ,MAAQ,CACrB,cAAc,KAAK,GAAK,EAAE,OAAS,KAAK,MAAQ,EAAE,OAAS,KAAK,IAAI,IACxE,oBAAsB,KAAK,KAC3B,KAAK,GAAK,KAAK,IAAI,EAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,EAC7D,KAAK,QAAU,CAAE,OAAQ,CAAC,iBAAiB,aAAc,aAAa,CAAC,CAAE,EACzE,KAAK,aAAe,UACpB,KAAK,UAAY,mBACjB,cAAc,KAAK,IAAI,EACvB,SAAS,KAAK,IAAI,EAClB,qBAAqB,IAAI,EAC3B,CAAC,EACG,SAAS,SAAW,EAAG,OAE3B,MAAM,aAAe,SAAS,IAAI,aAAa,EAEzC,gBAAkB,SAAS,CAAC,EAAE,UAC9B,WAAa,cAAc,SAAW,SAAS,OAKrD,aAAe,aAAa,KAAK,IAAM,CACrC,MAAM,YAAc,CAAE,MAAO,YAAa,EAC1C,OAAI,iBACF,YAAY,SAAW,eACvB,YAAY,iBAAmB,iBAE1B,QAAQ,+BAAgC,WAAW,EAAE,KAAK,MAAQ,CAClE,iBACH,eAAiB,KAAK,SACtB,gBAAkB,KAAK,WAEzB,KAAK,YAAY,QAAQ,CAAC,QAAS,IAAM,CACvC,MAAM,KAAO,SAAS,CAAC,EACnB,CAAC,MAAQ,CAAC,cAAc,SAAS,IAAI,IACzC,KAAK,MAAQ,QAAQ,OACrB,KAAK,aAAe,YACpB,eAAe,KAAM,QAAQ,OAAO,EACtC,CAAC,CACH,CAAC,CACH,CAAC,EAAE,MAAM,KAAO,CACd,QAAQ,MAAM,4BAA6B,GAAG,EAC9C,SAAS,QAAQ,GAAK,cAAc,EAAE,GAAI,2BAA6B,IAAI,OAAO,CAAC,CACrF,CAAC,EAED,mBAAmB,EACnB,GAAG,KAAK,OAAS,GACjB,sBAAsB,EAElB,YACF,WAAW,IAAM,GAAG,WAAW,eAAe,CAAE,SAAU,SAAU,MAAO,OAAQ,CAAC,EAAG,GAAG,CAE9F,CAGA,SAAS,QAAQ,OAAQ,CAAE,OAAO,GAAG,SAAS,cAAc,kBAAkB,MAAM,IAAI,CAAG,CAE3F,SAAS,cAAc,OAAQ,QAAS,KAAM,MAAO,CACnD,MAAM,KAAO,QAAQ,MAAM,EAC3B,GAAI,CAAC,KAAM,OACX,MAAM,KAAO,KAAK,cAAc,mBAAmB,EACnD,KAAK,OAAS,GACd,KAAK,UAAY,oBAAsB,MAAQ,OAAS,MAAQ,IAChE,KAAK,cAAc,MAAM,EAAE,MAAM,MAAQ,QAAU,IACnD,KAAK,cAAc,GAAG,EAAE,YAAc,KAClC,QAAU,QAAQ,WAAW,IAAM,CAAE,KAAK,OAAS,EAAM,EAAG,GAAI,CACtE,CAEA,SAAS,eAAe,KAAM,OAAQ,CACpC,MAAM,IAAM,IAAI,eAChB,kBAAkB,KAAK,EAAE,EAAI,CAAE,IAAK,OAAQ,YAAa,WAAY,KAAK,KAAM,cAAe,CAAE,EACjG,cAAc,KAAK,GAAI,EAAG,cAAc,EAExC,IAAI,KAAK,MAAO,OAAQ,EAAI,EAC5B,IAAI,iBAAiB,eAAgB,KAAK,MAAQ,YAAY,EAE9D,IAAI,QAAU,IAAU,IAExB,IAAI,OAAO,WAAa,SAAU,EAAG,CACnC,GAAI,CAAC,EAAE,iBAAkB,OACzB,MAAM,GAAK,kBAAkB,KAAK,EAAE,EACpC,GAAI,CAAC,GAAI,OACT,oBAAsB,EAAE,OAAS,GAAG,cACpC,GAAG,cAAgB,EAAE,OACrB,MAAM,IAAM,KAAK,MAAO,EAAE,OAAS,EAAE,MAAS,GAAG,EACjD,cAAc,KAAK,GAAI,IAAK,aAAa,GAAG,GAAG,EAC/C,sBAAsB,CACxB,EAEA,IAAI,OAAS,UAAY,CACvB,MAAM,GAAK,kBAAkB,KAAK,EAAE,EAC/B,KACD,IAAI,QAAU,KAAO,IAAI,OAAS,KACpC,KAAK,aAAe,YACpB,GAAG,OAAS,YACZ,oBAAsB,GAAG,WAAa,GAAG,cACzC,GAAG,cAAgB,GAAG,WACtB,cAAc,KAAK,GAAI,IAAK,WAAY,MAAM,EAC9C,sBAAsB,GAEtB,cAAc,KAAK,GAAI,kBAAkB,IAAI,MAAM,GAAG,EAE1D,EACA,IAAI,QAAU,UAAY,CAAE,cAAc,KAAK,GAAI,mDAA8C,CAAG,EACpG,IAAI,UAAY,UAAY,CAAE,cAAc,KAAK,GAAI,+CAA0C,CAAG,EAClG,IAAI,KAAK,IAAI,CACf,CAIA,SAAS,cAAc,OAAQ,QAAS,CAClC,kBAAkB,MAAM,IAAG,kBAAkB,MAAM,EAAE,OAAS,SAClE,MAAM,KAAO,cAAc,KAAK,GAAK,EAAE,KAAO,MAAM,EAChD,OAAM,KAAK,aAAe,SAC9B,cAAc,OAAQ,EAAG,QAAS,OAAO,EACzC,sBAAsB,CACxB,CAGA,SAAS,qBAAqB,KAAM,CAClC,MAAM,KAAO,SAAS,cAAc,KAAK,EAczC,GAbA,KAAK,UAAY,WACjB,KAAK,QAAQ,OAAS,KAAK,GAC3B,KAAK,UAAY;AAAA,4EACqD,WAAW,KAAK,IAAI,CAAC;AAAA,6CACpD,WAAW,MAAM,KAAK,IAAI,EAAE,YAAY,GAAK,OAAO,CAAC;AAAA,oCAC9D,WAAW,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,QAKnD,GAAG,SAAS,YAAY,IAAI,EAGxB,YAAY,IAAI,MAAM,KAAK,IAAI,CAAC,GAAM,KAAK,MAAQ,mCAAmC,KAAK,KAAK,IAAI,EAAI,CAC1G,MAAM,IAAM,IAAI,MAChB,IAAI,IAAM,GACV,IAAI,SAAW,QACf,IAAI,OAAS,IAAM,CAAE,MAAM,EAAI,KAAK,cAAc,kBAAkB,EAAG,EAAE,UAAY,GAAI,EAAE,YAAY,GAAG,CAAG,EAC7G,IAAI,IAAM,KAAK,SAAW,IAAI,gBAAgB,IAAI,CACpD,CAEA,KAAK,cAAc,mBAAmB,EAAE,iBAAiB,QAAS,IAAM,WAAW,KAAK,EAAE,CAAC,EAE3F,KAAK,iBAAiB,SAAU,gBAAgB,EAChD,KAAK,iBAAiB,QAAS,eAAe,EAC9C,KAAK,iBAAiB,QAAS,eAAe,EAC9C,iBAAiB,KAAK,EAAE,CAC1B,CAEA,SAAS,oBAAoB,KAAM,CACjC,MAAM,KAAO,WAAW,KAAK,MAAM,EAC7B,IAAM,KAAO,cAAgB,cACnC,OAAK,IAAI,KAAK,OAAO,KAAK,IAAG,KAAK,OAAO,MAAQ,gBAAgB,IAAI,GAC9D,OAAO,KAAK,GAAG,EAAE,IAAI,GAC1B,kBAAkB,CAAC,IAAI,IAAM,KAAK,OAAO,MAAQ,YAAc,EAAE,IAAI,IAAI,CAAC,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,CACzG,CAEA,SAAS,iBAAiB,KAAM,IAAK,KAAM,CACzC,OAAK,eAAe,KAAK,IAAI,EACtB,yBAA2B,UAAU,KAAK,IAAI,EAAE,IAAI,GAAK;AAAA;AAAA,oDAElB,KAAK,EAAE,IAAI,GAAG,YAAY,CAAC,IAAI,IAAM,KAAK,MAAQ,WAAa,EAAE;AAAA,8CACvE,aAAa,CAAC,CAAC;AAAA,2DACF,CAAC,KAAK,MAAM,aAAa,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAAA,qBAC7E,EAAE,KAAK,EAAE,EANe,EAOzC,CAEA,SAAS,iBAAiB,KAAM,IAAK,CACnC,MAAM,KAAO,KAAK,QAAQ,OAAO,GAAG,EAC9B,YAAc,UAAU,IAAI,GAChC,kBAAkB,CAAC,IAAI,IAAM,KAAK,KAAO,YAAc,EAAE,IAAI,UAAU,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,EACzF,QAAU,SAAS,cAAc,KAAK,EAC5C,eAAQ,UAAY,WACpB,QAAQ,QAAQ,OAAS,KAAK,GAC9B,QAAQ,QAAQ,QAAU,OAAO,GAAG,EACpC,QAAQ,UAAY;AAAA;AAAA,6DAEmC,WAAW;AAAA,8EACM,KAAK,QAAQ;AAAA,wFACH,KAAK,QAAQ,OAAO,SAAW,EAAI,UAAY,EAAE;AAAA;AAAA,qCAEpG,eAAe,KAAK,IAAI,EAAI,GAAK,SAAS,IAAI,iBAAiB,KAAM,IAAK,IAAI,CAAC;AAAA,iCACnF,KAAK,OAAS,SAAW,GAAK,SAAS;AAAA,6EACK,eAAe,UAAU,eAAe,qBAAqB,KAAK,OAAO,SAAS;AAAA,4EACnF,eAAe,UAAU,eAAe,qBAAqB,KAAK,OAAO,QAAQ;AAAA,0DACnG,oBAAoB,IAAI,CAAC;AAAA;AAAA,8CAErC,eAAe,SAAI,eAAe,cAAc,eAAe,SAAI,eAAe,kEAAkE,WAAW,CAAC,EAAE,WAAa,WAAW,QAAQ,CAAC,CAAC,OAAO,WAAW,CAAC,EAAE,KAAK,YAAY,kBAAoB,WAAW,QAAQ,CAAC,CAAC,OAAO,WAAW,CAAC,EAAE,KAAK;AAAA;AAAA,QAG9U,OACT,CAEA,SAAS,iBAAiB,OAAQ,CAChC,MAAM,KAAO,cAAc,KAAK,GAAK,EAAE,KAAO,MAAM,EAC9C,KAAO,QAAQ,MAAM,EAC3B,GAAI,CAAC,MAAQ,CAAC,KAAM,OACpB,MAAM,UAAY,KAAK,cAAc,YAAY,EACjD,UAAU,UAAY,GACtB,KAAK,QAAQ,OAAO,QAAQ,CAAC,EAAG,MAAQ,UAAU,YAAY,iBAAiB,KAAM,GAAG,CAAC,CAAC,EAC1F,uBAAuB,IAAI,CAC7B,CAEA,SAAS,uBAAuB,KAAM,CACpC,MAAM,KAAO,QAAQ,KAAK,EAAE,EACvB,MACL,KAAK,iBAAiB,WAAW,EAAE,QAAQ,QAAU,CACnD,MAAM,KAAO,KAAK,QAAQ,OAAO,SAAS,OAAO,QAAQ,QAAS,EAAE,CAAC,EAC/D,IAAM,OAAO,cAAc,kBAAkB,EACnD,GAAI,CAAC,MAAQ,CAAC,IAAK,OACf,eAAe,KAAK,IAAI,GAC1B,OAAO,iBAAiB,mBAAmB,EAAE,QAAQ,GAAK,CACxD,EAAE,YAAc,MAAM,aAAa,KAAK,IAAI,EAAE,EAAE,QAAQ,KAAK,CAAC,CAChE,CAAC,EAEH,MAAM,MAAQ,KAAK,OAAS,SAAW,yBAAyB,KAAK,MAAM,EAAI,EACzE,IAAM,KAAK,OAAS,UAAY,OAAS,EAC/C,IAAI,YAAc,KAAK,OAAS,SAAW,SAAM,IAAM,oBAAsB,MAAM,KAAK,EACxF,IAAI,UAAU,OAAO,SAAU,GAAG,CACpC,CAAC,CACH,CAEA,SAAS,QAAQ,OAAQ,CACvB,MAAM,OAAS,OAAO,QAAQ,WAAW,EACzC,GAAI,CAAC,OAAQ,OAAO,KACpB,MAAM,KAAO,cAAc,KAAK,GAAK,EAAE,KAAO,OAAO,QAAQ,MAAM,EACnE,GAAI,CAAC,KAAM,OAAO,KAClB,MAAM,IAAM,SAAS,OAAO,QAAQ,QAAS,EAAE,EACzC,KAAO,KAAK,QAAQ,OAAO,GAAG,EACpC,OAAO,KAAO,CAAE,KAAM,KAAM,OAAQ,GAAI,EAAI,IAC9C,CAEA,SAAS,iBAAiB,EAAG,CAC3B,MAAM,IAAM,QAAQ,EAAE,MAAM,EAC5B,GAAK,IACL,IAAI,EAAE,OAAO,QAAQ,WAAW,EAAG,CACjC,IAAI,KAAK,KAAO,EAAE,OAAO,MACzB,IAAI,OAAO,cAAc,aAAa,EAAE,OAAS,IAAI,KAAK,OAAS,SAEnE,eAAe,IAAI,IAAI,EACvB,MAAM,OAAS,IAAI,OAAO,cAAc,YAAY,EACpD,OAAO,OAAS,CAAC,eAAe,IAAI,KAAK,IAAI,EAC7C,OAAO,UAAY,iBAAiB,IAAI,KAAM,IAAI,IAAK,IAAI,IAAI,CACjE,SAAW,EAAE,OAAO,QAAQ,kBAAkB,EAC5C,IAAI,KAAK,MAAQ,EAAE,OAAO,cACjB,EAAE,OAAO,QAAQ,UAAU,EACpC,IAAI,KAAK,SAAW,SAAS,EAAE,OAAO,KAAK,EAC3C,EAAE,OAAO,MAAQ,IAAI,KAAK,iBACjB,EAAE,OAAO,QAAQ,mBAAmB,EAC7C,IAAI,KAAK,OAAO,MAAQ,EAAE,OAAO,UAEjC,QAEF,uBAAuB,IAAI,IAAI,EAC/B,mBAAmB,EACrB,CAEA,SAAS,gBAAgB,EAAG,CAC1B,MAAM,IAAM,QAAQ,EAAE,MAAM,EAC5B,GAAK,IACL,IAAI,EAAE,OAAO,QAAQ,eAAe,GAAK,EAAE,OAAO,QAAQ,eAAe,EAAG,CAC1E,MAAM,IAAM,EAAE,OAAO,QAAQ,eAAe,EAAI,YAAc,WAC9D,IAAI,KAAK,OAAO,GAAG,EAAI,WAAW,EAAE,OAAO,KAAK,GAAK,EACrD,IAAI,OAAO,cAAc,mBAAmB,EAAE,UAAY,oBAAoB,IAAI,IAAI,EACtF,uBAAuB,IAAI,IAAI,CACjC,SAAW,EAAE,OAAO,QAAQ,UAAU,EAEpC,IAAI,KAAK,SAAW,SAAS,EAAE,OAAO,KAAK,MAE3C,QAEF,mBAAmB,EACrB,CAEA,SAAS,gBAAgB,EAAG,CAC1B,MAAM,UAAY,EAAE,OAAO,QAAQ,mBAAmB,EACtD,GAAI,UAAW,CACb,MAAM,IAAM,QAAQ,SAAS,EAC7B,GAAI,CAAC,KAAO,IAAI,KAAK,QAAQ,OAAO,QAAU,EAAG,OACjD,IAAI,KAAK,QAAQ,OAAO,OAAO,IAAI,IAAK,CAAC,EACzC,iBAAiB,IAAI,KAAK,EAAE,EAC5B,mBAAmB,EACnB,MACF,CACA,MAAM,OAAS,EAAE,OAAO,QAAQ,gBAAgB,EAChD,GAAI,OAAQ,CACV,MAAM,KAAO,cAAc,KAAK,GAAK,EAAE,KAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ,MAAM,EACxF,GAAI,CAAC,KAAM,OACX,KAAK,QAAQ,OAAO,KAAK,iBAAiB,aAAc,aAAa,CAAC,EACtE,iBAAiB,KAAK,EAAE,EACxB,mBAAmB,CACrB,CACF,CAEA,SAAS,WAAW,OAAQ,CAC1B,MAAM,KAAO,cAAc,KAAK,GAAK,EAAE,KAAO,MAAM,EACpD,GAAI,CAAC,KAAM,OAGX,MAAM,GAAK,kBAAkB,MAAM,EAC/B,KACE,GAAG,SAAW,aAAe,GAAG,KAAK,GAAG,IAAI,MAAM,EACtD,oBAAsB,GAAG,eAAiB,EAC1C,OAAO,kBAAkB,MAAM,GAEjC,oBAAsB,KAAK,KACvB,KAAK,UAAU,IAAI,gBAAgB,KAAK,QAAQ,EAEpD,cAAgB,cAAc,OAAO,GAAK,EAAE,KAAO,MAAM,EACzD,MAAM,KAAO,QAAQ,MAAM,EACvB,MAAM,KAAK,OAAO,EAElB,cAAc,SAAW,IAC3B,eAAiB,KACjB,gBAAkB,KAClB,iBAAmB,EACnB,mBAAqB,EACrB,mBAAqB,EACrB,GAAG,KAAK,OAAS,IAEnB,mBAAmB,EACnB,sBAAsB,CACxB,CAGA,SAAS,SAAS,KAAM,CAAE,OAAO,eAAe,KAAK,IAAI,EAAI,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,GAAK,KAAK,IAAM,CAIvG,SAAS,gBAAiB,CACxB,MAAM,YAAc,CAAC,EACf,YAAc,CAAC,EACrB,cAAc,QAAQ,MAAQ,CAC5B,KAAK,QAAQ,OAAO,QAAQ,MAAQ,CAClC,MAAM,IAAM,KAAK,UAAY,EAC7B,GAAI,KAAK,OAAS,SAAU,CAC1B,MAAM,KAAO,yBAAyB,KAAK,MAAM,EACjD,YAAY,KAAK,CAAE,KAAM,KAAM,KAAM,IAAK,UAAW,KAAO,GAAI,CAAC,CACnE,KAAO,CAEL,MAAM,IAAM,SAAS,IAAI,EACpB,YAAY,GAAG,IAAG,YAAY,GAAG,EAAI,CAAE,KAAM,KAAK,KAAM,MAAO,eAAe,KAAK,IAAI,EAAI,KAAK,MAAQ,KAAM,IAAK,CAAE,GAC1H,YAAY,GAAG,EAAE,KAAO,GAC1B,CACF,CAAC,CACH,CAAC,EAED,MAAM,QAAU,CAAC,EACjB,OAAO,OAAO,WAAW,EAAE,QAAQ,KAAO,CACxC,MAAM,EAAI,QAAQ,IAAI,KAAM,IAAI,KAAK,EACrC,QAAQ,CAAC,GAAK,QAAQ,CAAC,GAAK,GAAK,IAAI,GACvC,CAAC,EACD,IAAI,SAAW,EACf,cAAO,KAAK,WAAW,EAAE,QAAQ,KAAO,CACtC,MAAM,IAAM,YAAY,GAAG,EAC3B,IAAI,KAAO,qBAAqB,IAAI,KAAM,QAAQ,QAAQ,IAAI,KAAM,IAAI,KAAK,CAAC,EAAG,IAAI,KAAK,EAC1F,IAAI,UAAY,IAAI,KAAO,IAAI,IAC/B,UAAY,IAAI,SAClB,CAAC,EACD,YAAY,QAAQ,GAAK,CAAE,UAAY,EAAE,SAAW,CAAC,EAC9C,CAAE,YAAa,YAAa,QAAS,CAC9C,CAEA,SAAS,oBAAqB,CAC5B,KAAM,CAAE,QAAS,EAAI,eAAe,EAC9B,WAAa,cAAc,EAC3B,SAAW,YAAY,UAAU,EACvC,GAAG,MAAM,YAAc,cAAc,OACrC,GAAG,SAAS,YAAc,MAAM,QAAQ,EACxC,GAAG,YAAY,OAAS,aAAe,OACvC,GAAG,SAAS,YAAc,MAAM,QAAQ,EACxC,GAAG,UAAU,YAAc,IAAM,MAAM,aAAa,CAAC,EACrD,GAAG,MAAM,YAAc,MAAM,SAAW,QAAQ,EAChD,GAAG,OAAO,OAAS,CAAC,SAAS,UAAU,EACvC,GAAG,cAAc,YAAc,SAAS,UAAU,EAAI,UAAY,cAElE,GAAG,WAAW,OAAS,CAAC,cAAc,KAAK,GAAK,EAAE,QAAQ,OAAO,KAAK,GAAK,EAAE,OAAS,UAAY,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC,EAC1H,kBAAkB,CACpB,CAEA,iBAAiB,QAAQ,OAAS,MAAM,iBAAiB,SAAU,kBAAkB,CAAC,EACtF,GAAG,OAAO,iBAAiB,SAAU,kBAAkB,EAGvD,SAAS,eAAgB,CACvB,KAAM,CAAE,YAAa,YAAa,QAAS,EAAI,eAAe,EACxD,WAAa,cAAc,EAC3B,SAAW,YAAY,UAAU,EACjC,YAAc,GAAG,OAAO,QAAU,QAAU,OAAS,SACrD,KAAO,CAAC,EAER,WAAa,OAAO,KAAK,YAAY,EAC3C,OAAO,OAAO,WAAW,EACtB,KAAK,CAAC,EAAG,IAAO,UAAU,QAAQ,EAAE,IAAI,EAAI,UAAU,QAAQ,EAAE,IAAI,GAAO,WAAW,QAAQ,EAAE,KAAK,EAAI,WAAW,QAAQ,EAAE,KAAK,CAAE,EACrI,QAAQ,KAAO,CACd,MAAM,MAAQ,IAAI,MAAQ,aAAa,IAAI,KAAK,EAAI,YACpD,KAAK,KAAK,cAAc,WAAW,UAAU,IAAI,IAAI,CAAC,CAAC,aAAU,WAAW,KAAK,CAAC,wBAAwB,IAAI,GAAG,wBAAwB,MAAM,IAAI,IAAI,CAAC,wBAAwB,MAAM,IAAI,SAAS,CAAC,YAAY,CAClN,CAAC,EACH,YAAY,QAAQ,GAAK,CACvB,MAAM,YAAc,YAAY,EAAE,KAAK,OAAO,KAAK,GAAK,CAAC,GAAG,OAAS,EAAE,KAAK,OAAO,MACnF,KAAK,KAAK,qCAAkC,EAAE,KAAK,OAAO,SAAS,OAAI,EAAE,KAAK,OAAO,QAAQ,WAAQ,WAAW,UAAU,CAAC,wBAAwB,EAAE,GAAG,wBAAwB,MAAM,EAAE,IAAI,CAAC,wBAAwB,MAAM,EAAE,SAAS,CAAC,YAAY,CACrP,CAAC,EACG,aAAe,QACjB,KAAK,KAAK,yDAAyD,MAAM,QAAQ,CAAC,YAAY,EAGhG,MAAM,IAAM,CACV,QAAS,uFACT,QAAS,8HACT,KAAM,kHACR,EAAE,UAAU,EAEZ,GAAG,WAAW,UAAY;AAAA;AAAA;AAAA,mBAGb,KAAK,KAAK,EAAE,CAAC;AAAA,iEACiC,MAAM,SAAW,QAAQ,CAAC;AAAA;AAAA,qCAEtD,GAAG;AAAA,OAEpC,CAKA,SAAS,2BAA4B,CACnC,MAAM,aAAe,GAAG,OAAO,MACzB,CAAE,WAAY,EAAI,eAAe,EACjC,IAAM,CAAC,EACb,qBAAc,QAAQ,MAAQ,CAC5B,KAAK,QAAQ,OAAO,QAAQ,MAAQ,CAClC,MAAM,SAAW,KAAK,OAAS,SACzB,MAAQ,CACZ,KAAM,KAAK,KACX,OAAQ,KAAK,OAAS,GACtB,WAAY,KAAK,KACjB,OAAQ,SAAW,KAAK,OAAO,MAAQ,eAAe,KAAK,IAAI,EAAI,KAAK,MAAQ,aAChF,IAAK,KAAK,QACZ,EACA,GAAI,SACF,MAAM,OAAS,CACb,MAAO,KAAK,OAAO,MACnB,UAAW,OAAO,KAAK,OAAO,SAAS,EACvC,SAAU,OAAO,KAAK,OAAO,QAAQ,CACvC,EACA,MAAM,MAAQ,yBAAyB,KAAK,MAAM,EAAI,KAAK,aACtD,CACL,MAAM,IAAM,YAAY,SAAS,IAAI,CAAC,EACtC,MAAM,OAAS,IAAM,IAAI,KAAO,qBAAqB,KAAK,KAAM,KAAK,SAAU,KAAK,KAAK,GAAK,KAAK,QACrG,CACA,IAAI,KAAK,KAAK,CAChB,CAAC,CACH,CAAC,EACM,GACT,CAEA,GAAG,OAAO,iBAAiB,QAAS,UAAY,CAE9C,GADA,UAAU,EAAE,EACR,CAAC,QAAS,CAAE,UAAU,iFAA6E,EAAG,MAAQ,CAClH,GAAI,cAAc,SAAW,EAAG,CAAE,UAAU,+BAA+B,EAAG,MAAQ,CACtF,GAAI,CAAC,GAAG,KAAK,MAAM,KAAK,EAAG,CAAE,UAAU,yBAAyB,EAAG,GAAG,KAAK,MAAM,EAAG,MAAQ,CAC5F,GAAI,CAAC,GAAG,MAAM,MAAM,KAAK,GAAK,CAAC,GAAG,MAAM,cAAc,EAAG,CAAE,UAAU,qCAAqC,EAAG,GAAG,MAAM,MAAM,EAAG,MAAQ,CAEvI,MAAM,eAAiB,cAAc,OAAO,CAAC,EAAG,IAC9C,EAAI,EAAE,QAAQ,OAAO,OAAO,GAAK,EAAE,OAAS,UAAY,CAAC,kBAAkB,EAAE,MAAM,CAAC,EAAE,OAAQ,CAAC,EACjG,GAAI,eAAiB,EAAG,CACtB,UAAU,GAAG,cAAc,wDAAmD,eAAe,SAAI,eAAe,cAAc,eAAe,SAAI,eAAe,KAAK,EACrK,MACF,CACA,MAAM,QAAU,aAAa,EAC7B,GAAI,QAAU,EAAG,CACf,UAAU,GAAG,OAAO,wFAAmF,EACvG,MACF,CACA,MAAM,OAAS,cAAc,OAAO,GAAK,EAAE,eAAiB,OAAO,EAAE,OACrE,GAAI,OAAS,EAAG,CAAE,UAAU,GAAG,MAAM,iEAA8D,EAAG,MAAQ,CAC9G,GAAI,CAAC,eAAgB,CAAE,UAAU,qEAAqE,EAAG,MAAQ,CAEjH,cAAc,EACV,OAAO,GAAG,OAAO,WAAc,WAAY,GAAG,OAAO,UAAU,EAC9D,cAAc,CACrB,CAAC,EAED,GAAG,aAAa,iBAAiB,QAAS,IAAM,GAAG,OAAO,MAAM,CAAC,EACjE,GAAG,cAAc,iBAAiB,QAAS,IAAM,CAAE,GAAG,OAAO,MAAM,EAAG,cAAc,CAAG,CAAC,EAExF,SAAS,WAAW,GAAI,KAAM,CAC5B,GAAG,QAAQ,OAAS,CAAC,GACrB,GAAG,YAAY,YAAc,MAAQ,EACvC,CAEA,SAAS,eAAgB,CACvB,WAAW,GAAM,0BAAqB,EACtC,MAAM,QAAU,CACd,SAAU,eACV,UAAW,gBACX,SAAU,CAAE,KAAM,GAAG,KAAK,MAAM,KAAK,EAAG,MAAO,GAAG,MAAM,MAAM,KAAK,CAAE,EACrE,MAAO,GAAG,MAAM,MAAM,KAAK,EAC3B,MAAO,0BAA0B,EACjC,WAAY,cAAc,CAC5B,EAEA,SAAS,gBAAgB,QAAS,CAChC,OAAO,QAAQ,yBAA0B,OAAO,EAAE,MAAM,KAAO,CAC7D,GAAI,CAAC,iBAAiB,GAAG,GAAK,SAAW,yBAAyB,OAAQ,MAAM,IAChF,kBAAW,GAAM,6DAAwD,QAAU,CAAC,OAAO,yBAAyB,OAAS,CAAC,SAAI,EAC3H,IAAI,QAAQ,GAAK,WAAW,EAAG,yBAAyB,OAAO,CAAC,CAAC,EAAE,KAAK,IAAM,gBAAgB,QAAU,CAAC,CAAC,CACnH,CAAC,CACH,CAEA,gBAAgB,CAAC,EAAE,KAAK,MAAQ,CAC9B,GAAI,MAAQ,KAAK,aAAc,CAE7B,WAAW,GAAM,qCAAgC,EACjD,cAAgB,CAAC,EACjB,OAAO,SAAS,KAAO,KAAK,aAC5B,MACF,CACA,WAAW,EAAK,EAChB,GAAG,QAAQ,YAAc,KAAK,UAAY,eAC1C,cAAc,QAAQ,GAAK,EAAE,UAAY,IAAI,gBAAgB,EAAE,QAAQ,CAAC,EACxE,WAAW,EACX,GAAG,KAAK,OAAS,GACjB,GAAG,KAAK,OAAS,GACjB,GAAG,KAAK,eAAe,CAAE,SAAU,SAAU,MAAO,QAAS,CAAC,CAChE,CAAC,EAAE,MAAM,OAAS,CAChB,QAAQ,MAAM,gBAAiB,KAAK,EACpC,WAAW,EAAK,EACZ,iBAAiB,KAAK,EAExB,UAAU,wJAAoJ,EAE9J,UAAU,2CAA6C,MAAM,OAAO,CAExE,CAAC,CACH,CAEA,OAAO,iBAAiB,eAAgB,SAAU,EAAG,CAC/C,cAAc,OAAS,IAAK,EAAE,eAAe,EAAG,EAAE,YAAc,GACtE,CAAC,EAED,GAAG,SAAS,iBAAiB,QAAS,UAAY,CAChD,GAAG,SAAS,UAAY,GACxB,GAAG,MAAM,MAAQ,GACjB,WAAW,EACX,GAAG,OAAO,MAAQ,SAClB,MAAM,QAAU,KAAK,cAAc,oCAAoC,EACnE,UAAS,QAAQ,QAAU,IAC/B,GAAG,KAAK,OAAS,GACjB,mBAAmB,EACnB,sBAAsB,EACtB,GAAG,KAAK,OAAS,GACjB,GAAG,KAAK,OAAS,EACnB,CAAC,EAED,mBAAmB,CACrB,CAEA,SAAS,MAAO,CAAE,SAAS,iBAAiB,gBAAgB,EAAE,QAAQ,IAAI,CAAG,CACzE,SAAS,aAAe,UAAW,SAAS,iBAAiB,mBAAoB,IAAI,EACpF,KAAK,CACZ,GAAG",
  "names": []
}
