{
  "version": 3,
  "sources": ["/cdn/shop/t/76/assets/maven-ingredients.js?cb=212551"],
  "sourcesContent": ["/*\n * The QR-code ingredients page \u2014 docs/theme-contracts.md \u00A75.\n *\n * `/pages/ingredients?ids=<product-ids>&order=<order-number>` is printed as a\n * QR code on physical shipping labels. A customer scans it standing at the\n * fridge. Paper cannot be recalled, so this URL and these query parameters are\n * a permanent contract; Heidi, 2026-09-23, put the window at two weeks, which\n * bounds how long an OLD label must keep working, not how long the page lives.\n *\n * WHAT IT SHOWS. Kumquat's product blob, from its App Proxy (on dawn since\n * 2026-10-03, #577, and on production since 2026-10-05, #578). Since MAV-1029\n * the proxy is the only source; see loadBlob() below. The blob holds one\n * <div class=\"productlist-product\"> per product, carrying data-product-id,\n * data-product-title, data-product-tags, a photograph and five fields:\n * Ingredients, Serving Size, Eat Within, Notes, Instructions. This\n * script fetches that blob, keeps only the ids in the order, and renders the\n * dietary pills the blob does not.\n *\n * \u26A0\uFE0F THE BLOB IS 1.98 MB AND HOLDS EVERY PRODUCT \u2014 1,558 entries, both measured on\n * production 2026-09-23, growing about 78% every seven months. The whole file\n * is downloaded to show one order, at the fridge, on a phone, on cellular. A\n * per-id Kumquat endpoint would make it a few KB and is issue #169's option 2.\n * It does not change this URL, so it can replace the fetch below without\n * touching a printed label.\n *\n * \u26A0\uFE0F THE BLOB RENDERS NO DIETARY CLAIM OF ITS OWN. Measured: all 1,558 entries\n * carry exactly the five field labels above and nothing else. The pills here\n * are NEW on this page \u2014 Heidi asked for them on 2026-09-23 \u2014 so everything\n * below is the first time an allergen claim reaches this surface. That is why\n * the mapping is pinned rather than written by hand.\n */\n\n(function () {\n  'use strict';\n\n  /*\n   * \u26A0\uFE0F THIS TABLE IS PINNED TO scripts/allergen-guard.mjs CLAIMS.\n   * `assertIngredientsClaims()` there fails the build if the two disagree, for\n   * the same reason menu-diet-tags.liquid is pinned: the first version of that\n   * snippet's tests passed while a gluten-free tag lit the dairy-free flag, and\n   * nothing about the page looked wrong. Change a row there and here in one\n   * commit.\n   *\n   * `tag` is the FULL namespaced tag, not the blob's abbreviation, so this\n   * table and CLAIMS compare as strings. See expandTag() for why that matters.\n   */\n  var CLAIMS = [\n    { tag: 'filter::diet::vegetarian', flag: 'veg', cls: 'veg', abbr: 'V', label: 'Vegetarian' },\n    { tag: 'filter::diet::vegan', flag: 'vegan', cls: 'vegan', abbr: 'VG', label: 'Vegan' },\n    /* Shown only where V and VG are not \u2014 see rule 2 in claimsFor(). The tag\n     * is the store's spelling and the label is Heidi's. */\n    { tag: 'filter::diet::pescetarian', flag: 'pesc', cls: 'pesc', abbr: 'P', label: 'Pescatarian' },\n    { tag: 'filter::allergen::gluten-free', flag: 'gf', cls: 'gf', abbr: 'GF', label: 'Gluten-free' },\n    { tag: 'filter::allergen::dairy-free', flag: 'df', cls: 'df', abbr: 'DF', label: 'Dairy-free' },\n    { tag: 'filter::allergen::egg-free', flag: 'ef', cls: 'ef', abbr: 'EF', label: 'Egg-free' },\n    /* No pill, by Heidi's call of 2026-09-01. The flag is still set and still\n     * cleared by either caution; it reaches the page only as data-mv-claims.\n     * This is the one claim whose ABSENCE means something, which is why the two\n     * cautions below are what the nut claim actually rests on. */\n    { tag: 'filter::allergen::nut-free', flag: 'nf', cls: 'nutfree', abbr: 'NF', label: 'Nut-free', rendered: false },\n    { tag: 'filter::allergen::no-chilis', flag: 'nc', cls: 'nochili', abbr: 'NC', label: 'No chilis' },\n    /* \u26A0\uFE0F `filter::diet::`, and NOT the allergen namespace the blob carries.\n     * The blob holds `a::high-protein` on 461 products, which expands to\n     * `filter::allergen::high-protein` \u2014 production's string, which this theme\n     * deliberately does not render. CLAIMS says matching both namespaces \"would\n     * make the divergence permanent by making it invisible\", so HP shows here\n     * on nothing until step 5 of docs/live-allergen-tagging.md retags those\n     * products. Rendering it from the allergen string would hide the very\n     * migration that note exists to force. */\n    { tag: 'filter::diet::high-protein', flag: 'hp', cls: 'hp', abbr: 'HP', label: 'High protein' },\n    /* MAV-944. A dish with a gluten-free variant and one that is not. It\n     * outranks GF (rule 3 in claimsFor()), and a screen reader hears `sr`.\n     * After HP, so the pills sit in the same order as on the menu card. */\n    { tag: 'filter::allergen::gluten-free-option', flag: 'gf_option', cls: 'gfoption', abbr: null, label: 'GF option', sr: 'Gluten-free option' },\n    { tag: 'filter::allergen::contains-nuts', flag: 'contains_nuts', cls: 'contains', abbr: null, label: 'Contains nuts' },\n    { tag: 'filter::allergen::nuts-on-side', flag: 'nuts_on_side', cls: 'contains', abbr: null, label: 'Nuts on the side' }\n  ];\n\n  /*\n   * The blob abbreviates the namespace: `a::nut-free`, `d::vegan`. The theme's\n   * vocabulary is `filter::allergen::nut-free`, `filter::diet::vegan`.\n   *\n   * Expanding here rather than keeping a second lookup table is the whole\n   * defence against drift: after this call the tag is the SAME STRING the\n   * Liquid snippet and CLAIMS match on, so a new claim added there needs no\n   * translation invented here. A tag in any other namespace \u2014 cuisine::,\n   * format::, p::, protein::, role:: \u2014 expands to nothing and falls out.\n   */\n  function expandTag(raw) {\n    var t = String(raw || '').trim();\n    if (t.indexOf('a::') === 0) return 'filter::allergen::' + t.slice(3);\n    if (t.indexOf('d::') === 0) return 'filter::diet::' + t.slice(3);\n    return null;\n  }\n\n  /*\n   * Precedence, identical to snippets/menu-diet-tags.liquid and in its order.\n   * Six rules, each one a decision rather than a tidy-up:\n   *\n   *   1. vegan clears vegetarian \u2014 one mark, the more specific one.\n   *   2. vegetarian or vegan clears pescatarian. The tag means \"a pescatarian\n   *      can eat this\", so P marks only the fish dishes. Heidi, 2026-09-24.\n   *   3. GF option clears gluten-free. A dish with a flour variant is not\n   *      gluten-free as a whole. MAV-944, Heidi, 2026-10-19.\n   *   4. nuts on the side clear nut-free. They are still nuts in the box.\n   *   5. contains-nuts clears nuts-on-side. Only ever one caution, the stronger.\n   *   6. contains-nuts clears nut-free. Fail toward caution.\n   *\n   * A claim is NEVER derived from absence. \"Not tagged nut-free\" also covers\n   * \"contains no nuts but nobody recorded it\", so inferring a caution would\n   * print \"Contains nuts\" on a dish with none in it.\n   */\n  function claimsFor(tagString) {\n    var present = {};\n    String(tagString || '')\n      .split(',')\n      .forEach(function (raw) {\n        var full = expandTag(raw);\n        if (full) present[full] = true;\n      });\n\n    var flags = {};\n    CLAIMS.forEach(function (c) {\n      flags[c.flag] = present[c.tag] === true;\n    });\n\n    if (flags.vegan) flags.veg = false;\n    if (flags.veg || flags.vegan) flags.pesc = false;\n    if (flags.gf_option) flags.gf = false;\n    if (flags.nuts_on_side) flags.nf = false;\n    if (flags.contains_nuts) flags.nuts_on_side = false;\n    if (flags.contains_nuts) flags.nf = false;\n    return flags;\n  }\n\n  /*\n   * \u26A0\uFE0F KUMQUAT'S MARKUP, NOT OURS \u2014 AND THEREFORE VERIFIED, NOT ASSUMED.\n   *\n   * These four names come from Kumquat's rebuildIngredientsAsset. The file they\n   * describe is not on dawn (only on production, from Kumquat's bot\n   * commits) and is absent on the trial store, so this page\n   * cannot be rendered end to end before a merge. If any one of them were\n   * wrong, every real order would show \"We could not find the items from this\n   * order\" \u2014 a message written to read as a data problem, so it would not even\n   * be reported as a bug.\n   *\n   * They were asserted in a comment and checked nowhere until 2026-09-23.\n   * tests/fixtures/kumquat-products.sample.html is now two real entries captured\n   * from the production storefront, and scripts/allergen-guard.test.mjs asserts\n   * each selector below against it. Exported so the test reads THESE strings\n   * rather than copies of them.\n   */\n  var SEL = {\n    entry: '.productlist-product',\n    details: '.productlist-product__details',\n    heading: 'h5',\n    idAttr: 'data-product-id',\n    tagsAttr: 'data-product-tags'\n  };\n\n  /*\n   * \u26A0\uFE0F THE ID GOES INTO A CSS SELECTOR AND COMES FROM A URL, SO IT IS\n   * VALIDATED RATHER THAN ESCAPED. Stripping quotes is not enough: an id like\n   * `123\"] , [x` still produced `[data-product-id=\"] , [x\"]`, which is a\n   * malformed selector. querySelector THROWS on that, the rejection lands in\n   * the catch below, and the customer sees \"We could not load the ingredient\n   * list\" for the whole order instead of a message about the one bad item.\n   *\n   * Shopify product ids are digits, so anything else is a malformed URL rather\n   * than a dish. isValidId() is the gate; a failing id is still counted as\n   * ASKED, so the page says some items are not shown and gives the phone\n   * number \u2014 which is true from where the customer is standing \u2014 rather than\n   * silently rendering fewer dishes and looking complete.\n   */\n  function isValidId(id) {\n    return /^[0-9]+$/.test(String(id));\n  }\n\n  function entrySelector(id) {\n    return SEL.entry + '[' + SEL.idAttr + '=\"' + String(id) + '\"]';\n  }\n\n  function esc(s) {\n    return String(s).replace(/[&<>\"']/g, function (c) {\n      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c];\n    });\n  }\n\n  /*\n   * The markup mirrors snippets/menu-diet-tags.liquid exactly, including the\n   * accessible name: the visible letters are aria-hidden and the full word is a\n   * visually-hidden sibling. An <abbr> maps to a generic role, for which a\n   * name from the author is not guaranteed to be announced.\n   *\n   * The row renders whenever there is anything to SAY, pill or not. A dish\n   * whose only claim is nut-free has no pill at all and still needs the\n   * element, because the claim rides on it as data-mv-claims.\n   */\n  function pillsHtml(flags) {\n    var anything = CLAIMS.some(function (c) {\n      return flags[c.flag];\n    });\n    if (!anything) return '';\n\n    var items = CLAIMS.filter(function (c) {\n      return flags[c.flag] && c.rendered !== false;\n    })\n      .map(function (c) {\n        if (c.abbr) {\n          return (\n            '<li class=\"tag abbr ' + c.cls + '\">' +\n            '<abbr title=\"' + esc(c.label) + '\" aria-hidden=\"true\">' + esc(c.abbr) + '</abbr>' +\n            '<span class=\"visually-hidden\">' + esc(c.label) + '</span></li>'\n          );\n        }\n        /* GF option: the visible words are hidden from a screen reader, which\n         * would spell out G and F, and the full words sit beside them. */\n        if (c.sr) {\n          return (\n            '<li class=\"tag ' + c.cls + '\"><span aria-hidden=\"true\">' + esc(c.label) + '</span>' +\n            '<span class=\"visually-hidden\">' + esc(c.sr) + '</span></li>'\n          );\n        }\n        /* A caution is spelled out in full and never abbreviated. */\n        return '<li class=\"tag contains\">' + esc(c.label) + '</li>';\n      })\n      .join('');\n\n    return '<ul class=\"mv-tags\" role=\"list\"' + (flags.nf ? ' data-mv-claims=\"nutfree\"' : '') + '>' + items + '</ul>';\n  }\n\n  /*\n   * \u26A0\uFE0F DEDUPED, AND THAT IS A CORRECTNESS FIX RATHER THAN A TIDY-UP.\n   *\n   * An order can list the same product id twice \u2014 two units of one dish. The\n   * render loop used to MOVE each matched entry out of the parsed blob and\n   * into the page, so the second lookup of a repeated id found nothing: the\n   * node was no longer a descendant of the holder. (Since 2026-10-02 render()\n   * CLONES the entry, so the parsed blob can be reused. Do not go back to a\n   * move: it would empty the cache for every later open.) `found` then came up short of the number\n   * of ids asked for, and the page appended \"Some items from this order are not\n   * shown. Please call us\" \u2014 a false claim that data is missing, on the one\n   * surface a customer reads while deciding whether to trust what is in front\n   * of them, for an order that was complete.\n   *\n   * One card per distinct dish is the intent: a second identical ingredient\n   * list tells a customer nothing. So the ids are deduped here, and the\n   * completeness check below compares against the deduped count.\n   */\n  function wantedIds(ids) {\n    var seen = {};\n    var out = [];\n    String(ids || '')\n      .split(',')\n      .forEach(function (raw) {\n        var id = raw.trim();\n        if (!id || seen[id]) return;\n        seen[id] = true;\n        out.push(id);\n      });\n    return out;\n  }\n\n  /* The blob is parsed once and each entry is cloned out of it. Since\n   * 2026-10-02 a dish opens in place and can be opened again, and parsing\n   * 2 MB of HTML on every press is a visible pause on a phone. */\n  var parsedHtml = null;\n  var parsedHolder = null;\n  function render(root, blobHtml, ids) {\n    if (parsedHtml !== blobHtml) {\n      parsedHolder = document.createElement('div');\n      parsedHolder.innerHTML = blobHtml;\n      parsedHtml = blobHtml;\n    }\n    var holder = parsedHolder;\n\n    var wanted = wantedIds(ids);\n\n    var found = 0;\n    wanted.forEach(function (id) {\n      if (!isValidId(id)) return;\n      var source = holder.querySelector(entrySelector(id));\n      if (!source) return;\n      var entry = source.cloneNode(true);\n      found += 1;\n\n      var details = entry.querySelector(SEL.details);\n      if (details) {\n        var html = pillsHtml(claimsFor(entry.getAttribute(SEL.tagsAttr)));\n        if (html) {\n          var h = details.querySelector(SEL.heading);\n          if (h) h.insertAdjacentHTML('afterend', html);\n          else details.insertAdjacentHTML('afterbegin', html);\n        }\n      }\n      root.appendChild(entry);\n    });\n    return { found: found, asked: wanted.length };\n  }\n\n  function state(root, msg, link, target) {\n    /* Every dead end offers a way onward. A customer at the fridge who gets\n     * \"we couldn't find it\" and nothing else has been told to give up.\n     *\n     * \u26A0\uFE0F `{PHONE}` IS SUBSTITUTED AFTER ESCAPING, and the order matters. The\n     * message is escaped first, so nothing in it can inject markup; only then\n     * is the placeholder swapped for an anchor this file built. Putting the\n     * anchor into the string beforehand would have it escaped and printed as\n     * visible tags. */\n    var html = esc(msg).split('{PHONE}').join(phoneLink(root));\n    (target || root).innerHTML = '<p class=\"mv-ing__state\">' + html + (link ? ' ' + link : '') + '</p>';\n  }\n\n  /*\n   * A link to Past Menus, built from the section setting so the URL is a theme\n   * decision rather than a string in a script. Heidi asked for it on\n   * 2026-09-23: the messages used to end at the phone number.\n   */\n  function pastMenusLink(root, words) {\n    var href = root.getAttribute('data-past-menus');\n    if (!href) return '';\n    return '<a class=\"mv-ing__state-link\" href=\"' + esc(href) + '\">' + esc(words) + '</a>';\n  }\n\n  function phoneOf(root) {\n    return root.getAttribute('data-phone') || '';\n  }\n\n  /*\n   * \u26A0\uFE0F THE PHONE NUMBER IS A LINK IN EVERY MESSAGE, which is why these strings\n   * are assembled rather than written whole. Ryan's brief, Task 4: each script\n   * message must carry a `tel:+12065577985` link. They reach a customer at the\n   * fridge, on a phone, at the moment something has gone wrong \u2014 a number they\n   * have to retype is a number they do not call.\n   *\n   * \u26A0\uFE0F BOTH HALVES COME FROM THE SECTION, AND AN EARLIER VERSION HARD-CODED THE\n   * DIAL STRING. Its comment claimed that fixing the href stopped it drifting\n   * from the printed number. It does the opposite: the printed number is\n   * `section.settings.phone`, a Theme Editor field, so the first edit to it\n   * would leave every message showing the new number and dialling the old one.\n   * Nothing on the page would look wrong, and the customer reaches a dead line.\n   *\n   * Two fields, the same pattern as `sections/maven-content.liquid`'s contact\n   * block: `phone` as printed, `phone_href` as dialled. With no dial string the\n   * number renders as plain text rather than as a link to nowhere. `state()`\n   * escapes its message and appends this already escaped, so the anchor\n   * survives.\n   */\n  function phoneLink(root) {\n    var words = esc(phoneOf(root));\n    var href = (root.getAttribute('data-phone-href') || '').replace(/^tel:/, '');\n    if (!href) return words;\n    return '<a href=\"tel:' + esc(href) + '\">' + words + '</a>';\n  }\n\n  /*\n   * \u26A0\uFE0F THE BLOB IS FETCHED LAZILY, AND THAT IS THE POINT OF MEMOISING IT.\n   *\n   * It is 1.98 MB (2026-09-23) and holds every product. Before this page had an\n   * index it was only ever loaded by somebody who had scanned a label, so the\n   * cost fell on the one visitor who needed it. The index makes this a page\n   * people browse, so loading it on arrival would charge every visitor 2 MB at\n   * the fridge, on cellular, to read a list the server already rendered.\n   *\n   * So: the index is server-rendered and needs no blob, and the blob is\n   * requested on the first thing that actually needs it \u2014 an `ids` parameter, a\n   * dish being opened, or a search that has to reach beyond the index.\n   *\n   * Since 2026-10-11 only the search loads the whole list. A label and an\n   * opened dish ask the proxy for their own ids (loadSome, below), and use\n   * the whole list only when that fails or when the search has loaded it.\n   */\n  /*\n   * \u26A0\uFE0F ONE SOURCE, KUMQUAT'S PROXY, SINCE MAV-1029 (2026-10-30).\n   *\n   * `data-blob-proxy` names Kumquat's App Proxy, /apps/kumquat/ingredients\n   * (kumquat_v2 #622). It reads the current product data.\n   *\n   * From 2026-10-03 the page fell back to the theme file\n   * assets/kumquat_products.html, in `data-blob`. Kumquat stopped writing\n   * that file on 2026-10-07, so it could only go stale: on 2026-10-03 the\n   * copy in git held old allergen tags on 10 dishes, 2 of them a false\n   * nut-free. Heidi, 2026-10-30, #monet: \"Retire the fallback. The theme\n   * shows an error when the proxy fails.\" Do not add a second source back.\n   *\n   * A failed proxy is a rejected promise: a non-2xx answer (Kumquat answers\n   * 503 when it has nothing to serve), a network error, or a body that is\n   * not the blob. Each caller turns the rejection into its \"didn't load\"\n   * state with the phone number. The body test stops a Shopify error page\n   * from looking like an empty order.\n   */\n  var BLOB_MARK = 'productlist-product';\n  function fetchBlob(url) {\n    if (!url) return Promise.reject(new Error('blob: no url'));\n    return fetch(url, { cache: 'no-store' }).then(function (r) {\n      /* \u26A0\uFE0F A MISSING BLOB MUST NOT LOOK LIKE AN EMPTY ORDER. The proxy can\n       * fail. A page with no dishes on it is a fault, so throw. */\n      if (!r.ok) throw new Error('blob ' + r.status);\n      return r.text();\n    }).then(function (text) {\n      if (text.indexOf(BLOB_MARK) === -1) throw new Error('blob: not the product list');\n      return text;\n    });\n  }\n\n  /* \u26A0\uFE0F A FAILED LOAD IS NOT KEPT, SINCE 2026-10-11. `blobPromise` is the\n   * shared request, and `blobText` is its answer once it has one. A rejected\n   * request clears `blobPromise`, so the next caller tries again rather than\n   * inherit the failure: the review of promotion #605 found that a failed\n   * search made a later dish tap skip its own `?ids=` request and show the\n   * search's error. loadSome() reuses only `blobText`, a list that loaded. */\n  var blobPromise = null;\n  var blobText = null;\n  function loadBlob(root) {\n    if (blobPromise) return blobPromise;\n    blobPromise = fetchBlob(root.getAttribute('data-blob-proxy'))\n      .then(function (text) {\n        blobText = text;\n        return text;\n      }, function (err) {\n        blobPromise = null;\n        throw err;\n      });\n    return blobPromise;\n  }\n\n  /*\n   * \u26A0\uFE0F ONLY THE DISHES ASKED FOR, SINCE 2026-10-11 (MAV-631).\n   *\n   * Kumquat's proxy answers `?ids=<id>,<id>` with only those dishes: 3.3 KB\n   * for two, against 2 MB for the full list (Tim's agent, #monet,\n   * 2026-10-10). Each block is the same markup as in the full list, so\n   * render() reads it unchanged. A scanned label and an opened dish use this.\n   * The past-dish search still needs every title, so it keeps loadBlob().\n   *\n   * \u26A0\uFE0F ANY FAILURE FALLS BACK TO loadBlob(), and so to the full proxy list.\n   * That covers a non-2xx answer (404 when no id is known, 400 for a\n   * malformed one), a route that is missing (the trial store's proxy\n   * answered 404 on 2026-10-05), and a body without the product markup. A\n   * dish that really is unknown then gets the same \"We couldn't find\" answer\n   * from the full list that it got before. If the full list fails too, the\n   * caller shows its \"didn't load\" state with the phone number.\n   *\n   * No `cache: 'no-store'` here: the route lets the browser keep an answer\n   * for five minutes, so opening the same dish again costs nothing. The full\n   * list keeps `no-store`, so the search always reads current product data.\n   *\n   * If the full list has already loaded, for a search, it is used instead. It\n   * holds every dish, so a second request would only cost time. A full list\n   * that is still loading, or that failed, is not used: the `?ids=` request\n   * goes ahead on its own.\n   */\n  function loadSome(root, ids) {\n    if (blobText) return Promise.resolve(blobText);\n    var proxy = root.getAttribute('data-blob-proxy');\n    var valid = wantedIds(ids).filter(isValidId);\n    if (!proxy || !valid.length) return loadBlob(root);\n    var url = proxy + (proxy.indexOf('?') === -1 ? '?' : '&') + 'ids=' + valid.join(',');\n    return fetch(url)\n      .then(function (r) {\n        if (!r.ok) throw new Error('ids ' + r.status);\n        return r.text();\n      })\n      .then(function (text) {\n        if (text.indexOf(BLOB_MARK) === -1) throw new Error('ids: not the product list');\n        return text;\n      })\n      .catch(function () {\n        return loadBlob(root);\n      });\n  }\n\n  /*\n   * \u26A0\uFE0F A JS PORT OF snippets/maven-dish-title.liquid, AND THE ONLY REASON IT\n   * EXISTS IS THAT THE BLOB CARRIES RAW SHOPIFY TITLES.\n   *\n   * The index renders titles through that snippet. The search reads the blob,\n   * which holds every product ever under its raw title \u2014 so a dish that has\n   * left its collection came back as \"Lentil Soup - Stock Your Freezer\", and a\n   * title still carrying `**NEW**` would print its asterisks to a customer,\n   * which that snippet's own comment says must never happen. Measured\n   * 2026-09-15: 11 products spell it `**NEW**`, 4 spell it `*NEW*`, 1 spells it\n   * `**NEW **`.\n   *\n   * The rules are the snippet's, in its order, including the one that is easy\n   * to miss: removing a marker can leave its separator behind, because four\n   * titles read \"*NEW* - Egg Salad\". The strip list is passed in as data from\n   * the section, so it cannot be a different list from the index's.\n   */\n  function cleanTitle(raw, stripList) {\n    var t = String(raw || '');\n    ['**NEW**', '**NEW **', '*NEW*'].forEach(function (marker) {\n      t = t.split(marker).join('');\n    });\n    String(stripList || '')\n      .split(',')\n      .forEach(function (part) {\n        var p = part.trim();\n        if (p) t = t.split(p).join('');\n      });\n    /* \"Limited Quantities\", in any of the seven spellings the store uses. The\n     * same steps as the snippet, in the same order: find the words case-blind,\n     * take \"y\" or \"ies\" and an optional \" available\", then the punctuation\n     * after them, and drop anything before them that is only asterisks. */\n    var low = t.toLowerCase();\n    var at = low.indexOf('limited quantit');\n    if (at !== -1) {\n      var end = at + 15;\n      if (low.substr(end, 3) === 'ies') end += 3;\n      else if (low.charAt(end) === 'y') end += 1;\n      if (low.substr(end, 10) === ' available') end += 10;\n      while (end < low.length && '* -:!|'.indexOf(low.charAt(end)) !== -1) end += 1;\n      var head = t.slice(0, at);\n      head = head.split('*').join('').trim() === '' ? '' : head.trim();\n      t = head + ' ' + t.slice(end);\n    }\n    t = t.trim();\n    if (t.charAt(0) === '-') t = t.slice(1).trim();\n    return t;\n  }\n\n  /* Titles of everything in the blob, by id. Used to search beyond the index \u2014\n   * the index holds this week and Everyday, the blob holds every dish the\n   * kitchen has made. Parsed once. */\n  var titleIndex = null;\n  function titlesFrom(blobHtml) {\n    if (titleIndex) return titleIndex;\n    titleIndex = [];\n    var re = /data-product-id='(\\d+)'\\s+data-product-title='([^']*)'/g;\n    var m;\n    while ((m = re.exec(blobHtml))) {\n      titleIndex.push({ id: m[1], title: m[2].replace(/&#39;/g, \"'\").replace(/&amp;/g, '&') });\n    }\n    /* Each entry's own text too, since 2026-09-26, so the search reaches the\n     * ingredients. It is the markup between one entry's id and the next, with\n     * the tags stripped: a string pass, so it needs no DOM and parses the\n     * 2 MB file once. The title stays a clean title; the text is separate. */\n    var starts = [];\n    var sr = /data-product-id='(\\d+)'/g;\n    var sm;\n    while ((sm = sr.exec(blobHtml))) starts.push({ id: sm[1], at: sm.index });\n    var text = {};\n    starts.forEach(function (st, i) {\n      var chunk = blobHtml.slice(st.at, i + 1 < starts.length ? starts[i + 1].at : blobHtml.length);\n      text[st.id] = fold(chunk.replace(/<[^>]*>/g, ' ').replace(/&#39;/g, \"'\").replace(/&amp;/g, '&'));\n    });\n    titleIndex.forEach(function (t) { t.text = text[t.id] || ''; });\n    return titleIndex;\n  }\n\n  /* Lowercase, with accents folded, so \"creme\" finds \"cr\u00E8me\". */\n  function fold(t) {\n    t = String(t || '').toLowerCase();\n    return t.normalize ? t.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '') : t;\n  }\n  /* Every word typed must appear, anywhere in the name or the ingredients. */\n  function hasAll(hay, words) {\n    return words.every(function (w) { return hay.indexOf(w) !== -1; });\n  }\n\n  function showIds(root, ids, opts) {\n    var o = opts || {};\n    loadSome(root, ids)\n      .then(function (html) {\n        root.innerHTML = '';\n        var res = render(root, html, ids);\n        if (res.found === 0) {\n          state(\n            root,\n            o.orderContext\n              ? \"We couldn't find the items from this order. Call us at {PHONE} and we'll read them to you.\"\n              : \"We couldn't find that dish. Call us at {PHONE} and we'll look it up.\",\n            pastMenusLink(root, 'Browse past menus')\n          );\n        } else if (res.found < res.asked) {\n          root.insertAdjacentHTML(\n            'beforeend',\n            '<p class=\"mv-ing__state\">Some items from this order aren\\'t showing. Call us at ' +\n              phoneLink(root) + ' for the rest.</p>'\n          );\n        }\n      })\n      .catch(function () {\n        state(\n          root,\n          \"The ingredient list didn't load. Call us at {PHONE} and we'll read it to you.\",\n          pastMenusLink(root, 'Browse past menus')\n        );\n      });\n  }\n\n  /* ---- a dish opens in place, since 2026-10-02 ------------------------------\n   * Heidi: \"currently when you open an item to view ingredients it opens the\n   * item at the top of the list. I'd like it to expand in place and then be\n   * able to close it when done.\"\n   *\n   * So a dish row in the index, or in the past-dishes list, opens a panel\n   * directly under itself. Pressing the row again closes it, and so does the\n   * panel's own Close button. More than one dish can be open at once. The\n   * panel is built once, from the same render() as the QR path, so its\n   * ingredients and pills cannot differ from what a scanned label shows.\n   *\n   * \u26A0\uFE0F THE QR PATH DOES NOT CHANGE. `?ids=` still renders the order's dishes\n   * into #mv-ingredients at the top: that list is the answer to the scan, not\n   * a row the customer chose. */\n  var panelN = 0;\n  function togglePanel(root, btn) {\n    var id = btn.getAttribute('data-mv-id');\n    var li = btn.closest ? btn.closest('.mv-ing__item') : null;\n    if (!id || !li) return;\n    var panel = li.querySelector('.mv-ing__panel');\n    /* A panel that failed to load is discarded, so pressing the row again\n     * tries again. Toggling it would only show the same error. */\n    if (panel && panel.hasAttribute('data-mv-failed')) {\n      panel.parentNode.removeChild(panel);\n      panel = null;\n    }\n    if (panel) {\n      var open = panel.hidden;\n      panel.hidden = !open;\n      btn.setAttribute('aria-expanded', open ? 'true' : 'false');\n      return;\n    }\n    panelN += 1;\n    panel = document.createElement('div');\n    panel.className = 'mv-ing__list mv-ing__panel';\n    panel.id = 'mv-ing-panel-' + panelN;\n    panel.innerHTML = '<p class=\"mv-ing__state mv-ing__state--quiet\">Loading the ingredients\\u2026</p>';\n    btn.setAttribute('aria-controls', panel.id);\n    btn.setAttribute('aria-expanded', 'true');\n    li.appendChild(panel);\n    loadSome(root, id)\n      .then(function (html) {\n        panel.innerHTML = '';\n        var res = render(panel, html, id);\n        if (res.found === 0) {\n          state(root, \"We couldn't find that dish. Call us at {PHONE} and we'll look it up.\",\n            pastMenusLink(root, 'Browse past menus'), panel);\n        }\n        addClose(panel, btn);\n      })\n      .catch(function () {\n        state(root, \"The ingredient list didn't load. Tap the dish to try again, or call us at {PHONE} and we'll read it to you.\",\n          pastMenusLink(root, 'Browse past menus'), panel);\n        /* A failed load is not cached: the panel is marked, so the next press\n         * on the row discards it and loads again, and the shared request is\n         * cleared, so that load is a new one. */\n        panel.setAttribute('data-mv-failed', '');\n        blobPromise = null;\n        addClose(panel, btn, true);\n      });\n  }\n\n  /* The Close button returns focus to the row, so a keyboard or screen-reader\n   * user lands where they were rather than at the top of the page. */\n  function addClose(panel, btn, forget) {\n    var close = document.createElement('button');\n    close.type = 'button';\n    close.className = 'mv-ing__close';\n    close.textContent = 'Close';\n    close.addEventListener('click', function () {\n      if (forget) panel.parentNode.removeChild(panel);\n      else panel.hidden = true;\n      btn.setAttribute('aria-expanded', 'false');\n      btn.focus();\n    });\n    panel.appendChild(close);\n  }\n\n  /* ---- the index, and the search over it ---------------------------------- */\n\n  function wireIndex(root) {\n    var index = document.getElementById('mv-ing-index');\n    if (!index) return;\n\n    /* Opening a dish loads the blob on demand and shows that one dish, under\n     * its own row. */\n    [].forEach.call(index.querySelectorAll('.mv-ing__pick'), function (b) {\n      b.setAttribute('aria-expanded', 'false');\n    });\n    index.addEventListener('click', function (e) {\n      var btn = e.target.closest ? e.target.closest('.mv-ing__pick') : null;\n      if (!btn) return;\n      togglePanel(root, btn);\n    });\n  }\n\n  function wireSearch(root) {\n    var q = document.getElementById('mv-ing-q');\n    var index = document.getElementById('mv-ing-index');\n    var count = document.getElementById('mv-ing-count');\n    var past = document.getElementById('mv-ing-past');\n    var note = document.getElementById('mv-ing-note');\n    var pastItems = document.getElementById('mv-ing-past-items');\n    if (!q || !index) return;\n\n    var items = [].slice.call(index.querySelectorAll('.mv-ing__item'));\n    var groups = [].slice.call(index.querySelectorAll('.mv-ing__group'));\n\n    /* \u26A0\uFE0F A WORD SEARCH, NOT AN ALLERGEN CHECK, AND AN EMPTY RESULT SAYS SO.\n     * Heidi, 2026-09-26. Kumquat's ingredient text is free-form, so \"peanut\"\n     * can miss \"groundnut\", and an empty result is when somebody might read\n     * \"nothing contains it\". */\n    function showNote(term, on) {\n      if (!note) return;\n      note.textContent = on\n        ? 'No dish lists \\u201c' + term + '\\u201d. This is a word search, not an allergen check. Open a dish to read its full ingredient list.'\n        : '';\n      note.hidden = !on;\n    }\n\n    function run() {\n      var term = q.value.trim().toLowerCase();\n      var words = fold(term).split(/\\s+/).filter(Boolean);\n      showNote(term, false);\n\n      /* The index filters immediately, with no network. That is why it is\n       * server-rendered: a search that waits 2 MB for its first result is not a\n       * search. */\n      /* \u26A0\uFE0F EACH DISH COUNTS AND SHOWS ONCE, EVEN IN TWO GROUPS. A product can sit\n       * in more than one collection \u2014 \"Roasted Diced Chicken Breast\" is in both\n       * Salad Bar and Stock Your Freezer \u2014 and the index deliberately keeps it\n       * under each, because someone browsing Salad Bar should find it there.\n       *\n       * A search is the other case: matching it twice reports \"2 dishes\" for\n       * one dish and prints the same row twice. Ryan's brief, Task 3.4. The\n       * first occurrence in group order wins, which is why this walks `items`\n       * in document order rather than grouping first. */\n      var shown = 0;\n      var seenInSearch = {};\n      items.forEach(function (li) {\n        var hit = !term || hasAll(fold(li.getAttribute('data-mv-search') || li.getAttribute('data-mv-name')), words);\n        if (hit && term) {\n          var btn = li.querySelector('.mv-ing__pick');\n          var id = btn ? btn.getAttribute('data-mv-id') : null;\n          if (id && seenInSearch[id]) hit = false;\n          else if (id) seenInSearch[id] = true;\n        }\n        li.hidden = !hit;\n        if (hit) shown += 1;\n      });\n      groups.forEach(function (g) {\n        g.hidden = !g.querySelector('.mv-ing__item:not([hidden])');\n      });\n\n      if (!term) {\n        if (count) count.textContent = '';\n        if (past) past.hidden = true;\n        if (pastItems) pastItems.innerHTML = '';\n        return;\n      }\n\n      if (count) count.textContent = shown === 1 ? '1 dish on a menu now' : shown + ' dishes on a menu now';\n\n      /* \u26A0\uFE0F AND EVERYTHING ELSE THE KITCHEN HAS MADE. Heidi: \"the search should\n       * have access to all of them.\" The index is this week and Everyday; the\n       * blob is every dish. A dish found here is not on a menu now, so it is\n       * listed separately rather than mixed in \u2014 the heading is what stops a\n       * customer reading a 2023 dish as orderable. */\n      if (!past || !pastItems) { showNote(term, shown === 0); return; }\n      loadBlob(root)\n        .then(function (html) {\n          if (q.value.trim().toLowerCase() !== term) return; /* typed on */\n          var onMenu = {};\n          items.forEach(function (li) {\n            var b = li.querySelector('.mv-ing__pick');\n            if (b) onMenu[b.getAttribute('data-mv-id')] = true;\n          });\n          /* \u26A0\uFE0F THE SAME TWO RULES THE INDEX APPLIES, from the section's data.\n           * `onMenu` is built from the index, which has ALREADY dropped the\n           * e-book and the sticker \u2014 so they are absent from it and would pass\n           * `!onMenu[t.id]` and render here as dishes. The exclusion has to be\n           * applied to this path in its own right. */\n          var excluded = {};\n          (root.getAttribute('data-exclude-ids') || '').split(',').forEach(function (id) {\n            var v = id.trim();\n            if (v) excluded[v] = true;\n          });\n          var stripList = root.getAttribute('data-strip') || '';\n\n          var hits = titlesFrom(html)\n            .map(function (t) {\n              return { id: t.id, title: cleanTitle(t.title, stripList) };\n            })\n            .filter(function (t) {\n              return !onMenu[t.id] && !excluded[t.id] &&\n                (hasAll(fold(t.title), words) || hasAll(t.text || '', words));\n            })\n            .slice(0, 40);\n          pastItems.innerHTML = hits\n            .map(function (t) {\n              return (\n                '<li class=\"mv-ing__item\"><button type=\"button\" class=\"mv-ing__pick\" aria-expanded=\"false\" data-mv-id=\"' +\n                esc(t.id) + '\"><span class=\"mv-ing__pick-name\">' + esc(t.title) + '</span></button></li>'\n              );\n            })\n            .join('');\n          past.hidden = hits.length === 0;\n          showNote(term, shown === 0 && hits.length === 0);\n        })\n        .catch(function () {\n          /* The index still filtered. Say what is missing rather than nothing. */\n          past.hidden = false;\n          pastItems.innerHTML =\n            '<li class=\"mv-ing__state\">We couldn\\'t search past dishes just now. Call us at ' +\n            phoneLink(root) + '.</li>';\n        });\n    }\n\n    var t = null;\n    q.addEventListener('input', function () {\n      clearTimeout(t);\n      t = setTimeout(run, 120);\n    });\n\n    if (past) {\n      past.addEventListener('click', function (e) {\n        var btn = e.target.closest ? e.target.closest('.mv-ing__pick') : null;\n        if (!btn) return;\n        togglePanel(root, btn);\n      });\n    }\n  }\n\n  document.addEventListener('DOMContentLoaded', function () {\n    var root = document.getElementById('mv-ingredients');\n    if (!root) return;\n\n    var params = new URLSearchParams(window.location.search);\n    var ids = params.get('ids');\n    var order = params.get('order');\n\n    var orderEl = document.getElementById('mv-ingredients-order');\n    if (orderEl && order) orderEl.textContent = 'Order ' + order;\n\n    wireIndex(root);\n    wireSearch(root);\n\n    /* No parameters is the ordinary case now, not an error: the index below is\n     * the page. Say how the page is reached from a label and leave it at that. */\n    /* \u26A0\uFE0F NOTHING IS PRINTED WHEN NO ORDER IS SCANNED. This used to say \"Scan\n     * the QR code on your shipping label to see your order, or find a dish\n     * below\", which the lede now says in the same words a few lines above it.\n     * Ryan's brief, Task 1. The index below is the page in that case. */\n    if (!ids) return;\n\n    showIds(root, ids, { orderContext: true });\n  });\n\n  /* Exported for scripts/allergen-guard.mjs, which pins claimsFor() and this\n   * table against CLAIMS. Assigning to window is what lets a plain node test\n   * evaluate this file without a DOM. */\n  if (typeof window !== 'undefined') {\n    window.__mvIngredients = {\n      CLAIMS: CLAIMS, expandTag: expandTag, claimsFor: claimsFor, pillsHtml: pillsHtml,\n      wantedIds: wantedIds, SEL: SEL, entrySelector: entrySelector, render: render,\n      isValidId: isValidId, titlesFrom: titlesFrom, cleanTitle: cleanTitle\n    };\n  }\n})();\n"],
  "mappings": "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",
  "names": ["t"]
}
