{
  "version": 3,
  "sources": ["/cdn/shop/t/5/assets/starlyzed-chart-quiz.js?fresh=1782701426341"],
  "sourcesContent": ["const MARKETING_OPT_IN_ENDPOINT = 'https://app.starlyzed.com/api/marketing-opt-in';\nconst SAVED_CHARTS_KEY = 'stz_saved_charts';\nconst MARKETING_TURNSTILE_TOKEN_PROVIDER = 'STARLYZED_MARKETING_TURNSTILE_TOKEN';\nconst DISMISSED_UNTIL_KEY = 'stz_birth_quiz_dismissed_until';\n// Auto-open is a takeover; keep it rare. 30s (not 9s) so a first visit gets to breathe, once per\n// browser session, and never on product pages (they carry their own birth-data intake and purchase\n// intent; interrupting there costs conversions). 2026-07-01 storefront review.\nconst AUTO_OPEN_DELAY_MS = 30000;\nconst AUTO_OPEN_SESSION_KEY = 'stz_birth_quiz_auto_opened';\n\n// Submitting the chart talks to an external service that can be cold, so the customer needs to be\n// told what is happening rather than left with a dead button. The 10s figure is Nielsen's limit for\n// keeping attention on a dialogue, so it is when we REASSURE, not when we give up. The 45s backstop\n// exists only to end a genuinely hung request: a short abort would turn a slow success into a hard\n// failure and throw the lead away, and it would also move the time limit out of the browser (where\n// WCAG 2.2.1 exempts it) and into our content (where it does not).\nconst QUIZ_REASSURE_AFTER_MS = 10000;\nconst QUIZ_SUBMIT_TIMEOUT_MS = 45000;\nconst DISMISS_DAYS = 14;\n\n// SAVED-CHART CONTRACT -- founder decision 2026-08-22 (.team/decisions.md).\n//\n// Keeping a birth chart is its own decision, not a side effect of joining the mailing list, so it\n// has its own unchecked box and its own copy. Every stored chart dies 30 days after it was saved,\n// the customer can delete one or all of them, and no storage failure anywhere in here may stop the\n// quiz, personalization, the cart or checkout: every path returns a value instead of throwing.\nconst SAVED_CHART_SCHEMA = 1;\nconst SAVED_CHART_TTL_MS = 30 * 86400000;\n// Bounds, because this key is not ours alone. It was written by an older build that validated\n// nothing, and anything running on the origin can put anything in it. Each read is capped before it\n// costs work: bytes before we parse, items before we validate, characters before we render.\nconst SAVED_CHART_MAX_BYTES = 20000;\nconst SAVED_CHART_MAX_ITEMS = 12;\nconst SAVED_CHART_MAX_FIELD_CHARS = 120;\nconst SAVED_CHART_MAX_TZ_CHARS = 64;\n// The quiz date input is already bounded `min=\"1900-01-01\" max=\"<today>\"`; storage enforces the same\n// window, so a record cannot hold a birth date the form would never have produced.\nconst SAVED_CHART_MIN_DATE = '1900-01-01';\n// The complete set of keys a saved chart may carry. An unknown key makes the whole record invalid\n// rather than being quietly stripped, so a capability, a Turnstile token, a contact-verification\n// flag, a raw server response, an email or any other unrelated customer state cannot ride along by\n// being ignored. Fail closed: we drop the record, we do not clean it up and keep it.\nconst SAVED_CHART_KEYS = new Set([\n  'v', 'id', 'label', 'isSelf', 'date', 'time', 'place', 'lat', 'lon', 'tz', 'savedAt', 'expiresAt',\n]);\nconst PHOTON_ENDPOINT = 'https://photon.komoot.io/api/';\n\nconst SIGN_LINES = {\n  Aries: 'First through the door. You move before the world is ready, and it follows.',\n  Taurus: 'Built to last. You make beauty something you can touch.',\n  Gemini: 'Two minds, one current. You are the conversation everyone remembers.',\n  Cancer: 'The tide remembers everything. You feel in a key the rest of us forgot.',\n  Leo: 'Born lit. A room reorganizes itself around you and pretends it always did.',\n  Virgo: 'You see the seam in everything and quietly make it perfect.',\n  Libra: 'You hold the scales the rest of us drop. Grace as a discipline.',\n  Scorpio: 'Still water, deep current. People feel you before they see you.',\n  Sagittarius: 'An arrow already loosed. You were never meant to stay.',\n  Capricorn: 'You climb in the dark and reach the summit on schedule.',\n  Aquarius: 'You live a few years ahead and send the rest of us postcards.',\n  Pisces: 'You dream in a language we are still learning to read.',\n};\n\nconst FIELD_SELECTOR = {\n  date: '[name=\"properties[Birth Date]\"]',\n  time: '[name=\"properties[Birth Time]\"]',\n  place: '[data-place-input], [name=\"properties[Birth Place]\"]',\n  lat: '[name=\"properties[_Birth Lat]\"]',\n  lon: '[name=\"properties[_Birth Lng]\"]',\n  timeNote: '[name=\"properties[Birth Time Note]\"]',\n};\n\n// Guarded so the file can be loaded outside a browser. The storage contract below is the privacy\n// boundary and has to be provable without a DOM; scripts/tests/saved-chart-privacy.test.mjs loads\n// this exact asset, with no document, and drives the seam exported at the bottom.\nif (typeof document !== 'undefined') {\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', initStarlyzedChartQuiz, { once: true });\n  } else {\n    initStarlyzedChartQuiz();\n  }\n}\n\nfunction initStarlyzedChartQuiz() {\n  mountQuiz();\n  // Saved-charts picker DISABLED (2026-07-01, Jade's call). The \"Saved charts\" widget front-loaded a\n  // power-user CRM (chart dropdown, \"save for next time\", \"this is my chart\", a dead \"add another\n  // person\" button) above the birth-data form on one-time-buy products; ~95% of buyers are first-timers\n  // who never use it. The clean birth form + DOB gate + age-fit card stand on their own. The one real\n  // value (silent cross-product prefill) can return later as a no-UI one-liner \u2014 the function below is\n  // kept intact for that.\n  // mountProductChartPicker();\n}\n\nfunction mountQuiz() {\n  if (document.querySelector('[data-stz-quiz-root]')) return;\n  if (location.pathname.startsWith('/cart') || location.pathname.startsWith('/checkout')) return;\n\n  const root = document.createElement('div');\n  root.className = 'stz-quiz';\n  root.dataset.stzQuizRoot = 'true';\n  root.innerHTML = `\n    <button class=\"stz-quiz__launcher\" type=\"button\" data-quiz-open>\n      <span aria-hidden=\"true\">\u2726</span>\n      <span>Find your sky</span>\n    </button>\n    <div class=\"stz-quiz__overlay\" data-quiz-overlay hidden>\n      <section class=\"stz-quiz__dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"stz-quiz-title\">\n        <button class=\"stz-quiz__close\" type=\"button\" aria-label=\"Close\" data-quiz-close>\u00D7</button>\n        <form class=\"stz-quiz__form\" data-quiz-form novalidate>\n          <div class=\"stz-quiz__step\" data-step=\"intro\">\n            <p class=\"stz-quiz__eyebrow\">Starlyzed chart ritual</p>\n            <h2 id=\"stz-quiz-title\">Find your sky</h2>\n            <p>Answer three things about the moment you arrived. We will read your chart and render it as something you can wear.</p>\n            <button class=\"stz-quiz__button\" type=\"button\" data-next=\"date\">Begin</button>\n          </div>\n          <div class=\"stz-quiz__step\" data-step=\"date\" hidden>\n            <p class=\"stz-quiz__eyebrow\">01 / Date</p>\n            <h2>What\u2019s your birthday?</h2>\n            <label for=\"stz-quiz-date\">Date of birth<input id=\"stz-quiz-date\" name=\"birth_date\" class=\"stz-quiz__input\" type=\"date\" min=\"1900-01-01\" max=\"${today()}\" data-quiz-date required></label>\n            <p class=\"stz-quiz__error\" data-error=\"date\" hidden></p>\n            <button class=\"stz-quiz__button\" type=\"button\" data-next=\"time\">Next</button>\n          </div>\n          <div class=\"stz-quiz__step\" data-step=\"time\" hidden>\n            <p class=\"stz-quiz__eyebrow\">02 / Time</p>\n            <h2>What time?</h2>\n            <p>Your birth time unlocks your rising sign and your houses. If you do not know it, that is okay.</p>\n            <label for=\"stz-quiz-time\">Birth time<input id=\"stz-quiz-time\" name=\"birth_time\" class=\"stz-quiz__input\" type=\"time\" data-quiz-time></label>\n            <button class=\"stz-quiz__link\" type=\"button\" data-skip-time>I do not know my time</button>\n            <button class=\"stz-quiz__button\" type=\"button\" data-next=\"place\">Next</button>\n          </div>\n          <div class=\"stz-quiz__step\" data-step=\"place\" hidden>\n            <p class=\"stz-quiz__eyebrow\">03 / Place</p>\n            <h2>Where were you born?</h2>\n            <p>Your birthplace places the moon and the planets exactly.</p>\n            <label for=\"stz-quiz-place\">City of birth\n              <input id=\"stz-quiz-place\" name=\"birth_place\" class=\"stz-quiz__input\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" data-quiz-place placeholder=\"City, region, country\">\n            </label>\n            <ul class=\"stz-quiz__suggestions\" data-quiz-suggestions hidden></ul>\n            <button class=\"stz-quiz__button\" type=\"button\" data-next=\"reveal\">Reveal my sign</button>\n          </div>\n          <div class=\"stz-quiz__step\" data-step=\"reveal\" hidden>\n            <p class=\"stz-quiz__eyebrow\">Your surface sign</p>\n            <h2 data-reveal-title>You are a sun.</h2>\n            <p data-reveal-line></p>\n            <p class=\"stz-quiz__small\">This is only the surface. Your full chart holds your moon, your rising, and every placement.</p>\n            <button class=\"stz-quiz__button\" type=\"button\" data-next=\"email\">Get my full chart + 10% off</button>\n          </div>\n          <div class=\"stz-quiz__step\" data-step=\"email\" hidden>\n            <p class=\"stz-quiz__eyebrow\">Chart delivery</p>\n            <h2>Where should we send it?</h2>\n            <p>Enter your email to continue. Opt in below for 10% off your first piece and early access to new drops.</p>\n            <label for=\"stz-quiz-first-name\">First name<input id=\"stz-quiz-first-name\" name=\"first_name\" class=\"stz-quiz__input\" type=\"text\" autocomplete=\"given-name\" data-quiz-first-name></label>\n            <label for=\"stz-quiz-email\">Email address<input id=\"stz-quiz-email\" name=\"email\" class=\"stz-quiz__input\" type=\"email\" autocomplete=\"email\" data-quiz-email required></label>\n            <p class=\"stz-quiz__consent\">Unsubscribe anytime. We never share your details.</p>\n            <label class=\"stz-quiz__checkbox\">\n              <input type=\"checkbox\" data-quiz-marketing-opt-in>\n              <span>Email me 10% off my first piece and early access to new drops.</span>\n            </label>\n            <label class=\"stz-quiz__checkbox\">\n              <input type=\"checkbox\" data-quiz-save-chart>\n              <span>Also keep my birth details in this browser for 30 days so I do not have to type them again. They stay on this device, and you can delete them here anytime.</span>\n            </label>\n            <p class=\"stz-quiz__error\" data-error=\"email\" hidden></p>\n            <p class=\"stz-quiz__error\" data-error=\"form\" hidden></p>\n            <p class=\"stz-quiz__small\" data-status role=\"status\" aria-live=\"polite\" hidden></p>\n            <button class=\"stz-quiz__button\" type=\"submit\" data-submit>Unlock 10%</button>\n          </div>\n          <div class=\"stz-quiz__step\" data-step=\"success\" hidden>\n            <p class=\"stz-quiz__eyebrow\">You are in</p>\n            <h2 data-success-title>You're all set.</h2>\n            <p data-success-body></p>\n            <p class=\"stz-quiz__small\" data-save-note></p>\n            <a class=\"stz-quiz__button stz-quiz__button--link\" href=\"/collections/personalized-astrology-gifts\">Shop your chart</a>\n          </div>\n        </form>\n        <section class=\"stz-quiz__saved\" data-saved-charts hidden>\n          <p class=\"stz-quiz__eyebrow\">Saved on this device</p>\n          <p class=\"stz-quiz__small\" data-saved-note role=\"status\" aria-live=\"polite\" hidden></p>\n          <ul class=\"stz-quiz__saved-list\" data-saved-list></ul>\n          <button class=\"stz-quiz__link\" type=\"button\" data-saved-clear>Delete all saved charts</button>\n        </section>\n      </section>\n    </div>\n  `;\n\n  document.body.appendChild(root);\n  const state = { step: 'intro', date: '', time: '', place: '', lat: '', lon: '', sign: '' };\n  const overlay = root.querySelector('[data-quiz-overlay]');\n  const launcher = root.querySelector('[data-quiz-open]');\n  const close = root.querySelector('[data-quiz-close]');\n  const form = root.querySelector('[data-quiz-form]');\n  const dateInput = root.querySelector('[data-quiz-date]');\n  const timeInput = root.querySelector('[data-quiz-time]');\n  const placeInput = root.querySelector('[data-quiz-place]');\n  const suggestions = root.querySelector('[data-quiz-suggestions]');\n\n  launcher?.addEventListener('click', () => openQuiz(root));\n  close?.addEventListener('click', () => closeQuiz(root, true));\n  overlay?.addEventListener('click', (event) => {\n    if (event.target === overlay) closeQuiz(root, true);\n  });\n  root.querySelector('[data-skip-time]')?.addEventListener('click', () => {\n    if (timeInput) timeInput.value = '';\n    showStep(root, 'place');\n  });\n  root.addEventListener('click', (event) => {\n    const button = event.target instanceof Element ? event.target.closest('[data-next]') : null;\n    if (!button) return;\n    const next = button.getAttribute('data-next') || 'intro';\n    if (next === 'time' && !dateInput.value) {\n      showError(root, 'date', 'Add your birth date to reveal your sign.');\n      return;\n    }\n    if (next === 'reveal') {\n      state.date = dateInput.value;\n      state.time = timeInput.value;\n      state.place = placeInput.value.trim();\n      state.sign = sunSign(state.date);\n      renderReveal(root, state.sign);\n    }\n    showStep(root, next);\n  });\n  form?.addEventListener('submit', async (event) => {\n    event.preventDefault();\n    await submitQuiz(root, state);\n  });\n  placeInput?.addEventListener('input', debounce(() => searchPlace(placeInput, suggestions, state), 280));\n  placeInput?.addEventListener('blur', () => setTimeout(() => closeSuggestions(suggestions), 150));\n\n  // Deletion. Both controls are real buttons inside the dialog, so they are reachable by tab and\n  // fire on Enter and Space with no key handling of our own. Deleting reports what actually\n  // happened: a browser that refuses to write is told about rather than papered over with a\n  // \"deleted\" message for data still sitting on the device.\n  root.addEventListener('click', (event) => {\n    const target = event.target instanceof Element ? event.target : null;\n    if (!target) return;\n    const perChart = target.closest('[data-delete-chart]');\n    if (perChart) {\n      const removed = deleteSavedChart(perChart.getAttribute('data-delete-chart') || '');\n      renderSavedCharts(root, removed\n        ? 'Deleted from this browser.'\n        : 'This browser would not let us delete it. Clearing site data for starlyzed.com removes it.');\n      return;\n    }\n    if (target.closest('[data-saved-clear]')) {\n      const cleared = clearSavedCharts();\n      renderSavedCharts(root, cleared\n        ? 'All saved charts deleted from this browser.'\n        : 'This browser would not let us delete them. Clearing site data for starlyzed.com removes them.');\n    }\n  });\n  renderSavedCharts(root);\n\n  const dismissedUntil = quizDismissedUntil();\n  const onProductPage = location.pathname.includes('/products/');\n  const autoOpenedThisSession = Boolean(sessionGet(AUTO_OPEN_SESSION_KEY));\n  if (!onProductPage && !autoOpenedThisSession && !savedCharts().length && Date.now() > dismissedUntil) {\n    setTimeout(() => {\n      if (!document.hidden && !savedCharts().length) {\n        sessionSet(AUTO_OPEN_SESSION_KEY, '1');\n        openQuiz(root);\n      }\n    }, AUTO_OPEN_DELAY_MS);\n  }\n}\n\n// The saved list is re-read (and therefore re-pruned) every time it is drawn, so an expired chart\n// can never be shown, applied, or deleted from a stale copy.\nfunction renderSavedCharts(root, note) {\n  const panel = root.querySelector('[data-saved-charts]');\n  const list = root.querySelector('[data-saved-list]');\n  const summary = root.querySelector('[data-saved-note]');\n  if (!panel || !list || !summary) return;\n  const charts = savedCharts();\n  // The note is an on-demand status region, not a subtitle. Founder decision 2026-08-24: the opt-in\n  // already promises browser-only storage for 30 days and every row already counts down its own\n  // days, so a third standing copy of the same sentence was repetition. What is left here is the\n  // outcome of an action \u2014 a deletion that worked, or a browser that refused one \u2014 and when there is\n  // no outcome to report the region takes up no room. Unhidden before the text is written so the\n  // live region actually announces the change rather than mutating while hidden.\n  summary.hidden = !note;\n  summary.textContent = note || '';\n  if (!charts.length) {\n    list.innerHTML = '';\n    panel.hidden = !note;\n    return;\n  }\n  panel.hidden = false;\n  list.innerHTML = charts.map((chart) => {\n    const label = chart.label || 'Saved chart';\n    return `\n      <li class=\"stz-quiz__saved-item\">\n        <span>${escapeHtml(label)} \u00B7 born ${escapeHtml(birthDateLabel(chart.date))} \u00B7 ${escapeHtml(expiryLabel(chart.expiresAt))}</span>\n        <button class=\"stz-quiz__saved-delete\" type=\"button\" data-delete-chart=\"${escapeHtml(chart.id)}\" aria-label=\"Delete the saved chart for ${escapeHtml(label)}\">Delete</button>\n      </li>`;\n  }).join('');\n}\n\n// The by-hand fallback for birthDateLabel, used only when the runtime cannot build a formatter.\nconst MONTH_NAMES_EN = [\n  'January', 'February', 'March', 'April', 'May', 'June',\n  'July', 'August', 'September', 'October', 'November', 'December',\n];\n\n/**\n * The visible birthday only. What is stored stays the exact ISO day the customer typed, because\n * that is what the engine reads; this is the label beside it.\n *\n * `timeZone: 'UTC'` is the whole point of the helper. `Date.UTC(1999, 11, 31)` rendered in the\n * browser's own zone reads as December 30 anywhere west of Greenwich, which would show a customer\n * the wrong birthday. Passing no locale is deliberate too: undefined means the browser's locale,\n * so the same record reads as December 31, 1999 in en-US and 31 de diciembre de 1999 in es-ES.\n */\nfunction birthDateLabel(iso) {\n  if (typeof iso !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(iso)) return typeof iso === 'string' ? iso : '';\n  const [year, month, day] = iso.split('-').map(Number);\n  if (month < 1 || month > 12 || day < 1 || day > 31) return iso;\n  try {\n    return new Intl.DateTimeFormat(undefined, {\n      year: 'numeric',\n      month: 'long',\n      day: 'numeric',\n      timeZone: 'UTC',\n    }).format(new Date(Date.UTC(year, month - 1, day)));\n  } catch (error) {\n    // A locale the runtime cannot build is not a reason to show the customer a raw ISO string \u2014\n    // that is the display Jade rejected. Fail open to the same long English shape by hand.\n    return `${MONTH_NAMES_EN[month - 1]} ${day}, ${year}`;\n  }\n}\n\nfunction expiryLabel(expiresAt) {\n  const days = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86400000));\n  return days === 1 ? 'deletes tomorrow' : `deletes in ${days} days`;\n}\n\nfunction mountProductChartPicker() {\n  if (!document.querySelector(FIELD_SELECTOR.date)) return;\n  if (document.querySelector('[data-stz-chart-picker]')) return;\n\n  const firstField = document.querySelector('product-custom-property-component');\n  if (!firstField) return;\n\n  const picker = document.createElement('section');\n  picker.className = 'stz-chart-picker';\n  picker.dataset.stzChartPicker = 'true';\n  picker.innerHTML = `\n    <div class=\"stz-chart-picker__head\">\n      <p class=\"stz-chart-picker__eyebrow\">Saved charts</p>\n      <p data-picker-summary></p>\n    </div>\n    <div class=\"stz-chart-picker__controls\" data-picker-saved hidden>\n      <label>Use a saved chart\n        <select class=\"stz-chart-picker__select\" data-picker-select></select>\n      </label>\n      <button class=\"stz-chart-picker__button\" type=\"button\" data-picker-apply>Use this chart</button>\n      <button class=\"stz-chart-picker__link\" type=\"button\" data-picker-new>Add another person's chart</button>\n    </div>\n    <div class=\"stz-chart-picker__save\">\n      <label class=\"stz-chart-picker__checkbox\">\n        <input type=\"checkbox\" data-picker-save>\n        <span>Save these birth details for next time</span>\n      </label>\n      <input class=\"stz-chart-picker__label\" type=\"text\" data-picker-label placeholder=\"Chart label, e.g. Mom\" hidden>\n      <label class=\"stz-chart-picker__checkbox\" data-picker-self-row hidden>\n        <input type=\"checkbox\" data-picker-self>\n        <span>This is my chart</span>\n      </label>\n    </div>\n  `;\n  firstField.parentNode?.insertBefore(picker, firstField);\n\n  const select = picker.querySelector('[data-picker-select]');\n  const savedWrap = picker.querySelector('[data-picker-saved]');\n  const summary = picker.querySelector('[data-picker-summary]');\n  const saveToggle = picker.querySelector('[data-picker-save]');\n  const saveLabel = picker.querySelector('[data-picker-label]');\n  const selfRow = picker.querySelector('[data-picker-self-row]');\n  const selfToggle = picker.querySelector('[data-picker-self]');\n  const charts = savedCharts();\n\n  if (charts.length) {\n    savedWrap.hidden = false;\n    summary.textContent = `${charts.length} saved ${charts.length === 1 ? 'chart' : 'charts'} on this device.`;\n    select.innerHTML = charts.map((chart) => `<option value=\"${escapeHtml(chart.id)}\">${escapeHtml(chart.label || 'Saved chart')}</option>`).join('');\n    picker.querySelector('[data-picker-apply]')?.addEventListener('click', () => applySelectedChart(select.value));\n    picker.querySelector('[data-picker-new]')?.addEventListener('click', () => {\n      clearBirthFields();\n      saveToggle.checked = true;\n      saveLabel.hidden = false;\n      selfRow.hidden = false;\n      saveLabel.focus();\n    });\n    applyChart(charts[0]);\n  } else {\n    summary.textContent = 'Fill this once. Save it here for your next personalized piece.';\n  }\n\n  saveToggle?.addEventListener('change', () => {\n    saveLabel.hidden = !saveToggle.checked;\n    selfRow.hidden = !saveToggle.checked;\n    if (saveToggle.checked && !saveLabel.value) saveLabel.value = 'Me';\n  });\n\n  const form = document.querySelector('form[data-type=\"add-to-cart-form\"]');\n  form?.addEventListener('submit', () => {\n    if (!saveToggle?.checked) return;\n    const chart = readProductBirthChart(saveLabel?.value || 'Me', Boolean(selfToggle?.checked));\n    if (chart.date) saveChart(chart);\n  }, true);\n\n  function applySelectedChart(id) {\n    const chart = savedCharts().find((item) => item.id === id);\n    if (chart) applyChart(chart);\n  }\n}\n\nfunction openQuiz(root) {\n  const overlay = root.querySelector('[data-quiz-overlay]');\n  if (!overlay) return;\n  overlay.hidden = false;\n  document.documentElement.classList.add('stz-quiz-open');\n  renderSavedCharts(root);\n  showStep(root, 'intro');\n  root.querySelector('[data-quiz-close]')?.focus();\n}\n\nfunction closeQuiz(root, remember) {\n  const overlay = root.querySelector('[data-quiz-overlay]');\n  if (overlay) overlay.hidden = true;\n  document.documentElement.classList.remove('stz-quiz-open');\n  if (remember) rememberQuizDismissal(DISMISS_DAYS);\n}\n\nfunction showStep(root, step) {\n  for (const node of root.querySelectorAll('[data-step]')) node.hidden = node.getAttribute('data-step') !== step;\n}\n\nasync function submitQuiz(root, state) {\n  const email = root.querySelector('[data-quiz-email]')?.value.trim();\n  const firstName = root.querySelector('[data-quiz-first-name]')?.value.trim();\n  const date = root.querySelector('[data-quiz-date]')?.value;\n  const time = root.querySelector('[data-quiz-time]')?.value;\n  const place = root.querySelector('[data-quiz-place]')?.value.trim();\n  const wantsMarketingEmails = Boolean(root.querySelector('[data-quiz-marketing-opt-in]')?.checked);\n  const wantsSavedChart = Boolean(root.querySelector('[data-quiz-save-chart]')?.checked);\n  if (!email || !email.includes('@')) {\n    showError(root, 'email', 'Add a valid email to continue.');\n    return;\n  }\n  const button = root.querySelector('[data-submit]');\n  if (button && button.disabled) return;\n  if (button) button.disabled = true;\n\n  hideNote(root, 'form');\n  hideNote(root, 'email');\n\n  const chart = {\n    id: 'me',\n    label: 'Me',\n    isSelf: true,\n    date,\n    time: time || '',\n    place: place || '',\n    lat: state.lat || '',\n    lon: state.lon || '',\n    tz: '',\n  };\n  const saveOutcome = persistQuizChart(chart, wantsSavedChart);\n\n  if (!wantsMarketingEmails) {\n    hideNote(root, 'status');\n    renderSuccess(root, { optedIn: false });\n    const saveNote = root.querySelector('[data-save-note]');\n    if (saveNote) saveNote.textContent = savedChartNote(saveOutcome);\n    renderSavedCharts(root);\n    rememberQuizDismissal(365);\n    if (button) button.disabled = false;\n    return;\n  }\n\n  showStatus(root, 'Joining the list.');\n  const reassure = setTimeout(\n    () => showStatus(root, 'Still joining. This can take a moment on first load.'),\n    QUIZ_REASSURE_AFTER_MS,\n  );\n  const controller = new AbortController();\n  const backstop = setTimeout(() => controller.abort(), QUIZ_SUBMIT_TIMEOUT_MS);\n\n  try {\n    if (place && (!state.lat || !state.lon)) {\n      await resolveTypedPlace(place, state, controller.signal);\n    }\n    const birthMonthDay = toBirthMonthDay(date);\n    const challengeToken = await resolveMarketingChallengeToken(controller.signal);\n    const response = await fetch(MARKETING_OPT_IN_ENDPOINT, {\n      signal: controller.signal,\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n      body: JSON.stringify({\n        email,\n        firstName: firstName || undefined,\n        marketingOptIn: true,\n        sunSign: state.sign || undefined,\n        birthMonthDay: birthMonthDay || undefined,\n        isSelf: true,\n        quizCompleted: true,\n        source: 'quiz',\n        challengeToken,\n      }),\n    });\n    let json = {};\n    try {\n      json = await response.json();\n    } catch (parseError) {\n      if (parseError && parseError.name === 'AbortError') throw parseError;\n    }\n    if (!response.ok || json.ok === false) {\n      throw new Error('opt_in_failed');\n    }\n    hideNote(root, 'status');\n    renderSuccess(root, { optedIn: true });\n    const saveNote = root.querySelector('[data-save-note]');\n    if (saveNote) saveNote.textContent = savedChartNote(saveOutcome);\n    renderSavedCharts(root);\n    rememberQuizDismissal(365);\n  } catch (error) {\n    hideNote(root, 'status');\n    const timedOut = error && error.name === 'AbortError';\n    showError(\n      root,\n      'form',\n      timedOut\n        ? 'That took too long. Your details are still here \u2014 try again when the connection is steadier.'\n        : 'We could not join the list just now. Your details are still here \u2014 try again in a moment.',\n    );\n  } finally {\n    clearTimeout(reassure);\n    clearTimeout(backstop);\n    if (button) button.disabled = false;\n  }\n}\n\nasync function resolveMarketingChallengeToken(signal) {\n  const provider = typeof window !== 'undefined' ? window[MARKETING_TURNSTILE_TOKEN_PROVIDER] : undefined;\n  if (typeof provider === 'function') {\n    const token = await provider({ action: 'starlyzed_marketing_opt_in', signal });\n    if (typeof token === 'string' && token.trim()) return token;\n  }\n  if (typeof provider === 'string' && provider.trim()) return provider;\n  throw new Error('challenge_unavailable');\n}\n\nfunction toBirthMonthDay(isoDate) {\n  if (typeof isoDate !== 'string') return '';\n  const match = /^\\d{4}-(\\d{2})-(\\d{2})$/.exec(isoDate);\n  return match ? `${match[1]}-${match[2]}` : '';\n}\n\nfunction renderSuccess(root, { optedIn }) {\n  const title = root.querySelector('[data-success-title]');\n  const body = root.querySelector('[data-success-body]');\n  if (optedIn) {\n    if (title) title.textContent = 'You\\u2019re in.';\n    if (body) body.innerHTML = 'Your code is <strong>WELCOME10</strong> for 10% off your first order. You are on the list for early access.';\n  } else {\n    if (title) title.textContent = 'Your sky is here.';\n    if (body) body.textContent = 'You can shop your chart anytime. Come back when you\\u2019re ready for 10% off and early access to new drops.';\n  }\n  showStep(root, 'success');\n}\n\n// The single write path out of the quiz. Nothing else in this file persists a chart from the quiz\n// flow, so \"the customer did not tick the box\" is one branch, in one place, that can be proved.\nfunction persistQuizChart(chart, wantsSavedChart) {\n  if (!wantsSavedChart) return 'skipped';\n  return saveChart(chart) ? 'saved' : 'failed';\n}\n\n// Say what actually happened. A browser in private mode, or out of quota, silently refuses the\n// write, and promising a saved chart there is a promise about the customer's own data that we know\n// to be false.\nfunction savedChartNote(outcome) {\n  if (outcome === 'saved') {\n    return 'Your birth details are saved in this browser for 30 days. Reopen this window to delete them sooner.';\n  }\n  if (outcome === 'failed') {\n    return 'We could not save your birth details on this device, so nothing was stored here.';\n  }\n  return 'Nothing was stored on this device. You will add your birth details on the product page when you order.';\n}\n\nfunction renderReveal(root, sign) {\n  const title = root.querySelector('[data-reveal-title]');\n  const line = root.querySelector('[data-reveal-line]');\n  if (title) title.textContent = `You are a ${sign} sun.`;\n  if (line) line.textContent = SIGN_LINES[sign] || '';\n}\n\nasync function searchPlace(input, suggestions, state) {\n  state.lat = '';\n  state.lon = '';\n  const query = input.value.trim();\n  if (!suggestions || query.length < 3) {\n    closeSuggestions(suggestions);\n    return;\n  }\n  try {\n    // fetch DEEP, show few (see birth-place.js): counties dominate the shallow ranks for bare\n    // same-named queries; a deep fetch + populated-first sort puts the real towns in view.\n    const response = await fetch(`${PHOTON_ENDPOINT}?q=${encodeURIComponent(query)}&limit=${QUIZ_PLACE_FETCH_LIMIT}&lang=en&osm_tag=place`);\n    if (!response.ok) return;\n    const data = await response.json();\n    // Same disambiguation as birth-place.js (the wrong-Warren bug): admin areas always type-tagged,\n    // populated places sorted first, colliding labels gain their county, THEN dedupe \u2014 so two\n    // different same-named places never fold into one row.\n    const isPopulated = (f) => QUIZ_POPULATED_PLACE_TYPES.has(String((f.properties || {}).osm_value || '').replace(/_/g, ' '));\n    const featuresOrdered = [...(data.features || []).filter(isPopulated), ...(data.features || []).filter((f) => !isPopulated(f))];\n    const dupCounts = (list) => {\n      const m = new Map();\n      for (const item of list) m.set(item.label, (m.get(item.label) || 0) + 1);\n      return m;\n    };\n    let places = featuresOrdered.map((feature) => ({ feature, label: placeLabel(feature) }));\n    const counts = dupCounts(places);\n    places = places.map((item) => (counts.get(item.label) > 1 ? { feature: item.feature, label: placeLabel(item.feature, { county: true }) } : item));\n    const seen = new Set();\n    places = places.filter((item) => {\n      if (!item.label || seen.has(item.label)) return false;\n      seen.add(item.label);\n      return true;\n    }).slice(0, QUIZ_PLACE_MAX_SUGGESTIONS);\n    suggestions.innerHTML = places.map((item, index) => `<li><button type=\"button\" data-place-index=\"${index}\">${escapeHtml(item.label)}</button></li>`).join('');\n    suggestions.hidden = places.length === 0;\n    suggestions.querySelectorAll('[data-place-index]').forEach((button) => {\n      button.addEventListener('mousedown', (event) => {\n        event.preventDefault();\n        const item = places[Number(button.getAttribute('data-place-index'))];\n        const coords = item.feature?.geometry?.coordinates || [];\n        input.value = item.label;\n        state.place = item.label;\n        state.lon = coords[0] != null ? String(coords[0]) : '';\n        state.lat = coords[1] != null ? String(coords[1]) : '';\n        closeSuggestions(suggestions);\n      });\n    });\n  } catch {\n    closeSuggestions(suggestions);\n  }\n}\n\nasync function resolveTypedPlace(query, state, signal) {\n  try {\n    const response = await fetch(`${PHOTON_ENDPOINT}?q=${encodeURIComponent(query)}&limit=1&lang=en`, { signal });\n    if (!response.ok) return;\n    const data = await response.json();\n    const feature = data.features?.[0];\n    const coords = feature?.geometry?.coordinates || [];\n    const label = placeLabel(feature) || query;\n    state.place = label;\n    state.lon = coords[0] != null ? String(coords[0]) : '';\n    state.lat = coords[1] != null ? String(coords[1]) : '';\n    const input = document.querySelector('[data-quiz-place]');\n    if (input && state.place) input.value = state.place;\n  } catch (error) {\n    // An abort means the whole attempt timed out and the caller has to hear about it. Everything\n    // else stays tolerated: a typed place is still useful for the contact record, and product pages\n    // ask for an exact place when coordinates are missing.\n    if (error && error.name === 'AbortError') throw error;\n  }\n}\n\n// the place types a birth certificate names, shown untagged; everything else carries its type\n// (a county named \"Warren\" is indistinguishable from Warren the city without it)\nconst QUIZ_PLAIN_PLACE_TYPES = new Set(['city', 'town', 'village', 'hamlet']);\n// populated places (sorted above counties/regions/districts in the suggestion list)\nconst QUIZ_POPULATED_PLACE_TYPES = new Set(['city', 'town', 'village', 'hamlet', 'suburb', 'quarter', 'neighbourhood', 'borough', 'locality']);\n// fetch deep, show few: counties dominate the shallow ranks for bare same-named queries\nconst QUIZ_PLACE_FETCH_LIMIT = 30;\nconst QUIZ_PLACE_MAX_SUGGESTIONS = 8;\n\nfunction placeLabel(feature, { county = false } = {}) {\n  const props = feature?.properties || {};\n  const name = props.name || props.city || '';\n  const t = String(props.osm_value || '').replace(/_/g, ' ');\n  const typed = name && t && !QUIZ_PLAIN_PLACE_TYPES.has(t) ? `${name} (${t})` : name;\n  return [typed, county ? props.county : '', props.state, props.country].filter(Boolean).join(', ');\n}\n\nfunction closeSuggestions(suggestions) {\n  if (!suggestions) return;\n  suggestions.hidden = true;\n  suggestions.innerHTML = '';\n}\n\nfunction applyChart(chart) {\n  setValue(FIELD_SELECTOR.date, chart.date || '');\n  setValue(FIELD_SELECTOR.time, chart.time || '');\n  setValue(FIELD_SELECTOR.place, chart.place || '');\n  setValue(FIELD_SELECTOR.lat, chart.lat || '');\n  setValue(FIELD_SELECTOR.lon, chart.lon || '');\n  setValue(FIELD_SELECTOR.timeNote, chart.time ? '' : 'Unknown time');\n}\n\nfunction clearBirthFields() {\n  Object.values(FIELD_SELECTOR).forEach((selector) => setValue(selector, ''));\n}\n\nfunction setValue(selector, value) {\n  const field = document.querySelector(selector);\n  if (!field) return;\n  field.value = value;\n  field.dispatchEvent(new Event('input', { bubbles: true }));\n  field.dispatchEvent(new Event('change', { bubbles: true }));\n}\n\nfunction readProductBirthChart(label, isSelf) {\n  return {\n    id: isSelf ? 'me' : `chart-${Date.now()}`,\n    label: label.trim() || (isSelf ? 'Me' : 'Saved chart'),\n    isSelf,\n    date: document.querySelector(FIELD_SELECTOR.date)?.value || '',\n    time: document.querySelector(FIELD_SELECTOR.time)?.value || '',\n    place: document.querySelector(FIELD_SELECTOR.place)?.value || '',\n    lat: document.querySelector(FIELD_SELECTOR.lat)?.value || '',\n    lon: document.querySelector(FIELD_SELECTOR.lon)?.value || '',\n    tz: '',\n  };\n}\n\n/* ---------------------------------------------------------------------------------------------\n   Saved-chart storage. Everything below is the privacy contract, and it has two jobs at once:\n   keep what is stored to the smallest honest thing, and never let storage itself become a way to\n   break the store. Web Storage throws rather than returns on a blocked origin, a full quota, or\n   Safari private mode, and those throws used to escape into the quiz submit and the add-to-cart\n   path. Every accessor here is contained and returns a value; nothing in this section can throw\n   into personalization, the cart, or checkout.\n   ------------------------------------------------------------------------------------------- */\n\nfunction storageGet(key) {\n  try {\n    return localStorage.getItem(key);\n  } catch {\n    return null;\n  }\n}\n\nfunction storageSet(key, value) {\n  try {\n    localStorage.setItem(key, value);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction storageRemove(key) {\n  try {\n    localStorage.removeItem(key);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction sessionGet(key) {\n  try {\n    return sessionStorage.getItem(key);\n  } catch {\n    return null;\n  }\n}\n\nfunction sessionSet(key, value) {\n  try {\n    sessionStorage.setItem(key, value);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction rememberQuizDismissal(days) {\n  return storageSet(DISMISSED_UNTIL_KEY, String(Date.now() + days * 86400000));\n}\n\nfunction quizDismissedUntil() {\n  const value = Number(storageGet(DISMISSED_UNTIL_KEY) || 0);\n  return Number.isFinite(value) ? value : 0;\n}\n\n/**\n * A real date on the real calendar, inside the window the form allows. `/\\d{4}-\\d{2}-\\d{2}/` is a\n * shape check, not a date check: it happily passes 9999-99-99 and 2023-02-29, and Date parsing\n * rolls those forward into a different, wrong day rather than refusing them. Building the day back\n * out of Date.UTC and demanding the same three numbers is what makes a non-day fail closed.\n */\nfunction isCalendarDate(value) {\n  if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return false;\n  if (value < SAVED_CHART_MIN_DATE || value > today()) return false;\n  const [year, month, day] = value.split('-').map(Number);\n  const stamp = new Date(Date.UTC(year, month - 1, day));\n  return stamp.getUTCFullYear() === year && stamp.getUTCMonth() === month - 1 && stamp.getUTCDate() === day;\n}\n\n// 24-hour clock time. The old shape check accepted 99:99, which is not a time and would have been\n// handed to the engine as one.\nfunction isClockTime(value) {\n  return typeof value === 'string' && /^([01]\\d|2[0-3]):[0-5]\\d$/.test(value);\n}\n\nfunction boundedText(value, max) {\n  if (value == null) return '';\n  if (typeof value !== 'string' || value.length > max) return null;\n  return value;\n}\n\n// Coordinates come back from Photon as strings and go into line-item properties as strings, so they\n// stay strings here. The shape check stops the field being used as a general text slot, and the\n// limit is per axis: latitude past 90 is not a place on Earth, and one shared 180 ceiling let every\n// impossible latitude through.\nfunction boundedCoordinate(value, limit) {\n  const text = boundedText(value, 32);\n  if (text === null || text === '') return text;\n  if (!/^-?\\d{1,3}(\\.\\d{1,10})?$/.test(text)) return null;\n  return Math.abs(Number(text)) <= limit ? text : null;\n}\n\n// Metadata is either absent - a legacy record, written before this contract existed, which migrates\n// - or present and correct. It is never present and repaired: a record carrying a version we do not\n// know, or a timestamp that is not a timestamp, is a record we cannot reason about, and guessing on\n// its behalf is exactly the \"accept anything, fix it up\" behavior this contract replaced.\nfunction readMetaNumber(value) {\n  if (value === undefined) return { missing: true, value: null };\n  if (typeof value !== 'number' || !Number.isFinite(value)) return { invalid: true, value: null };\n  return { value };\n}\n\n/**\n * Turn one stored value into a chart we are willing to keep, or into nothing.\n *\n * Fail closed everywhere: an unknown key, a field of the wrong type or beyond its length, a\n * malformed date or time, a coordinate that is not one, or a deadline that has passed all reject\n * the whole record. Nothing is repaired and kept, because a record we had to repair is a record we\n * do not understand, and this key holds someone's birth data.\n */\nfunction normalizeSavedChart(raw, now) {\n  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;\n  for (const key of Object.keys(raw)) {\n    if (!SAVED_CHART_KEYS.has(key)) return null;\n  }\n  if (typeof raw.id !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(raw.id)) return null;\n  if (!isCalendarDate(raw.date)) return null;\n  if (raw.isSelf != null && typeof raw.isSelf !== 'boolean') return null;\n  // A schema version we did not write is not ours to interpret.\n  if (raw.v !== undefined && raw.v !== SAVED_CHART_SCHEMA) return null;\n\n  const time = raw.time == null ? '' : raw.time;\n  if (time !== '' && !isClockTime(time)) return null;\n\n  const label = boundedText(raw.label, SAVED_CHART_MAX_FIELD_CHARS);\n  const place = boundedText(raw.place, SAVED_CHART_MAX_FIELD_CHARS);\n  const tz = boundedText(raw.tz, SAVED_CHART_MAX_TZ_CHARS);\n  const lat = boundedCoordinate(raw.lat, 90);\n  const lon = boundedCoordinate(raw.lon, 180);\n  if (label === null || place === null || tz === null || lat === null || lon === null) return null;\n\n  // The deadline is ours, not the record's. A record with NO expiry is legacy - written before the\n  // policy existed - and gets exactly one bounded life starting now; a record claiming a longer life\n  // than the policy allows is clamped to the policy; a record whose expiry is present but is not a\n  // number is malformed and dies here rather than being handed a fresh 30 days as if it were\n  // legacy. Either way nothing outlives 30 days, and an expired record is dropped before any caller\n  // can read a value out of it.\n  const ceiling = now + SAVED_CHART_TTL_MS;\n  const claimed = readMetaNumber(raw.expiresAt);\n  if (claimed.invalid) return null;\n  const expiresAt = claimed.missing ? ceiling : Math.min(claimed.value, ceiling);\n  if (expiresAt <= now) return null;\n\n  // A save that has not happened yet did not happen. The expiry above is clamped independently, so\n  // this only rejects records that contradict themselves.\n  const claimedSavedAt = readMetaNumber(raw.savedAt);\n  if (claimedSavedAt.invalid) return null;\n  if (!claimedSavedAt.missing && claimedSavedAt.value > now) return null;\n  const savedAt = claimedSavedAt.missing ? now : claimedSavedAt.value;\n\n  return {\n    v: SAVED_CHART_SCHEMA,\n    id: raw.id,\n    label: label || 'Saved chart',\n    isSelf: raw.isSelf === true,\n    date: raw.date,\n    time,\n    place,\n    lat,\n    lon,\n    tz,\n    savedAt,\n    expiresAt,\n  };\n}\n\n/**\n * Read the key with the work bounded at every step: bytes before we parse, items before we\n * validate, fields before we keep them. `changed` reports whether what we are willing to keep\n * differs from what is on disk, which is what drives the prune/migrate write-back in savedCharts().\n */\nfunction loadSavedCharts() {\n  const now = Date.now();\n  const raw = storageGet(SAVED_CHARTS_KEY);\n  if (raw == null) return { charts: [], changed: false, present: false };\n  if (typeof raw !== 'string' || raw.length > SAVED_CHART_MAX_BYTES) {\n    return { charts: [], changed: true, present: true };\n  }\n  let parsed = null;\n  try {\n    parsed = JSON.parse(raw);\n  } catch {\n    return { charts: [], changed: true, present: true };\n  }\n  if (!Array.isArray(parsed)) return { charts: [], changed: true, present: true };\n\n  const charts = [];\n  const seen = new Set();\n  for (const item of parsed.slice(0, SAVED_CHART_MAX_ITEMS)) {\n    const chart = normalizeSavedChart(item, now);\n    if (!chart || seen.has(chart.id)) continue;\n    seen.add(chart.id);\n    charts.push(chart);\n  }\n  const changed = charts.length !== parsed.length\n    || charts.some((chart, index) => JSON.stringify(chart) !== JSON.stringify(parsed[index]));\n  return { charts, changed, present: true };\n}\n\n/**\n * The only read. It prunes before it returns: expired, malformed, oversized and excess records are\n * taken out of storage BEFORE any value can be applied to a form, and a valid legacy record is\n * written back once carrying the bounded 30-day deadline.\n *\n * That write-back is not decoration, so it is not best effort either. A legacy record carries no\n * deadline, so the deadline is invented on read - and an invented deadline that never reaches the\n * device is not a deadline, it is a number this page load happened to think of. Hand the record\n * back anyway and the next read invents a later one from a later `now`, so the thirty days that\n * just elapsed never apply and the chart lives forever in thirty-day steps. Read-success with\n * write-denial is an ordinary browser state - full quota, private mode, blocked storage - not an\n * exotic one. So a list that differs from what is on disk becomes usable only once it IS on disk;\n * if the write is refused we take the key out instead and return nothing. Removal is the part that\n * stays best effort: a browser may refuse that too, and the bytes may survive, but they survive\n * unusable, because every read runs this same gate. Callers get an empty list and carry on - the\n * quiz, the cart and the checkout never see a failure here.\n */\nfunction savedCharts() {\n  const { charts, changed, present } = loadSavedCharts();\n  if (!changed) return charts;\n  if (!charts.length) {\n    if (present) storageRemove(SAVED_CHARTS_KEY);\n    return charts;\n  }\n  if (storageSet(SAVED_CHARTS_KEY, JSON.stringify(charts))) return charts;\n  storageRemove(SAVED_CHARTS_KEY);\n  return [];\n}\n\n/**\n * The only write. The candidate is validated by exactly the same rules as anything already on disk,\n * with a fresh 30-day deadline, so a caller cannot store a shape a read would reject. Returns\n * whether the chart is genuinely on the device: false for a rejected record and false for a browser\n * that refused the write, and callers tell the customer which happened.\n */\nfunction saveChart(chart) {\n  const now = Date.now();\n  // Metadata belongs to the contract, not the caller. A candidate that already carries a version or\n  // timestamps is not a fresh chart, and stamping our own over the top would be the same silent\n  // repair the read path refuses - it would let a bad value in by overwriting it instead of\n  // noticing it. Callers hand over chart fields; the deadline is ours to set.\n  if (chart && typeof chart === 'object'\n    && (chart.v !== undefined || chart.savedAt !== undefined || chart.expiresAt !== undefined)) {\n    return false;\n  }\n  const record = normalizeSavedChart(\n    { ...chart, v: SAVED_CHART_SCHEMA, savedAt: now, expiresAt: now + SAVED_CHART_TTL_MS },\n    now,\n  );\n  if (!record) return false;\n  const next = [record, ...savedCharts().filter((item) => item.id !== record.id)]\n    .slice(0, SAVED_CHART_MAX_ITEMS);\n  return storageSet(SAVED_CHARTS_KEY, JSON.stringify(next));\n}\n\nfunction deleteSavedChart(id) {\n  const remaining = savedCharts().filter((chart) => chart.id !== id);\n  if (!remaining.length) return storageRemove(SAVED_CHARTS_KEY);\n  return storageSet(SAVED_CHARTS_KEY, JSON.stringify(remaining));\n}\n\nfunction clearSavedCharts() {\n  return storageRemove(SAVED_CHARTS_KEY);\n}\n\nfunction sunSign(dateValue) {\n  const [, monthRaw, dayRaw] = String(dateValue || '').split('-').map(Number);\n  const value = monthRaw * 100 + dayRaw;\n  if (value >= 321 && value <= 419) return 'Aries';\n  if (value >= 420 && value <= 520) return 'Taurus';\n  if (value >= 521 && value <= 620) return 'Gemini';\n  if (value >= 621 && value <= 722) return 'Cancer';\n  if (value >= 723 && value <= 822) return 'Leo';\n  if (value >= 823 && value <= 922) return 'Virgo';\n  if (value >= 923 && value <= 1022) return 'Libra';\n  if (value >= 1023 && value <= 1121) return 'Scorpio';\n  if (value >= 1122 && value <= 1221) return 'Sagittarius';\n  if (value >= 1222 || value <= 119) return 'Capricorn';\n  if (value >= 120 && value <= 218) return 'Aquarius';\n  return 'Pisces';\n}\n\nfunction showError(root, key, message) {\n  const error = root.querySelector(`[data-error=\"${key}\"]`);\n  if (!error) return;\n  error.textContent = message;\n  error.hidden = false;\n}\n\n// The status line is role=\"status\" aria-live=\"polite\": it appears without moving focus, which is\n// exactly the case WCAG 2.2 SC 4.1.3 covers, so it has to be announced rather than merely visible.\nfunction showStatus(root, message) {\n  const status = root.querySelector('[data-status]');\n  if (!status) return;\n  status.textContent = message;\n  status.hidden = false;\n}\n\nfunction hideNote(root, key) {\n  const node = key === 'status'\n    ? root.querySelector('[data-status]')\n    : root.querySelector(`[data-error=\"${key}\"]`);\n  if (!node) return;\n  node.textContent = '';\n  node.hidden = true;\n}\n\nfunction today() {\n  return new Date().toISOString().slice(0, 10);\n}\n\nfunction debounce(fn, wait) {\n  let timer = 0;\n  return (...args) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => fn(...args), wait);\n  };\n}\n\nfunction escapeHtml(value) {\n  return String(value).replace(/[&<>\"']/g, (char) => ({\n    '&': '&amp;',\n    '<': '&lt;',\n    '>': '&gt;',\n    '\"': '&quot;',\n    \"'\": '&#39;',\n  })[char]);\n}\n\n// Functions only, never data. The storage contract above is a privacy boundary, and a boundary that\n// cannot be exercised without a browser cannot be proved; scripts/tests/saved-chart-privacy.test.mjs\n// loads this asset with no DOM and drives exactly these entry points, and contained browser QA can\n// use the same seam to check deletion.\nglobalThis.StarlyzedSavedCharts = {\n  SAVED_CHARTS_KEY,\n  SAVED_CHART_TTL_MS,\n  SAVED_CHART_MAX_ITEMS,\n  SAVED_CHART_MAX_BYTES,\n  MARKETING_OPT_IN_ENDPOINT,\n  savedCharts,\n  saveChart,\n  deleteSavedChart,\n  clearSavedCharts,\n  persistQuizChart,\n  savedChartNote,\n  birthDateLabel,\n  renderSavedCharts,\n  rememberQuizDismissal,\n  quizDismissedUntil,\n  sessionGet,\n  sessionSet,\n  toBirthMonthDay,\n};\n"],
  "mappings": "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",
  "names": []
}
